X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/tripe/blobdiff_plain/cf2e4ea6c40973c25002e84374c194bb5e4c4973..11ab0da64ef849cf3d035ecaa08dd581144f1ad7:/svc/conntrack.in diff --git a/svc/conntrack.in b/svc/conntrack.in index 827598fe..061d6d76 100644 --- a/svc/conntrack.in +++ b/svc/conntrack.in @@ -10,19 +10,18 @@ ### ### This file is part of Trivial IP Encryption (TrIPE). ### -### TrIPE is free software; you can redistribute it and/or modify -### it under the terms of the GNU General Public License as published by -### the Free Software Foundation; either version 2 of the License, or -### (at your option) any later version. +### TrIPE is free software: you can redistribute it and/or modify it under +### the terms of the GNU General Public License as published by the Free +### Software Foundation; either version 3 of the License, or (at your +### option) any later version. ### -### TrIPE is distributed in the hope that it will be useful, -### but WITHOUT ANY WARRANTY; without even the implied warranty of -### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -### GNU General Public License for more details. +### TrIPE is distributed in the hope that it will be useful, but WITHOUT +### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +### FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +### for more details. ### ### You should have received a copy of the GNU General Public License -### along with TrIPE; if not, write to the Free Software Foundation, -### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +### along with TrIPE. If not, see . VERSION = '@VERSION@' @@ -39,7 +38,8 @@ import tripe as T import dbus as D for i in ['mainloop', 'mainloop.glib']: __import__('dbus.%s' % i) -import gobject as G +try: from gi.repository import GLib as G +except ImportError: import gobject as G from struct import pack, unpack SM = T.svcmgr @@ -94,6 +94,9 @@ def toposort(cmp, things): if done: break +def parse_address(addrstr): + return unpack('>L', S.inet_aton(addrstr))[0] + ###-------------------------------------------------------------------------- ### Parse the configuration file. @@ -140,6 +143,7 @@ class Config (object): ## so turn them all off. cp = RawConfigParser() cp.read(me._file) + if T._debug: print '# reread config' ## Save the test address. Make sure it's vaguely sensible. The default ## is probably good for most cases, in fact, since that address isn't @@ -169,12 +173,12 @@ class Config (object): ## and MASK is either a dotted-quad or a single integer N indicating ## a mask with N leading ones followed by trailing zeroes. slash = net.index('/') - addr, = unpack('>L', S.inet_aton(net[:slash])) - if net.find('.', slash + 1) >= 0: - mask, = unpack('>L', S.inet_aton(net[:slash])) - else: + addr = parse_address(net[:slash]) + if net[slash + 1:].isdigit(): n = int(net[slash + 1:], 10) mask = (1 << 32) - (1 << 32 - n) + else: + mask = parse_address(net[slash + 1:]) pats.append((tag, peer, addr & mask, mask)) ## Annoyingly, RawConfigParser doesn't preserve the order of options. @@ -193,6 +197,29 @@ class Config (object): ### This will be a configuration file. CF = None +def straddr(a): return a is None and '#' or S.inet_ntoa(pack('>L', a)) +def strmask(m): + for i in xrange(33): + if m == 0xffffffff ^ ((1 << (32 - i)) - 1): return str(i) + return straddr(m) + +def cmd_showconfig(): + T.svcinfo('test-addr=%s' % CF.testaddr) +def cmd_showgroups(): + for sec, pats in CF.groups: + T.svcinfo(sec) +def cmd_showgroup(g): + for s, p in CF.groups: + if s == g: + pats = p + break + else: + raise T.TripeJobError('unknown-group', g) + for t, p, a, m in pats: + T.svcinfo('peer', t, + 'target', p or '(default)', + 'net', '%s/%s' % (straddr(a), strmask(m))) + ###-------------------------------------------------------------------------- ### Responding to a network up/down event. @@ -205,7 +232,7 @@ def localaddr(peer): try: sk.connect((peer, 1)) addr, _ = sk.getsockname() - addr, = unpack('>L', S.inet_aton(addr)) + addr = parse_address(addr) return addr except S.error: return None @@ -213,10 +240,42 @@ def localaddr(peer): sk.close() _kick = T.Queue() +_delay = None + +def cancel_delay(): + global _delay + if _delay is not None: + if T._debug: print '# cancel delayed kick' + G.source_remove(_delay) + _delay = None + +def netupdown(upness, reason): + """ + Add or kill peers according to whether the network is up or down. + + UPNESS is true if the network is up, or false if it's down. + """ + + _kick.put((upness, reason)) + +def delay_netupdown(upness, reason): + global _delay + cancel_delay() + def _func(): + global _delay + if T._debug: print '# delayed %s: %s' % (upness, reason) + _delay = None + netupdown(upness, reason) + return False + if T._debug: print '# delaying %s: %s' % (upness, reason) + _delay = G.timeout_add(2000, _func) + def kickpeers(): - lastip = {} while True: upness, reason = _kick.get() + if T._debug: print '# kickpeers %s: %s' % (upness, reason) + select = [] + cancel_delay() ## Make sure the configuration file is up-to-date. Don't worry if we ## can't do anything useful. @@ -236,10 +295,14 @@ def kickpeers(): upness = False else: addr = None + if not T._debug: pass + elif addr: print '# local address = %s' % straddr(addr) + else: print '# offline' ## Now decide what to do. changes = [] for g, pp in CF.groups: + if T._debug: print '# check group %s' % g ## Find out which peer in the group ought to be active. ip = None @@ -250,20 +313,31 @@ def kickpeers(): ipq = addr else: ipq = localaddr(p) + if T._debug: + info = 'peer=%s; target=%s; net=%s/%s; local=%s' % ( + t, p or '(default)', straddr(a), strmask(m), straddr(ipq)) if upness and ip is None and \ ipq is not None and (ipq & m) == a: + if T._debug: print '# %s: SELECTED' % info map[t] = 'up' - want = t + select.append('%s=%s' % (g, t)) + if t == 'down' or t.startswith('down/'): + want = None + else: + want = t ip = ipq else: map[t] = 'down' + if T._debug: print '# %s: skipped' % info ## Shut down the wrong ones. found = False + if T._debug: print '# peer-map = %r' % map for p in peers: what = map.get(p, 'leave') if what == 'up': found = True + if T._debug: print '# peer %s: already up' % p elif what == 'down': def _(p = p): try: @@ -274,41 +348,42 @@ def kickpeers(): pass else: raise + if T._debug: print '# peer %s: bring down' % p changes.append(_) ## Start the right one if necessary. - if want is not None and (not found or ip != lastip.get(g, None)): + if want is not None and not found: def _(want = want): try: - SM.svcsubmit('connect', 'active', want) + list(SM.svcsubmit('connect', 'active', want)) except T.TripeError, exc: SM.warn('conntrack', 'connect-failed', want, *exc.args) + if T._debug: print '# peer %s: bring up' % want changes.append(_) - lastip[g] = ip ## Commit the changes. if changes: - SM.notify('conntrack', upness and 'up' or 'down', *reason) + SM.notify('conntrack', upness and 'up' or 'down', *select + reason) for c in changes: c() -def netupdown(upness, reason): - """ - Add or kill peers according to whether the network is up or down. - - UPNESS is true if the network is up, or false if it's down. - """ - - _kick.put((upness, reason)) - ###-------------------------------------------------------------------------- ### NetworkManager monitor. +DBPROPS_IFACE = 'org.freedesktop.DBus.Properties' + NM_NAME = 'org.freedesktop.NetworkManager' NM_PATH = '/org/freedesktop/NetworkManager' NM_IFACE = NM_NAME NMCA_IFACE = NM_NAME + '.Connection.Active' -NM_STATE_CONNECTED = 3 +NM_STATE_CONNECTED = 3 #obsolete +NM_STATE_CONNECTED_LOCAL = 50 +NM_STATE_CONNECTED_SITE = 60 +NM_STATE_CONNECTED_GLOBAL = 70 +NM_CONNSTATES = set([NM_STATE_CONNECTED, + NM_STATE_CONNECTED_LOCAL, + NM_STATE_CONNECTED_SITE, + NM_STATE_CONNECTED_GLOBAL]) class NetworkManagerMonitor (object): """ @@ -327,28 +402,59 @@ class NetworkManagerMonitor (object): def attach(me, bus): try: nm = bus.get_object(NM_NAME, NM_PATH) - state = nm.Get(NM_IFACE, 'State') - if state == NM_STATE_CONNECTED: + state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE) + if state in NM_CONNSTATES: netupdown(True, ['nm', 'initially-connected']) else: netupdown(False, ['nm', 'initially-disconnected']) - except D.DBusException: - pass - bus.add_signal_receiver(me._nm_state, 'StateChanged', NM_IFACE, - NM_NAME, NM_PATH) - bus.add_signal_receiver(me._nm_connchange, - 'PropertiesChanged', NMCA_IFACE, - NM_NAME, None) + except D.DBusException, e: + if T._debug: print '# exception attaching to network-manager: %s' % e + bus.add_signal_receiver(me._nm_state, 'StateChanged', + NM_IFACE, NM_NAME, NM_PATH) + bus.add_signal_receiver(me._nm_connchange, 'PropertiesChanged', + NMCA_IFACE, NM_NAME, None) def _nm_state(me, state): - if state == NM_STATE_CONNECTED: - netupdown(True, ['nm', 'connected']) + if state in NM_CONNSTATES: + delay_netupdown(True, ['nm', 'connected']) else: - netupdown(False, ['nm', 'disconnected']) + delay_netupdown(False, ['nm', 'disconnected']) def _nm_connchange(me, props): - if props.get('Default', False): - netupdown(True, ['nm', 'default-connection-change']) + if props.get('Default', False) or props.get('Default6', False): + delay_netupdown(True, ['nm', 'default-connection-change']) + +##-------------------------------------------------------------------------- +### Connman monitor. + +CM_NAME = 'net.connman' +CM_PATH = '/' +CM_IFACE = 'net.connman.Manager' + +class ConnManMonitor (object): + """ + Watch ConnMan signls for changes in network state. + """ + + ## Strategy. Everything seems to be usefully encoded in the `State' + ## property. If it's `offline', `idle' or `ready' then we don't expect a + ## network connection. During handover from one network to another, the + ## property passes through `ready' to `online'. + + def attach(me, bus): + try: + cm = bus.get_object(CM_NAME, CM_PATH) + props = cm.GetProperties(dbus_interface = CM_IFACE) + state = props['State'] + netupdown(state == 'online', ['connman', 'initially-%s' % state]) + except D.DBusException, e: + if T._debug: print '# exception attaching to connman: %s' % e + bus.add_signal_receiver(me._cm_state, 'PropertyChanged', + CM_IFACE, CM_NAME, CM_PATH) + + def _cm_state(me, prop, value): + if prop != 'State': return + delay_netupdown(value == 'online', ['connman', value]) ###-------------------------------------------------------------------------- ### Maemo monitor. @@ -380,7 +486,8 @@ class MaemoICdMonitor (object): except D.DBusException: me._iap = None netupdown(False, ['icd', 'initially-disconnected']) - except D.DBusException: + except D.DBusException, e: + if T._debug: print '# exception attaching to icd: %s' % e me._iap = None bus.add_signal_receiver(me._icd_state, 'status_changed', ICD_IFACE, ICD_NAME, ICD_PATH) @@ -388,10 +495,10 @@ class MaemoICdMonitor (object): def _icd_state(me, iap, ty, state, hunoz): if state == 'CONNECTED': me._iap = iap - netupdown(True, ['icd', 'connected', iap]) + delay_netupdown(True, ['icd', 'connected', iap]) elif state == 'IDLE' and iap == me._iap: me._iap = None - netupdown(False, ['icd', 'idle']) + delay_netupdown(False, ['icd', 'idle']) ###-------------------------------------------------------------------------- ### D-Bus connection tracking. @@ -501,9 +608,11 @@ def init(): T.Coroutine(kickpeers, name = 'kickpeers').switch() DBM = DBusMonitor() DBM.addmon(NetworkManagerMonitor()) + DBM.addmon(ConnManMonitor()) DBM.addmon(MaemoICdMonitor()) - G.timeout_add_seconds(30, lambda: (netupdown(True, ['interval-timer']) - or True)) + G.timeout_add_seconds(30, lambda: (_delay is not None or + netupdown(True, ['interval-timer']) or + True)) def parse_options(): """ @@ -545,7 +654,10 @@ def cmd_updown(upness): return lambda *args: T.defer(netupdown, upness, ['manual'] + list(args)) service_info = [('conntrack', VERSION, { 'up': (0, None, '', cmd_updown(True)), - 'down': (0, None, '', cmd_updown(False)) + 'down': (0, None, '', cmd_updown(False)), + 'show-config': (0, 0, '', cmd_showconfig), + 'show-groups': (0, 0, '', cmd_showgroups), + 'show-group': (1, 1, 'GROUP', cmd_showgroup) })] if __name__ == '__main__':