X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/blobdiff_plain/f8d6fc7b379fc963c1a310872fb030c69c7aff7d..f8204e509efff191e5f7215be07c7060a401475b:/svc/conntrack.in diff --git a/svc/conntrack.in b/svc/conntrack.in index b840fc20..043c9698 100644 --- a/svc/conntrack.in +++ b/svc/conntrack.in @@ -36,6 +36,7 @@ import socket as S 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) try: from gi.repository import GLib as G @@ -53,47 +54,6 @@ class struct (object): 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. - - The THINGS may be any finite iterable; it is converted to a list - internally. - """ - - ## 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. @@ -158,6 +118,19 @@ def straddr(a): return a is None and '#' or str(a) TESTADDR = InetAddress('1.2.3.4') +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. @@ -191,52 +164,99 @@ class Config (object): 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) 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 - ## 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 = InetAddress(cp.get('DEFAULT', 'test-addr')) - else: - testaddr = TESTADDR - - ## Scan the configuration file and build the groups structure. + ## Initial state. + testaddr = None 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] + 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: - 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. - 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, n), (tt, pp, nn): \ - (p and not pp) or \ - (p == pp and n.withinp(nn)), - pats)) - groups[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 testaddr is not None: + raise ConfigError(me._file, lno, 'duplicate test-address') + testaddr = 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 + else: + try: + peer = parse_address(spec[i]) + except Exception, e: + raise ConfigError(me._file, lno, + "invalid IP address `%s': %s" % + (spec[i], e)) + i += 1 + + ## Parse the local network. + if len(spec) != i + 1: + raise ConfigError(me._file, lno, 'no network defined') + try: + net = parse_net(spec[i]) + except Exception, e: + raise ConfigError(me._file, lno, + "invalid IP network `%s': %s" % + (spec[i], e)) + + ## Add this entry to the list. + grplist.append((name, peer, net)) + + ## Fill in the default test address if necessary. + if testaddr is None: testaddr = TESTADDR ## Done. + if grpname is not None: groups[grpname] = grplist me.testaddr = testaddr me.groups = groups