###
### 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 <https://www.gnu.org/licenses/>.
VERSION = '@VERSION@'
import mLib as M
import tripe as T
import dbus as D
+import re as RX
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
+from cStringIO import StringIO
SM = T.svcmgr
##__import__('rmcr').__debug = True
def __init__(me, **kw):
me.__dict__.update(kw)
-def toposort(cmp, things):
- """
- Generate the THINGS in an order consistent with a given partial order.
-
- The function CMP(X, Y) should return true if X must precede Y, and false if
- it doesn't care. If X and Y are equal then it should return false.
+def loadb(s):
+ n = 0
+ for ch in s: n = 256*n + ord(ch)
+ return n
- The THINGS may be any finite iterable; it is converted to a list
- internally.
- """
+def storeb(n, wd = None):
+ if wd is None: wd = n.bit_length()
+ s = StringIO()
+ for i in xrange((wd - 1)&-8, -8, -8): s.write(chr((n >> i)&0xff))
+ return s.getvalue()
- ## Make sure we can index the THINGS, and prepare an ordering table.
- ## What's going on? The THINGS might not have a helpful equality
- ## predicate, so it's easier to work with indices. The ordering table will
- ## remember which THINGS (by index) are considered greater than other
- ## things.
- things = list(things)
- n = len(things)
- order = [{} for i in xrange(n)]
- rorder = [{} for i in xrange(n)]
- for i in xrange(n):
- for j in xrange(n):
- if i != j and cmp(things[i], things[j]):
- order[j][i] = True
- rorder[i][j] = True
-
- ## Now we can do the sort.
- out = []
- while True:
- done = True
- for i in xrange(n):
- if order[i] is not None:
- done = False
- if len(order[i]) == 0:
- for j in rorder[i]:
- del order[j][i]
- yield things[i]
- order[i] = None
- if done:
- break
+###--------------------------------------------------------------------------
+### Address manipulation.
+###
+### I think this is the most demanding application, in terms of address
+### hacking, in the entire TrIPE suite. At least we don't have to do it in
+### C.
+
+class BaseAddress (object):
+ def __init__(me, addrstr, maskstr = None):
+ me._setaddr(addrstr)
+ if maskstr is None:
+ me.mask = -1
+ elif maskstr.isdigit():
+ me.mask = (1 << me.NBITS) - (1 << me.NBITS - int(maskstr))
+ else:
+ me._setmask(maskstr)
+ if me.addr&~me.mask:
+ raise ValueError('network contains bits set beyond mask')
+ def _addrstr_to_int(me, addrstr):
+ try: return loadb(S.inet_pton(me.AF, addrstr))
+ except S.error: raise ValueError('bad address syntax')
+ def _int_to_addrstr(me, n):
+ return S.inet_ntop(me.AF, storeb(me.addr, me.NBITS))
+ def _setmask(me, maskstr):
+ raise ValueError('only prefix masked supported')
+ def _maskstr(me):
+ raise ValueError('only prefix masked supported')
+ def sockaddr(me, port = 0):
+ if me.mask != -1: raise ValueError('not a simple address')
+ return me._sockaddr(port)
+ def __str__(me):
+ addrstr = me._addrstr()
+ if me.mask == -1:
+ return addrstr
+ else:
+ inv = me.mask ^ ((1 << me.NBITS) - 1)
+ if (inv&(inv + 1)) == 0:
+ return '%s/%d' % (addrstr, me.NBITS - inv.bit_length())
+ else:
+ return '%s/%s' % (addrstr, me._maskstr())
+ def withinp(me, net):
+ if type(net) != type(me): return False
+ if (me.mask&net.mask) != net.mask: return False
+ if (me.addr ^ net.addr)&net.mask: return False
+ return me._withinp(net)
+ def eq(me, other):
+ if type(me) != type(other): return False
+ if me.mask != other.mask: return False
+ if me.addr != other.addr: return False
+ return me._eq(other)
+ def _withinp(me, net):
+ return True
+ def _eq(me, other):
+ return True
+
+class InetAddress (BaseAddress):
+ AF = S.AF_INET
+ AFNAME = 'IPv4'
+ NBITS = 32
+ def _addrstr_to_int(me, addrstr):
+ try: return loadb(S.inet_aton(addrstr))
+ except S.error: raise ValueError('bad address syntax')
+ def _setaddr(me, addrstr):
+ me.addr = me._addrstr_to_int(addrstr)
+ def _setmask(me, maskstr):
+ me.mask = me._addrstr_to_int(maskstr)
+ def _addrstr(me):
+ return me._int_to_addrstr(me.addr)
+ def _maskstr(me):
+ return me._int_to_addrstr(me.mask)
+ def _sockaddr(me, port = 0):
+ return (me._addrstr(), port)
+ @classmethod
+ def from_sockaddr(cls, sa):
+ addr, port = (lambda a, p: (a, p))(*sa)
+ return cls(addr), port
+
+class Inet6Address (BaseAddress):
+ AF = S.AF_INET6
+ AFNAME = 'IPv6'
+ NBITS = 128
+ def _setaddr(me, addrstr):
+ pc = addrstr.find('%')
+ if pc == -1:
+ me.addr = me._addrstr_to_int(addrstr)
+ me.scope = 0
+ else:
+ me.addr = me._addrstr_to_int(addrstr[:pc])
+ ais = S.getaddrinfo(addrstr, 0, S.AF_INET6, S.SOCK_DGRAM, 0,
+ S.AI_NUMERICHOST | S.AI_NUMERICSERV)
+ me.scope = ais[0][4][3]
+ def _addrstr(me):
+ addrstr = me._int_to_addrstr(me.addr)
+ if me.scope == 0:
+ return addrstr
+ else:
+ name, _ = S.getnameinfo((addrstr, 0, 0, me.scope),
+ S.NI_NUMERICHOST | S.NI_NUMERICSERV)
+ return name
+ def _sockaddr(me, port = 0):
+ return (me._addrstr(), port, 0, me.scope)
+ @classmethod
+ def from_sockaddr(cls, sa):
+ addr, port, _, scope = (lambda a, p, f = 0, s = 0: (a, p, f, s))(*sa)
+ me = cls(addr)
+ me.scope = scope
+ return me, port
+ def _withinp(me, net):
+ return net.scope == 0 or me.scope == net.scope
+ def _eq(me, other):
+ return me.scope == other.scope
+
+def parse_address(addrstr, maskstr = None):
+ if addrstr.find(':') >= 0: return Inet6Address(addrstr, maskstr)
+ else: return InetAddress(addrstr, maskstr)
+
+def parse_net(netstr):
+ try: sl = netstr.index('/')
+ except ValueError: raise ValueError('missing mask')
+ return parse_address(netstr[:sl], netstr[sl + 1:])
+
+def straddr(a): return a is None and '#<none>' or str(a)
###--------------------------------------------------------------------------
### Parse the configuration file.
## this service are largely going to be satellite notes, I don't think
## scalability's going to be a problem.
+TESTADDRS = [InetAddress('1.2.3.4'), Inet6Address('2001::1')]
+
+CONFSYNTAX = [
+ ('COMMENT', RX.compile(r'^\s*($|[;#])')),
+ ('GRPHDR', RX.compile(r'^\s*\[(.*)\]\s*$')),
+ ('ASSGN', RX.compile(r'\s*([\w.-]+)\s*[:=]\s*(|\S|\S.*\S)\s*$'))]
+
+class ConfigError (Exception):
+ def __init__(me, file, lno, msg):
+ me.file = file
+ me.lno = lno
+ me.msg = msg
+ def __str__(me):
+ return '%s:%d: %s' % (me.file, me.lno, me.msg)
+
class Config (object):
"""
Represents a configuration file.
The most interesting thing is probably the `groups' slot, which stores a
list of pairs (NAME, PATTERNS); the NAME is a string, and the PATTERNS a
- list of (TAG, PEER, ADDR, MASK) triples. The implication is that there
- should be precisely one peer with a name matching NAME-*, and that it
- should be NAME-TAG, where (TAG, PEER, ADDR, MASK) is the first triple such
- that the host's primary IP address (if PEER is None -- or the IP address it
- would use for communicating with PEER) is within the network defined by
- ADDR/MASK.
+ list of (TAG, PEER, NETS) triples. The implication is that there should be
+ precisely one peer from the set, and that it should be named TAG, where
+ (TAG, PEER, NETS) is the first triple such that the host's primary IP
+ address (if PEER is None -- or the IP address it would use for
+ communicating with PEER) is within one of the networks defined by NETS.
"""
def __init__(me, file):
Internal function to update the configuration from the underlying file.
"""
- ## Read the configuration. We have no need of the fancy substitutions,
- ## so turn them all off.
- cp = RawConfigParser()
- cp.read(me._file)
-
- ## 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
- ## actually in use. Note that we never send packets to the test address;
- ## we just use it to discover routing information.
- if cp.has_option('DEFAULT', 'test-addr'):
- testaddr = cp.get('DEFAULT', 'test-addr')
- S.inet_aton(testaddr)
- else:
- testaddr = '1.2.3.4'
-
- ## Scan the configuration file and build the groups structure.
- groups = []
- for sec in cp.sections():
- pats = []
- for tag in cp.options(sec):
- spec = cp.get(sec, tag).split()
-
- ## Parse the entry into peer and network.
- if len(spec) == 1:
- peer = None
- net = spec[0]
- else:
- peer, net = spec
-
- ## Syntax of a net is ADDRESS/MASK, where ADDRESS is a dotted-quad,
- ## 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]))
+ if T._debug: print '# reread config'
+
+ ## Initial state.
+ testaddrs = {}
+ groups = {}
+ grpname = None
+ grplist = []
+
+ ## Open the file and start reading.
+ with open(me._file) as f:
+ lno = 0
+ for line in f:
+ lno += 1
+ for tag, rx in CONFSYNTAX:
+ m = rx.match(line)
+ if m: break
else:
- n = int(net[slash + 1:], 10)
- mask = (1 << 32) - (1 << 32 - n)
- pats.append((tag, peer, addr & mask, mask))
-
- ## Annoyingly, RawConfigParser doesn't preserve the order of options.
- ## In order to make things vaguely sane, we topologically sort the
- ## patterns so that more specific patterns are checked first.
- pats = list(toposort(lambda (t, p, a, m), (tt, pp, aa, mm): \
- (p and not pp) or \
- (p == pp and m == (m | mm) and aa == (a & mm)),
- pats))
- groups.append((sec, pats))
+ raise ConfigError(me._file, lno, 'failed to parse line: %r' % line)
+
+ if tag == 'COMMENT':
+ ## A comment. Ignore it and hope it goes away.
+
+ continue
+
+ elif tag == 'GRPHDR':
+ ## A group header. Flush the old group and start a new one.
+ newname = m.group(1)
+
+ if grpname is not None: groups[grpname] = grplist
+ if newname in groups:
+ raise ConfigError(me._file, lno,
+ "duplicate group name `%s'" % newname)
+ grpname = newname
+ grplist = []
+
+ elif tag == 'ASSGN':
+ ## An assignment. Deal with it.
+ name, value = m.group(1), m.group(2)
+
+ if grpname is None:
+ ## We're outside of any group, so this is a global configuration
+ ## tweak.
+
+ if name == 'test-addr':
+ for astr in value.split():
+ try:
+ a = parse_address(astr)
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP address `%s': %s" %
+ (astr, e))
+ if a.AF in testaddrs:
+ raise ConfigError(me._file, lno,
+ 'duplicate %s test-address' % a.AFNAME)
+ testaddrs[a.AF] = a
+ else:
+ raise ConfigError(me._file, lno,
+ "unknown global option `%s'" % name)
+
+ else:
+ ## Parse a pattern and add it to the group.
+ spec = value.split()
+ i = 0
+
+ ## Check for an explicit target address.
+ if i >= len(spec) or spec[i].find('/') >= 0:
+ peer = None
+ af = None
+ else:
+ try:
+ peer = parse_address(spec[i])
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP address `%s': %s" %
+ (spec[i], e))
+ af = peer.AF
+ i += 1
+
+ ## Parse the list of local networks.
+ nets = []
+ while i < len(spec):
+ try:
+ net = parse_net(spec[i])
+ except Exception, e:
+ raise ConfigError(me._file, lno,
+ "invalid IP network `%s': %s" %
+ (spec[i], e))
+ else:
+ nets.append(net)
+ i += 1
+ if not nets:
+ raise ConfigError(me._file, lno, 'no networks defined')
+
+ ## Make sure that the addresses are consistent.
+ for net in nets:
+ if af is None:
+ af = net.AF
+ elif net.AF != af:
+ raise ConfigError(me._file, lno,
+ "net %s doesn't match" % net)
+
+ ## Add this entry to the list.
+ grplist.append((name, peer, nets))
+
+ ## Fill in the default test addresses if necessary.
+ for a in TESTADDRS: testaddrs.setdefault(a.AF, a)
## Done.
- me.testaddr = testaddr
+ if grpname is not None: groups[grpname] = grplist
+ me.testaddrs = testaddrs
me.groups = groups
### This will be a configuration file.
CF = None
+def cmd_showconfig():
+ T.svcinfo('test-addr=%s' %
+ ' '.join(str(a)
+ for a in sorted(CF.testaddrs.itervalues(),
+ key = lambda a: a.AFNAME)))
+def cmd_showgroups():
+ for g in sorted(CF.groups.iterkeys()):
+ T.svcinfo(g)
+def cmd_showgroup(g):
+ try: pats = CF.groups[g]
+ except KeyError: raise T.TripeJobError('unknown-group', g)
+ for t, p, nn in pats:
+ T.svcinfo('peer', t,
+ 'target', p and str(p) or '(default)',
+ 'net', ' '.join(map(str, nn)))
+
###--------------------------------------------------------------------------
### Responding to a network up/down event.
"""
Return the local IP address used for talking to PEER.
"""
- sk = S.socket(S.AF_INET, S.SOCK_DGRAM)
+ sk = S.socket(peer.AF, S.SOCK_DGRAM)
try:
try:
- sk.connect((peer, 1))
- addr, _ = sk.getsockname()
- addr, = unpack('>L', S.inet_aton(addr))
- return addr
+ sk.connect(peer.sockaddr(1))
+ addr = sk.getsockname()
+ return type(peer).from_sockaddr(addr)[0]
except S.error:
return None
finally:
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.
## Find the current list of peers.
peers = SM.list()
- ## Work out the primary IP address.
+ ## Work out the primary IP addresses.
+ locals = {}
if upness:
- addr = localaddr(CF.testaddr)
- if addr is None:
- upness = False
+ for af, remote in CF.testaddrs.iteritems():
+ local = localaddr(remote)
+ if local is not None: locals[af] = local
+ if not locals: upness = False
+ if not T._debug: pass
+ elif not locals: print '# offline'
else:
- addr = None
+ for local in locals.itervalues():
+ print '# local %s address = %s' % (local.AFNAME, local)
## Now decide what to do.
changes = []
- for g, pp in CF.groups:
+ for g, pp in CF.groups.iteritems():
+ if T._debug: print '# check group %s' % g
## Find out which peer in the group ought to be active.
- ip = None
- map = {}
+ statemap = {}
want = None
- for t, p, a, m in pp:
- if p is None or not upness:
- ipq = addr
- else:
- ipq = localaddr(p)
- if upness and ip is None and \
- ipq is not None and (ipq & m) == a:
- map[t] = 'up'
- want = t
- ip = ipq
+ matchp = False
+ for t, p, nn in pp:
+ af = nn[0].AF
+ if p is None or not upness: ip = locals.get(af)
+ else: ip = localaddr(p)
+ if T._debug:
+ info = 'peer = %s; target = %s; nets = %s; local = %s' % (
+ t, p or '(default)', ', '.join(map(str, nn)), straddr(ip))
+ if upness and not matchp and \
+ ip is not None and any(ip.withinp(n) for n in nn):
+ if T._debug: print '# %s: SELECTED' % info
+ statemap[t] = 'up'
+ select.append('%s=%s' % (g, t))
+ if t == 'down' or t.startswith('down/'): want = None
+ else: want = t
+ matchp = True
else:
- map[t] = 'down'
+ statemap[t] = 'down'
+ if T._debug: print '# %s: skipped' % info
## Shut down the wrong ones.
found = False
+ if T._debug: print '# peer-map = %r' % statemap
for p in peers:
- what = map.get(p, 'leave')
+ what = statemap.get(p, 'leave')
if what == 'up':
found = True
+ if T._debug: print '# peer %s: already up' % p
elif what == 'down':
- changes.append(lambda p=p: SM.kill(p))
+ def _(p = p):
+ try:
+ SM.kill(p)
+ except T.TripeError, exc:
+ if exc.args[0] == 'unknown-peer':
+ ## Inherently racy; don't worry about this.
+ 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)):
- changes.append(lambda: T._simple(SM.svcsubmit('connect', 'active',
- want)))
- lastip[g] = ip
+ if want is not None and not found:
+ def _(want = want):
+ try:
+ 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(_)
## 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):
"""
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.
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)
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.
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():
"""
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__':