svc/conntrack.in: Split out a base class from `InetAddress'.
[tripe] / svc / conntrack.in
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 ###
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.
17 ###
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.
22 ###
23 ### You should have received a copy of the GNU General Public License
24 ### along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
25
26 VERSION = '@VERSION@'
27
28 ###--------------------------------------------------------------------------
29 ### External dependencies.
30
31 from ConfigParser import RawConfigParser
32 from optparse import OptionParser
33 import os as OS
34 import sys as SYS
35 import socket as S
36 import mLib as M
37 import tripe as T
38 import dbus as D
39 import re as RX
40 for i in ['mainloop', 'mainloop.glib']:
41 __import__('dbus.%s' % i)
42 try: from gi.repository import GLib as G
43 except ImportError: import gobject as G
44 from struct import pack, unpack
45 from cStringIO import StringIO
46
47 SM = T.svcmgr
48 ##__import__('rmcr').__debug = True
49
50 ###--------------------------------------------------------------------------
51 ### Utilities.
52
53 class struct (object):
54 """A simple container object."""
55 def __init__(me, **kw):
56 me.__dict__.update(kw)
57
58 def loadb(s):
59 n = 0
60 for ch in s: n = 256*n + ord(ch)
61 return n
62
63 def storeb(n, wd = None):
64 if wd is None: wd = n.bit_length()
65 s = StringIO()
66 for i in xrange((wd - 1)&-8, -8, -8): s.write(chr((n >> i)&0xff))
67 return s.getvalue()
68
69 ###--------------------------------------------------------------------------
70 ### Address manipulation.
71 ###
72 ### I think this is the most demanding application, in terms of address
73 ### hacking, in the entire TrIPE suite. At least we don't have to do it in
74 ### C.
75
76 class BaseAddress (object):
77 def __init__(me, addrstr, maskstr = None):
78 me._setaddr(addrstr)
79 if maskstr is None:
80 me.mask = -1
81 elif maskstr.isdigit():
82 me.mask = (1 << me.NBITS) - (1 << me.NBITS - int(maskstr))
83 else:
84 me._setmask(maskstr)
85 if me.addr&~me.mask:
86 raise ValueError('network contains bits set beyond mask')
87 def _addrstr_to_int(me, addrstr):
88 try: return loadb(S.inet_pton(me.AF, addrstr))
89 except S.error: raise ValueError('bad address syntax')
90 def _int_to_addrstr(me, n):
91 return S.inet_ntop(me.AF, storeb(me.addr, me.NBITS))
92 def _setmask(me, maskstr):
93 raise ValueError('only prefix masked supported')
94 def _maskstr(me):
95 raise ValueError('only prefix masked supported')
96 def sockaddr(me, port = 0):
97 if me.mask != -1: raise ValueError('not a simple address')
98 return me._sockaddr(port)
99 def __str__(me):
100 addrstr = me._addrstr()
101 if me.mask == -1:
102 return addrstr
103 else:
104 inv = me.mask ^ ((1 << me.NBITS) - 1)
105 if (inv&(inv + 1)) == 0:
106 return '%s/%d' % (addrstr, me.NBITS - inv.bit_length())
107 else:
108 return '%s/%s' % (addrstr, me._maskstr())
109 def withinp(me, net):
110 if type(net) != type(me): return False
111 if (me.mask&net.mask) != net.mask: return False
112 if (me.addr ^ net.addr)&net.mask: return False
113 return me._withinp(net)
114 def eq(me, other):
115 if type(me) != type(other): return False
116 if me.mask != other.mask: return False
117 if me.addr != other.addr: return False
118 return me._eq(other)
119 def _withinp(me, net):
120 return True
121 def _eq(me, other):
122 return True
123
124 class InetAddress (BaseAddress):
125 AF = S.AF_INET
126 AFNAME = 'IPv4'
127 NBITS = 32
128 def _addrstr_to_int(me, addrstr):
129 try: return loadb(S.inet_aton(addrstr))
130 except S.error: raise ValueError('bad address syntax')
131 def _setaddr(me, addrstr):
132 me.addr = me._addrstr_to_int(addrstr)
133 def _setmask(me, maskstr):
134 me.mask = me._addrstr_to_int(maskstr)
135 def _addrstr(me):
136 return me._int_to_addrstr(me.addr)
137 def _maskstr(me):
138 return me._int_to_addrstr(me.mask)
139 def _sockaddr(me, port = 0):
140 return (me._addrstr(), port)
141 @classmethod
142 def from_sockaddr(cls, sa):
143 addr, port = (lambda a, p: (a, p))(*sa)
144 return cls(addr), port
145
146 def parse_address(addrstr, maskstr = None):
147 return InetAddress(addrstr, maskstr)
148
149 def parse_net(netstr):
150 try: sl = netstr.index('/')
151 except ValueError: raise ValueError('missing mask')
152 return parse_address(netstr[:sl], netstr[sl + 1:])
153
154 def straddr(a): return a is None and '#<none>' or str(a)
155
156 ###--------------------------------------------------------------------------
157 ### Parse the configuration file.
158
159 ## Hmm. Should I try to integrate this with the peers database? It's not a
160 ## good fit; it'd need special hacks in tripe-newpeers. And the use case for
161 ## this service are largely going to be satellite notes, I don't think
162 ## scalability's going to be a problem.
163
164 TESTADDRS = [InetAddress('1.2.3.4')]
165
166 CONFSYNTAX = [
167 ('COMMENT', RX.compile(r'^\s*($|[;#])')),
168 ('GRPHDR', RX.compile(r'^\s*\[(.*)\]\s*$')),
169 ('ASSGN', RX.compile(r'\s*([\w.-]+)\s*[:=]\s*(|\S|\S.*\S)\s*$'))]
170
171 class ConfigError (Exception):
172 def __init__(me, file, lno, msg):
173 me.file = file
174 me.lno = lno
175 me.msg = msg
176 def __str__(me):
177 return '%s:%d: %s' % (me.file, me.lno, me.msg)
178
179 class Config (object):
180 """
181 Represents a configuration file.
182
183 The most interesting thing is probably the `groups' slot, which stores a
184 list of pairs (NAME, PATTERNS); the NAME is a string, and the PATTERNS a
185 list of (TAG, PEER, NETS) triples. The implication is that there should be
186 precisely one peer from the set, and that it should be named TAG, where
187 (TAG, PEER, NETS) is the first triple such that the host's primary IP
188 address (if PEER is None -- or the IP address it would use for
189 communicating with PEER) is within one of the networks defined by NETS.
190 """
191
192 def __init__(me, file):
193 """
194 Construct a new Config object, reading the given FILE.
195 """
196 me._file = file
197 me._fwatch = M.FWatch(file)
198 me._update()
199
200 def check(me):
201 """
202 See whether the configuration file has been updated.
203 """
204 if me._fwatch.update():
205 me._update()
206
207 def _update(me):
208 """
209 Internal function to update the configuration from the underlying file.
210 """
211
212 if T._debug: print '# reread config'
213
214 ## Initial state.
215 testaddrs = {}
216 groups = {}
217 grpname = None
218 grplist = []
219
220 ## Open the file and start reading.
221 with open(me._file) as f:
222 lno = 0
223 for line in f:
224 lno += 1
225 for tag, rx in CONFSYNTAX:
226 m = rx.match(line)
227 if m: break
228 else:
229 raise ConfigError(me._file, lno, 'failed to parse line: %r' % line)
230
231 if tag == 'COMMENT':
232 ## A comment. Ignore it and hope it goes away.
233
234 continue
235
236 elif tag == 'GRPHDR':
237 ## A group header. Flush the old group and start a new one.
238 newname = m.group(1)
239
240 if grpname is not None: groups[grpname] = grplist
241 if newname in groups:
242 raise ConfigError(me._file, lno,
243 "duplicate group name `%s'" % newname)
244 grpname = newname
245 grplist = []
246
247 elif tag == 'ASSGN':
248 ## An assignment. Deal with it.
249 name, value = m.group(1), m.group(2)
250
251 if grpname is None:
252 ## We're outside of any group, so this is a global configuration
253 ## tweak.
254
255 if name == 'test-addr':
256 for astr in value.split():
257 try:
258 a = parse_address(astr)
259 except Exception, e:
260 raise ConfigError(me._file, lno,
261 "invalid IP address `%s': %s" %
262 (astr, e))
263 if a.AF in testaddrs:
264 raise ConfigError(me._file, lno,
265 'duplicate %s test-address' % a.AFNAME)
266 testaddrs[a.AF] = a
267 else:
268 raise ConfigError(me._file, lno,
269 "unknown global option `%s'" % name)
270
271 else:
272 ## Parse a pattern and add it to the group.
273 spec = value.split()
274 i = 0
275
276 ## Check for an explicit target address.
277 if i >= len(spec) or spec[i].find('/') >= 0:
278 peer = None
279 af = None
280 else:
281 try:
282 peer = parse_address(spec[i])
283 except Exception, e:
284 raise ConfigError(me._file, lno,
285 "invalid IP address `%s': %s" %
286 (spec[i], e))
287 af = peer.AF
288 i += 1
289
290 ## Parse the list of local networks.
291 nets = []
292 while i < len(spec):
293 try:
294 net = parse_net(spec[i])
295 except Exception, e:
296 raise ConfigError(me._file, lno,
297 "invalid IP network `%s': %s" %
298 (spec[i], e))
299 else:
300 nets.append(net)
301 i += 1
302 if not nets:
303 raise ConfigError(me._file, lno, 'no networks defined')
304
305 ## Make sure that the addresses are consistent.
306 for net in nets:
307 if af is None:
308 af = net.AF
309 elif net.AF != af:
310 raise ConfigError(me._file, lno,
311 "net %s doesn't match" % net)
312
313 ## Add this entry to the list.
314 grplist.append((name, peer, nets))
315
316 ## Fill in the default test addresses if necessary.
317 for a in TESTADDRS: testaddrs.setdefault(a.AF, a)
318
319 ## Done.
320 if grpname is not None: groups[grpname] = grplist
321 me.testaddrs = testaddrs
322 me.groups = groups
323
324 ### This will be a configuration file.
325 CF = None
326
327 def cmd_showconfig():
328 T.svcinfo('test-addr=%s' %
329 ' '.join(str(a)
330 for a in sorted(CF.testaddrs.itervalues(),
331 key = lambda a: a.AFNAME)))
332 def cmd_showgroups():
333 for g in sorted(CF.groups.iterkeys()):
334 T.svcinfo(g)
335 def cmd_showgroup(g):
336 try: pats = CF.groups[g]
337 except KeyError: raise T.TripeJobError('unknown-group', g)
338 for t, p, nn in pats:
339 T.svcinfo('peer', t,
340 'target', p and str(p) or '(default)',
341 'net', ' '.join(map(str, nn)))
342
343 ###--------------------------------------------------------------------------
344 ### Responding to a network up/down event.
345
346 def localaddr(peer):
347 """
348 Return the local IP address used for talking to PEER.
349 """
350 sk = S.socket(peer.AF, S.SOCK_DGRAM)
351 try:
352 try:
353 sk.connect(peer.sockaddr(1))
354 addr = sk.getsockname()
355 return type(peer).from_sockaddr(addr)[0]
356 except S.error:
357 return None
358 finally:
359 sk.close()
360
361 _kick = T.Queue()
362 _delay = None
363
364 def cancel_delay():
365 global _delay
366 if _delay is not None:
367 if T._debug: print '# cancel delayed kick'
368 G.source_remove(_delay)
369 _delay = None
370
371 def netupdown(upness, reason):
372 """
373 Add or kill peers according to whether the network is up or down.
374
375 UPNESS is true if the network is up, or false if it's down.
376 """
377
378 _kick.put((upness, reason))
379
380 def delay_netupdown(upness, reason):
381 global _delay
382 cancel_delay()
383 def _func():
384 global _delay
385 if T._debug: print '# delayed %s: %s' % (upness, reason)
386 _delay = None
387 netupdown(upness, reason)
388 return False
389 if T._debug: print '# delaying %s: %s' % (upness, reason)
390 _delay = G.timeout_add(2000, _func)
391
392 def kickpeers():
393 while True:
394 upness, reason = _kick.get()
395 if T._debug: print '# kickpeers %s: %s' % (upness, reason)
396 select = []
397 cancel_delay()
398
399 ## Make sure the configuration file is up-to-date. Don't worry if we
400 ## can't do anything useful.
401 try:
402 CF.check()
403 except Exception, exc:
404 SM.warn('conntrack', 'config-file-error',
405 exc.__class__.__name__, str(exc))
406
407 ## Find the current list of peers.
408 peers = SM.list()
409
410 ## Work out the primary IP addresses.
411 locals = {}
412 if upness:
413 for af, remote in CF.testaddrs.iteritems():
414 local = localaddr(remote)
415 if local is not None: locals[af] = local
416 if not locals: upness = False
417 if not T._debug: pass
418 elif not locals: print '# offline'
419 else:
420 for local in locals.itervalues():
421 print '# local %s address = %s' % (local.AFNAME, local)
422
423 ## Now decide what to do.
424 changes = []
425 for g, pp in CF.groups.iteritems():
426 if T._debug: print '# check group %s' % g
427
428 ## Find out which peer in the group ought to be active.
429 statemap = {}
430 want = None
431 matchp = False
432 for t, p, nn in pp:
433 af = nn[0].AF
434 if p is None or not upness: ip = locals.get(af)
435 else: ip = localaddr(p)
436 if T._debug:
437 info = 'peer = %s; target = %s; nets = %s; local = %s' % (
438 t, p or '(default)', ', '.join(map(str, nn)), straddr(ip))
439 if upness and not matchp and \
440 ip is not None and any(ip.withinp(n) for n in nn):
441 if T._debug: print '# %s: SELECTED' % info
442 statemap[t] = 'up'
443 select.append('%s=%s' % (g, t))
444 if t == 'down' or t.startswith('down/'): want = None
445 else: want = t
446 matchp = True
447 else:
448 statemap[t] = 'down'
449 if T._debug: print '# %s: skipped' % info
450
451 ## Shut down the wrong ones.
452 found = False
453 if T._debug: print '# peer-map = %r' % statemap
454 for p in peers:
455 what = statemap.get(p, 'leave')
456 if what == 'up':
457 found = True
458 if T._debug: print '# peer %s: already up' % p
459 elif what == 'down':
460 def _(p = p):
461 try:
462 SM.kill(p)
463 except T.TripeError, exc:
464 if exc.args[0] == 'unknown-peer':
465 ## Inherently racy; don't worry about this.
466 pass
467 else:
468 raise
469 if T._debug: print '# peer %s: bring down' % p
470 changes.append(_)
471
472 ## Start the right one if necessary.
473 if want is not None and not found:
474 def _(want = want):
475 try:
476 list(SM.svcsubmit('connect', 'active', want))
477 except T.TripeError, exc:
478 SM.warn('conntrack', 'connect-failed', want, *exc.args)
479 if T._debug: print '# peer %s: bring up' % want
480 changes.append(_)
481
482 ## Commit the changes.
483 if changes:
484 SM.notify('conntrack', upness and 'up' or 'down', *select + reason)
485 for c in changes: c()
486
487 ###--------------------------------------------------------------------------
488 ### NetworkManager monitor.
489
490 DBPROPS_IFACE = 'org.freedesktop.DBus.Properties'
491
492 NM_NAME = 'org.freedesktop.NetworkManager'
493 NM_PATH = '/org/freedesktop/NetworkManager'
494 NM_IFACE = NM_NAME
495 NMCA_IFACE = NM_NAME + '.Connection.Active'
496
497 NM_STATE_CONNECTED = 3 #obsolete
498 NM_STATE_CONNECTED_LOCAL = 50
499 NM_STATE_CONNECTED_SITE = 60
500 NM_STATE_CONNECTED_GLOBAL = 70
501 NM_CONNSTATES = set([NM_STATE_CONNECTED,
502 NM_STATE_CONNECTED_LOCAL,
503 NM_STATE_CONNECTED_SITE,
504 NM_STATE_CONNECTED_GLOBAL])
505
506 class NetworkManagerMonitor (object):
507 """
508 Watch NetworkManager signals for changes in network state.
509 """
510
511 ## Strategy. There are two kinds of interesting state transitions for us.
512 ## The first one is the global are-we-connected state, which we'll use to
513 ## toggle network upness on a global level. The second is which connection
514 ## has the default route, which we'll use to tweak which peer in the peer
515 ## group is active. The former is most easily tracked using the signal
516 ## org.freedesktop.NetworkManager.StateChanged; for the latter, we track
517 ## org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged and
518 ## look for when a new connection gains the default route.
519
520 def attach(me, bus):
521 try:
522 nm = bus.get_object(NM_NAME, NM_PATH)
523 state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE)
524 if state in NM_CONNSTATES:
525 netupdown(True, ['nm', 'initially-connected'])
526 else:
527 netupdown(False, ['nm', 'initially-disconnected'])
528 except D.DBusException, e:
529 if T._debug: print '# exception attaching to network-manager: %s' % e
530 bus.add_signal_receiver(me._nm_state, 'StateChanged',
531 NM_IFACE, NM_NAME, NM_PATH)
532 bus.add_signal_receiver(me._nm_connchange, 'PropertiesChanged',
533 NMCA_IFACE, NM_NAME, None)
534
535 def _nm_state(me, state):
536 if state in NM_CONNSTATES:
537 delay_netupdown(True, ['nm', 'connected'])
538 else:
539 delay_netupdown(False, ['nm', 'disconnected'])
540
541 def _nm_connchange(me, props):
542 if props.get('Default', False) or props.get('Default6', False):
543 delay_netupdown(True, ['nm', 'default-connection-change'])
544
545 ##--------------------------------------------------------------------------
546 ### Connman monitor.
547
548 CM_NAME = 'net.connman'
549 CM_PATH = '/'
550 CM_IFACE = 'net.connman.Manager'
551
552 class ConnManMonitor (object):
553 """
554 Watch ConnMan signls for changes in network state.
555 """
556
557 ## Strategy. Everything seems to be usefully encoded in the `State'
558 ## property. If it's `offline', `idle' or `ready' then we don't expect a
559 ## network connection. During handover from one network to another, the
560 ## property passes through `ready' to `online'.
561
562 def attach(me, bus):
563 try:
564 cm = bus.get_object(CM_NAME, CM_PATH)
565 props = cm.GetProperties(dbus_interface = CM_IFACE)
566 state = props['State']
567 netupdown(state == 'online', ['connman', 'initially-%s' % state])
568 except D.DBusException, e:
569 if T._debug: print '# exception attaching to connman: %s' % e
570 bus.add_signal_receiver(me._cm_state, 'PropertyChanged',
571 CM_IFACE, CM_NAME, CM_PATH)
572
573 def _cm_state(me, prop, value):
574 if prop != 'State': return
575 delay_netupdown(value == 'online', ['connman', value])
576
577 ###--------------------------------------------------------------------------
578 ### Maemo monitor.
579
580 ICD_NAME = 'com.nokia.icd'
581 ICD_PATH = '/com/nokia/icd'
582 ICD_IFACE = ICD_NAME
583
584 class MaemoICdMonitor (object):
585 """
586 Watch ICd signals for changes in network state.
587 """
588
589 ## Strategy. ICd only handles one connection at a time in steady state,
590 ## though when switching between connections, it tries to bring the new one
591 ## up before shutting down the old one. This makes life a bit easier than
592 ## it is with NetworkManager. On the other hand, the notifications are
593 ## relative to particular connections only, and the indicator that the old
594 ## connection is down (`IDLE') comes /after/ the new one comes up
595 ## (`CONNECTED'), so we have to remember which one is active.
596
597 def attach(me, bus):
598 try:
599 icd = bus.get_object(ICD_NAME, ICD_PATH)
600 try:
601 iap = icd.get_ipinfo(dbus_interface = ICD_IFACE)[0]
602 me._iap = iap
603 netupdown(True, ['icd', 'initially-connected', iap])
604 except D.DBusException:
605 me._iap = None
606 netupdown(False, ['icd', 'initially-disconnected'])
607 except D.DBusException, e:
608 if T._debug: print '# exception attaching to icd: %s' % e
609 me._iap = None
610 bus.add_signal_receiver(me._icd_state, 'status_changed', ICD_IFACE,
611 ICD_NAME, ICD_PATH)
612
613 def _icd_state(me, iap, ty, state, hunoz):
614 if state == 'CONNECTED':
615 me._iap = iap
616 delay_netupdown(True, ['icd', 'connected', iap])
617 elif state == 'IDLE' and iap == me._iap:
618 me._iap = None
619 delay_netupdown(False, ['icd', 'idle'])
620
621 ###--------------------------------------------------------------------------
622 ### D-Bus connection tracking.
623
624 class DBusMonitor (object):
625 """
626 Maintains a connection to the system D-Bus, and watches for signals.
627
628 If the connection is initially down, or drops for some reason, we retry
629 periodically (every five seconds at the moment). If the connection
630 resurfaces, we reattach the monitors.
631 """
632
633 def __init__(me):
634 """
635 Initialise the object and try to establish a connection to the bus.
636 """
637 me._mons = []
638 me._loop = D.mainloop.glib.DBusGMainLoop()
639 me._state = 'startup'
640 me._reconnect()
641
642 def addmon(me, mon):
643 """
644 Add a monitor object to watch for signals.
645
646 MON.attach(BUS) is called, with BUS being the connection to the system
647 bus. MON should query its service's current status and watch for
648 relevant signals.
649 """
650 me._mons.append(mon)
651 if me._bus is not None:
652 mon.attach(me._bus)
653
654 def _reconnect(me, hunoz = None):
655 """
656 Start connecting to the bus.
657
658 If we fail the first time, retry periodically.
659 """
660 if me._state == 'startup':
661 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'startup')
662 elif me._state == 'connected':
663 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'lost')
664 else:
665 T.aside(SM.notify, 'conntrack', 'dbus-connection',
666 'state=%s' % me._state)
667 me._state == 'reconnecting'
668 me._bus = None
669 if me._try_connect():
670 G.timeout_add_seconds(5, me._try_connect)
671
672 def _try_connect(me):
673 """
674 Actually make a connection attempt.
675
676 If we succeed, attach the monitors.
677 """
678 try:
679 addr = OS.getenv('TRIPE_CONNTRACK_BUS')
680 if addr == 'SESSION':
681 bus = D.SessionBus(mainloop = me._loop, private = True)
682 elif addr is not None:
683 bus = D.bus.BusConnection(addr, mainloop = me._loop)
684 else:
685 bus = D.SystemBus(mainloop = me._loop, private = True)
686 for m in me._mons:
687 m.attach(bus)
688 except D.DBusException, e:
689 return True
690 me._bus = bus
691 me._state = 'connected'
692 bus.call_on_disconnection(me._reconnect)
693 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'connected')
694 return False
695
696 ###--------------------------------------------------------------------------
697 ### TrIPE service.
698
699 class GIOWatcher (object):
700 """
701 Monitor I/O events using glib.
702 """
703 def __init__(me, conn, mc = G.main_context_default()):
704 me._conn = conn
705 me._watch = None
706 me._mc = mc
707 def connected(me, sock):
708 me._watch = G.io_add_watch(sock, G.IO_IN,
709 lambda *hunoz: me._conn.receive())
710 def disconnected(me):
711 G.source_remove(me._watch)
712 me._watch = None
713 def iterate(me):
714 me._mc.iteration(True)
715
716 SM.iowatch = GIOWatcher(SM)
717
718 def init():
719 """
720 Service initialization.
721
722 Add the D-Bus monitor here, because we might send commands off immediately,
723 and we want to make sure the server connection is up.
724 """
725 global DBM
726 T.Coroutine(kickpeers, name = 'kickpeers').switch()
727 DBM = DBusMonitor()
728 DBM.addmon(NetworkManagerMonitor())
729 DBM.addmon(ConnManMonitor())
730 DBM.addmon(MaemoICdMonitor())
731 G.timeout_add_seconds(30, lambda: (_delay is not None or
732 netupdown(True, ['interval-timer']) or
733 True))
734
735 def parse_options():
736 """
737 Parse the command-line options.
738
739 Automatically changes directory to the requested configdir, and turns on
740 debugging. Returns the options object.
741 """
742 op = OptionParser(usage = '%prog [-a FILE] [-d DIR]',
743 version = '%%prog %s' % VERSION)
744
745 op.add_option('-a', '--admin-socket',
746 metavar = 'FILE', dest = 'tripesock', default = T.tripesock,
747 help = 'Select socket to connect to [default %default]')
748 op.add_option('-d', '--directory',
749 metavar = 'DIR', dest = 'dir', default = T.configdir,
750 help = 'Select current diretory [default %default]')
751 op.add_option('-c', '--config',
752 metavar = 'FILE', dest = 'conf', default = 'conntrack.conf',
753 help = 'Select configuration [default %default]')
754 op.add_option('--daemon', dest = 'daemon',
755 default = False, action = 'store_true',
756 help = 'Become a daemon after successful initialization')
757 op.add_option('--debug', dest = 'debug',
758 default = False, action = 'store_true',
759 help = 'Emit debugging trace information')
760 op.add_option('--startup', dest = 'startup',
761 default = False, action = 'store_true',
762 help = 'Being called as part of the server startup')
763
764 opts, args = op.parse_args()
765 if args: op.error('no arguments permitted')
766 OS.chdir(opts.dir)
767 T._debug = opts.debug
768 return opts
769
770 ## Service table, for running manually.
771 def cmd_updown(upness):
772 return lambda *args: T.defer(netupdown, upness, ['manual'] + list(args))
773 service_info = [('conntrack', VERSION, {
774 'up': (0, None, '', cmd_updown(True)),
775 'down': (0, None, '', cmd_updown(False)),
776 'show-config': (0, 0, '', cmd_showconfig),
777 'show-groups': (0, 0, '', cmd_showgroups),
778 'show-group': (1, 1, 'GROUP', cmd_showgroup)
779 })]
780
781 if __name__ == '__main__':
782 opts = parse_options()
783 CF = Config(opts.conf)
784 T.runservices(opts.tripesock, service_info,
785 init = init, daemon = opts.daemon)
786
787 ###----- That's all, folks --------------------------------------------------