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