chiark / gitweb /
svc/conntrack.in: Make an `InetAddress' class to do address wrangling.
[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.
210 groups = []
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
MW
236 pats))
237 groups.append((sec, pats))
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():
249 for sec, pats in CF.groups:
250 T.svcinfo(sec)
251def cmd_showgroup(g):
252 for s, p in CF.groups:
253 if s == g:
254 pats = p
255 break
256 else:
171206b5 257 raise T.TripeJobError('unknown-group', g)
6aa21132 258 for t, p, n in pats:
2d4998c4 259 T.svcinfo('peer', t,
6aa21132
MW
260 'target', p and str(p) or '(default)',
261 'net', str(n))
2d4998c4 262
2ec90437
MW
263###--------------------------------------------------------------------------
264### Responding to a network up/down event.
265
266def localaddr(peer):
267 """
268 Return the local IP address used for talking to PEER.
269 """
270 sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
271 try:
272 try:
6aa21132
MW
273 sk.connect(peer.sockaddr(1))
274 addr = sk.getsockname()
275 return InetAddress.from_sockaddr(addr)[0]
2ec90437
MW
276 except S.error:
277 return None
278 finally:
279 sk.close()
280
281_kick = T.Queue()
f5393555
MW
282_delay = None
283
284def cancel_delay():
285 global _delay
286 if _delay is not None:
287 if T._debug: print '# cancel delayed kick'
288 G.source_remove(_delay)
289 _delay = None
4f6b41b9
MW
290
291def netupdown(upness, reason):
292 """
293 Add or kill peers according to whether the network is up or down.
294
295 UPNESS is true if the network is up, or false if it's down.
296 """
297
298 _kick.put((upness, reason))
299
f5393555
MW
300def delay_netupdown(upness, reason):
301 global _delay
302 cancel_delay()
303 def _func():
304 global _delay
305 if T._debug: print '# delayed %s: %s' % (upness, reason)
306 _delay = None
307 netupdown(upness, reason)
308 return False
309 if T._debug: print '# delaying %s: %s' % (upness, reason)
310 _delay = G.timeout_add(2000, _func)
311
2ec90437
MW
312def kickpeers():
313 while True:
314 upness, reason = _kick.get()
2d4998c4
MW
315 if T._debug: print '# kickpeers %s: %s' % (upness, reason)
316 select = []
f5393555 317 cancel_delay()
2ec90437
MW
318
319 ## Make sure the configuration file is up-to-date. Don't worry if we
320 ## can't do anything useful.
321 try:
322 CF.check()
323 except Exception, exc:
324 SM.warn('conntrack', 'config-file-error',
325 exc.__class__.__name__, str(exc))
326
327 ## Find the current list of peers.
328 peers = SM.list()
329
330 ## Work out the primary IP address.
331 if upness:
332 addr = localaddr(CF.testaddr)
333 if addr is None:
334 upness = False
b10a8c3d
MW
335 else:
336 addr = None
2d4998c4
MW
337 if not T._debug: pass
338 elif addr: print '# local address = %s' % straddr(addr)
339 else: print '# offline'
2ec90437
MW
340
341 ## Now decide what to do.
342 changes = []
343 for g, pp in CF.groups:
2d4998c4 344 if T._debug: print '# check group %s' % g
2ec90437
MW
345
346 ## Find out which peer in the group ought to be active.
b10a8c3d
MW
347 ip = None
348 map = {}
349 want = None
6aa21132 350 for t, p, n in pp:
b10a8c3d
MW
351 if p is None or not upness:
352 ipq = addr
353 else:
354 ipq = localaddr(p)
2d4998c4 355 if T._debug:
6aa21132
MW
356 info = 'peer=%s; target=%s; net=%s; local=%s' % (
357 t, p or '(default)', n, straddr(ipq))
b10a8c3d 358 if upness and ip is None and \
6aa21132 359 ipq is not None and ipq.withinp(n):
2d4998c4 360 if T._debug: print '# %s: SELECTED' % info
b10a8c3d 361 map[t] = 'up'
2d4998c4 362 select.append('%s=%s' % (g, t))
f2bdb96e
MW
363 if t == 'down' or t.startswith('down/'):
364 want = None
365 else:
366 want = t
b10a8c3d
MW
367 ip = ipq
368 else:
369 map[t] = 'down'
2d4998c4 370 if T._debug: print '# %s: skipped' % info
2ec90437
MW
371
372 ## Shut down the wrong ones.
373 found = False
2d4998c4 374 if T._debug: print '# peer-map = %r' % map
2ec90437 375 for p in peers:
b10a8c3d
MW
376 what = map.get(p, 'leave')
377 if what == 'up':
2ec90437 378 found = True
2d4998c4 379 if T._debug: print '# peer %s: already up' % p
b10a8c3d 380 elif what == 'down':
cf2e4ea6
MW
381 def _(p = p):
382 try:
383 SM.kill(p)
384 except T.TripeError, exc:
385 if exc.args[0] == 'unknown-peer':
386 ## Inherently racy; don't worry about this.
387 pass
388 else:
389 raise
2d4998c4 390 if T._debug: print '# peer %s: bring down' % p
cf2e4ea6 391 changes.append(_)
2ec90437
MW
392
393 ## Start the right one if necessary.
7b7e3c74 394 if want is not None and not found:
cf2e4ea6
MW
395 def _(want = want):
396 try:
8d1d183e 397 list(SM.svcsubmit('connect', 'active', want))
cf2e4ea6
MW
398 except T.TripeError, exc:
399 SM.warn('conntrack', 'connect-failed', want, *exc.args)
2d4998c4 400 if T._debug: print '# peer %s: bring up' % want
cf2e4ea6 401 changes.append(_)
2ec90437
MW
402
403 ## Commit the changes.
404 if changes:
2d4998c4 405 SM.notify('conntrack', upness and 'up' or 'down', *select + reason)
2ec90437
MW
406 for c in changes: c()
407
2ec90437
MW
408###--------------------------------------------------------------------------
409### NetworkManager monitor.
410
498d9f42
MW
411DBPROPS_IFACE = 'org.freedesktop.DBus.Properties'
412
2ec90437
MW
413NM_NAME = 'org.freedesktop.NetworkManager'
414NM_PATH = '/org/freedesktop/NetworkManager'
415NM_IFACE = NM_NAME
416NMCA_IFACE = NM_NAME + '.Connection.Active'
417
2079efa1
MW
418NM_STATE_CONNECTED = 3 #obsolete
419NM_STATE_CONNECTED_LOCAL = 50
420NM_STATE_CONNECTED_SITE = 60
421NM_STATE_CONNECTED_GLOBAL = 70
422NM_CONNSTATES = set([NM_STATE_CONNECTED,
423 NM_STATE_CONNECTED_LOCAL,
424 NM_STATE_CONNECTED_SITE,
425 NM_STATE_CONNECTED_GLOBAL])
2ec90437
MW
426
427class NetworkManagerMonitor (object):
428 """
429 Watch NetworkManager signals for changes in network state.
430 """
431
432 ## Strategy. There are two kinds of interesting state transitions for us.
433 ## The first one is the global are-we-connected state, which we'll use to
434 ## toggle network upness on a global level. The second is which connection
435 ## has the default route, which we'll use to tweak which peer in the peer
436 ## group is active. The former is most easily tracked using the signal
437 ## org.freedesktop.NetworkManager.StateChanged; for the latter, we track
438 ## org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged and
439 ## look for when a new connection gains the default route.
440
441 def attach(me, bus):
442 try:
443 nm = bus.get_object(NM_NAME, NM_PATH)
498d9f42 444 state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE)
2079efa1 445 if state in NM_CONNSTATES:
2ec90437
MW
446 netupdown(True, ['nm', 'initially-connected'])
447 else:
448 netupdown(False, ['nm', 'initially-disconnected'])
bd9bd714
MW
449 except D.DBusException, e:
450 if T._debug: print '# exception attaching to network-manager: %s' % e
2079efa1
MW
451 bus.add_signal_receiver(me._nm_state, 'StateChanged',
452 NM_IFACE, NM_NAME, NM_PATH)
453 bus.add_signal_receiver(me._nm_connchange, 'PropertiesChanged',
454 NMCA_IFACE, NM_NAME, None)
2ec90437
MW
455
456 def _nm_state(me, state):
2079efa1 457 if state in NM_CONNSTATES:
f5393555 458 delay_netupdown(True, ['nm', 'connected'])
2ec90437 459 else:
f5393555 460 delay_netupdown(False, ['nm', 'disconnected'])
2ec90437
MW
461
462 def _nm_connchange(me, props):
f5393555
MW
463 if props.get('Default', False) or props.get('Default6', False):
464 delay_netupdown(True, ['nm', 'default-connection-change'])
2ec90437 465
a95eb44a
MW
466##--------------------------------------------------------------------------
467### Connman monitor.
468
469CM_NAME = 'net.connman'
470CM_PATH = '/'
471CM_IFACE = 'net.connman.Manager'
472
473class ConnManMonitor (object):
474 """
475 Watch ConnMan signls for changes in network state.
476 """
477
478 ## Strategy. Everything seems to be usefully encoded in the `State'
479 ## property. If it's `offline', `idle' or `ready' then we don't expect a
480 ## network connection. During handover from one network to another, the
481 ## property passes through `ready' to `online'.
482
483 def attach(me, bus):
484 try:
485 cm = bus.get_object(CM_NAME, CM_PATH)
486 props = cm.GetProperties(dbus_interface = CM_IFACE)
487 state = props['State']
488 netupdown(state == 'online', ['connman', 'initially-%s' % state])
bd9bd714
MW
489 except D.DBusException, e:
490 if T._debug: print '# exception attaching to connman: %s' % e
a95eb44a
MW
491 bus.add_signal_receiver(me._cm_state, 'PropertyChanged',
492 CM_IFACE, CM_NAME, CM_PATH)
493
494 def _cm_state(me, prop, value):
495 if prop != 'State': return
f5393555 496 delay_netupdown(value == 'online', ['connman', value])
a95eb44a 497
2ec90437
MW
498###--------------------------------------------------------------------------
499### Maemo monitor.
500
501ICD_NAME = 'com.nokia.icd'
502ICD_PATH = '/com/nokia/icd'
503ICD_IFACE = ICD_NAME
504
505class MaemoICdMonitor (object):
506 """
507 Watch ICd signals for changes in network state.
508 """
509
510 ## Strategy. ICd only handles one connection at a time in steady state,
511 ## though when switching between connections, it tries to bring the new one
512 ## up before shutting down the old one. This makes life a bit easier than
513 ## it is with NetworkManager. On the other hand, the notifications are
514 ## relative to particular connections only, and the indicator that the old
515 ## connection is down (`IDLE') comes /after/ the new one comes up
516 ## (`CONNECTED'), so we have to remember which one is active.
517
518 def attach(me, bus):
519 try:
520 icd = bus.get_object(ICD_NAME, ICD_PATH)
521 try:
522 iap = icd.get_ipinfo(dbus_interface = ICD_IFACE)[0]
523 me._iap = iap
524 netupdown(True, ['icd', 'initially-connected', iap])
525 except D.DBusException:
526 me._iap = None
527 netupdown(False, ['icd', 'initially-disconnected'])
bd9bd714
MW
528 except D.DBusException, e:
529 if T._debug: print '# exception attaching to icd: %s' % e
2ec90437
MW
530 me._iap = None
531 bus.add_signal_receiver(me._icd_state, 'status_changed', ICD_IFACE,
532 ICD_NAME, ICD_PATH)
533
534 def _icd_state(me, iap, ty, state, hunoz):
535 if state == 'CONNECTED':
536 me._iap = iap
f5393555 537 delay_netupdown(True, ['icd', 'connected', iap])
2ec90437
MW
538 elif state == 'IDLE' and iap == me._iap:
539 me._iap = None
f5393555 540 delay_netupdown(False, ['icd', 'idle'])
2ec90437
MW
541
542###--------------------------------------------------------------------------
543### D-Bus connection tracking.
544
545class DBusMonitor (object):
546 """
547 Maintains a connection to the system D-Bus, and watches for signals.
548
549 If the connection is initially down, or drops for some reason, we retry
550 periodically (every five seconds at the moment). If the connection
551 resurfaces, we reattach the monitors.
552 """
553
554 def __init__(me):
555 """
556 Initialise the object and try to establish a connection to the bus.
557 """
558 me._mons = []
559 me._loop = D.mainloop.glib.DBusGMainLoop()
7bfa1e06 560 me._state = 'startup'
2ec90437
MW
561 me._reconnect()
562
563 def addmon(me, mon):
564 """
565 Add a monitor object to watch for signals.
566
567 MON.attach(BUS) is called, with BUS being the connection to the system
568 bus. MON should query its service's current status and watch for
569 relevant signals.
570 """
571 me._mons.append(mon)
572 if me._bus is not None:
573 mon.attach(me._bus)
574
16650038 575 def _reconnect(me, hunoz = None):
2ec90437
MW
576 """
577 Start connecting to the bus.
578
579 If we fail the first time, retry periodically.
580 """
7bfa1e06
MW
581 if me._state == 'startup':
582 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'startup')
583 elif me._state == 'connected':
584 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'lost')
585 else:
586 T.aside(SM.notify, 'conntrack', 'dbus-connection',
587 'state=%s' % me._state)
588 me._state == 'reconnecting'
2ec90437
MW
589 me._bus = None
590 if me._try_connect():
591 G.timeout_add_seconds(5, me._try_connect)
592
593 def _try_connect(me):
594 """
595 Actually make a connection attempt.
596
597 If we succeed, attach the monitors.
598 """
599 try:
7bfa1e06
MW
600 addr = OS.getenv('TRIPE_CONNTRACK_BUS')
601 if addr == 'SESSION':
602 bus = D.SessionBus(mainloop = me._loop, private = True)
603 elif addr is not None:
604 bus = D.bus.BusConnection(addr, mainloop = me._loop)
605 else:
606 bus = D.SystemBus(mainloop = me._loop, private = True)
607 for m in me._mons:
608 m.attach(bus)
609 except D.DBusException, e:
2ec90437
MW
610 return True
611 me._bus = bus
7bfa1e06 612 me._state = 'connected'
2ec90437 613 bus.call_on_disconnection(me._reconnect)
7bfa1e06 614 T.aside(SM.notify, 'conntrack', 'dbus-connection', 'connected')
2ec90437
MW
615 return False
616
617###--------------------------------------------------------------------------
618### TrIPE service.
619
620class GIOWatcher (object):
621 """
622 Monitor I/O events using glib.
623 """
624 def __init__(me, conn, mc = G.main_context_default()):
625 me._conn = conn
626 me._watch = None
627 me._mc = mc
628 def connected(me, sock):
629 me._watch = G.io_add_watch(sock, G.IO_IN,
630 lambda *hunoz: me._conn.receive())
631 def disconnected(me):
632 G.source_remove(me._watch)
633 me._watch = None
634 def iterate(me):
635 me._mc.iteration(True)
636
637SM.iowatch = GIOWatcher(SM)
638
639def init():
640 """
641 Service initialization.
642
643 Add the D-Bus monitor here, because we might send commands off immediately,
644 and we want to make sure the server connection is up.
645 """
29807d89 646 global DBM
22b47552 647 T.Coroutine(kickpeers, name = 'kickpeers').switch()
29807d89
MW
648 DBM = DBusMonitor()
649 DBM.addmon(NetworkManagerMonitor())
a95eb44a 650 DBM.addmon(ConnManMonitor())
29807d89 651 DBM.addmon(MaemoICdMonitor())
f5393555
MW
652 G.timeout_add_seconds(30, lambda: (_delay is not None or
653 netupdown(True, ['interval-timer']) or
654 True))
2ec90437
MW
655
656def parse_options():
657 """
658 Parse the command-line options.
659
660 Automatically changes directory to the requested configdir, and turns on
661 debugging. Returns the options object.
662 """
663 op = OptionParser(usage = '%prog [-a FILE] [-d DIR]',
664 version = '%%prog %s' % VERSION)
665
666 op.add_option('-a', '--admin-socket',
667 metavar = 'FILE', dest = 'tripesock', default = T.tripesock,
668 help = 'Select socket to connect to [default %default]')
669 op.add_option('-d', '--directory',
670 metavar = 'DIR', dest = 'dir', default = T.configdir,
671 help = 'Select current diretory [default %default]')
672 op.add_option('-c', '--config',
673 metavar = 'FILE', dest = 'conf', default = 'conntrack.conf',
674 help = 'Select configuration [default %default]')
675 op.add_option('--daemon', dest = 'daemon',
676 default = False, action = 'store_true',
677 help = 'Become a daemon after successful initialization')
678 op.add_option('--debug', dest = 'debug',
679 default = False, action = 'store_true',
680 help = 'Emit debugging trace information')
681 op.add_option('--startup', dest = 'startup',
682 default = False, action = 'store_true',
683 help = 'Being called as part of the server startup')
684
685 opts, args = op.parse_args()
686 if args: op.error('no arguments permitted')
687 OS.chdir(opts.dir)
688 T._debug = opts.debug
689 return opts
690
691## Service table, for running manually.
692def cmd_updown(upness):
693 return lambda *args: T.defer(netupdown, upness, ['manual'] + list(args))
694service_info = [('conntrack', VERSION, {
695 'up': (0, None, '', cmd_updown(True)),
2d4998c4
MW
696 'down': (0, None, '', cmd_updown(False)),
697 'show-config': (0, 0, '', cmd_showconfig),
698 'show-groups': (0, 0, '', cmd_showgroups),
699 'show-group': (1, 1, 'GROUP', cmd_showgroup)
2ec90437
MW
700})]
701
702if __name__ == '__main__':
703 opts = parse_options()
704 CF = Config(opts.conf)
705 T.runservices(opts.tripesock, service_info,
706 init = init, daemon = opts.daemon)
707
708###----- That's all, folks --------------------------------------------------