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