break
###--------------------------------------------------------------------------
+### Address manipulation.
+
+class InetAddress (object):
+ def __init__(me, addrstr, maskstr = None):
+ me.addr = me._addrstr_to_int(addrstr)
+ if maskstr is None:
+ me.mask = -1
+ elif maskstr.isdigit():
+ me.mask = (1 << 32) - (1 << 32 - int(maskstr))
+ else:
+ me.mask = me._addrstr_to_int(maskstr)
+ if me.addr&~me.mask:
+ raise ValueError('network contains bits set beyond mask')
+ def _addrstr_to_int(me, addrstr):
+ return unpack('>L', S.inet_aton(addrstr))[0]
+ def _int_to_addrstr(me, n):
+ return S.inet_ntoa(pack('>L', n))
+ def sockaddr(me, port = 0):
+ if me.mask != -1: raise ValueError('not a simple address')
+ return me._int_to_addrstr(me.addr), port
+ def __str__(me):
+ addrstr = me._int_to_addrstr(me.addr)
+ if me.mask == -1:
+ return addrstr
+ else:
+ inv = me.mask ^ ((1 << 32) - 1)
+ if (inv&(inv + 1)) == 0:
+ return '%s/%d' % (addrstr, 32 - inv.bit_length())
+ else:
+ return '%s/%s' % (addrstr, me._int_to_addrstr(me.mask))
+ def withinp(me, net):
+ if (me.mask&net.mask) != net.mask: return False
+ if (me.addr ^ net.addr)&net.mask: return False
+ return True
+ def eq(me, other):
+ if me.mask != other.mask: return False
+ if me.addr != other.addr: return False
+ return True
+ @classmethod
+ def from_sockaddr(cls, sa):
+ addr, port = (lambda a, p: (a, p))(*sa)
+ return cls(addr), port
+
+def parse_address(addrstr, maskstr = None):
+ 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.
## Hmm. Should I try to integrate this with the peers database? It's not a
## this service are largely going to be satellite notes, I don't think
## scalability's going to be a problem.
+TESTADDR = InetAddress('1.2.3.4')
+
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, NET) triples. The implication is that there should be
+ precisely one peer from the set, and that it should be named TAG, where
+ (TAG, PEER, NET) 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 NET.
"""
def __init__(me, file):
## 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)
+ testaddr = InetAddress(cp.get('DEFAULT', 'test-addr'))
else:
- testaddr = '1.2.3.4'
+ testaddr = TESTADDR
## Scan the configuration file and build the groups structure.
groups = []
peer = None
net = spec[0]
else:
- peer, net = spec
+ peer = InetAddress(spec[0])
+ net = spec[1]
## 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]))
- else:
- n = int(net[slash + 1:], 10)
- mask = (1 << 32) - (1 << 32 - n)
- pats.append((tag, peer, addr & mask, mask))
+ net = parse_net(net)
+ pats.append((tag, peer, net))
## 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): \
+ pats = list(toposort(lambda (t, p, n), (tt, pp, nn): \
(p and not pp) or \
- (p == pp and m == (m | mm) and aa == (a & mm)),
+ (p == pp and n.withinp(nn)),
pats))
groups.append((sec, pats))
### This will be a configuration file.
CF = None
-def straddr(a): return a is None and '#<none>' or S.inet_ntoa(pack('>L', a))
-def strmask(m):
- for i in xrange(33):
- if m == 0xffffffff ^ ((1 << (32 - i)) - 1): return i
- return straddr(m)
-
def cmd_showconfig():
T.svcinfo('test-addr=%s' % CF.testaddr)
def cmd_showgroups():
pats = p
break
else:
- raise T.TripeJobError, 'unknown-group', g
- for t, p, a, m in pats:
+ raise T.TripeJobError('unknown-group', g)
+ for t, p, n in pats:
T.svcinfo('peer', t,
- 'target', p or '(default)',
- 'net', '%s/%s' % (straddr(a), strmask(m)))
+ 'target', p and str(p) or '(default)',
+ 'net', str(n))
###--------------------------------------------------------------------------
### Responding to a network up/down event.
sk = S.socket(S.AF_INET, 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 InetAddress.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():
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.
ip = None
map = {}
want = None
- for t, p, a, m in pp:
+ for t, p, n in pp:
if p is None or not upness:
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))
+ info = 'peer=%s; target=%s; net=%s; local=%s' % (
+ t, p or '(default)', n, straddr(ipq))
if upness and ip is None and \
- ipq is not None and (ipq & m) == a:
+ ipq is not None and ipq.withinp(n):
if T._debug: print '# %s: SELECTED' % info
map[t] = 'up'
select.append('%s=%s' % (g, t))
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
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
def attach(me, bus):
try:
nm = bus.get_object(NM_NAME, NM_PATH)
- state = nm.Get(NM_IFACE, 'State')
+ state = nm.Get(NM_IFACE, 'State', dbus_interface = DBPROPS_IFACE)
if state in NM_CONNSTATES:
netupdown(True, ['nm', 'initially-connected'])
else:
def _nm_state(me, state):
if state in NM_CONNSTATES:
- netupdown(True, ['nm', 'connected'])
+ 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.
def _cm_state(me, prop, value):
if prop != 'State': return
- netupdown(value == 'online', ['connman', value])
+ delay_netupdown(value == 'online', ['connman', value])
###--------------------------------------------------------------------------
### Maemo monitor.
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.
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():
"""