svc/conntrack.in: Contemplate multiple address families.
authorMark Wooding <mdw@distorted.org.uk>
Fri, 29 Sep 2017 08:15:05 +0000 (09:15 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Thu, 28 Jun 2018 23:29:24 +0000 (00:29 +0100)
A number of relatively simple changes, with no overall functional change
except for a few diagnostic messages.

  * Attach the address-family code and a name string to the
    `InetAddress' class.  This will mean that we can add new address
    families without breaking things.

  * Make `testaddrs' (and related variables) be a dictionary, mapping
    address families to addresses, rather than just a lone address.

  * Ensure that the networks in a peer assignment belong to the same
    family.  They will do for now, because there's only one.

  * Have `kickpeers' maintain a local IP address for each family, rather
    than just a single one.

svc/conntrack.in

index a31b268..cc753d4 100644 (file)
@@ -58,6 +58,8 @@ class struct (object):
 ### Address manipulation.
 
 class InetAddress (object):
+  AF = S.AF_INET
+  AFNAME = 'IPv4'
   def __init__(me, addrstr, maskstr = None):
     me.addr = me._addrstr_to_int(addrstr)
     if maskstr is None:
@@ -116,7 +118,7 @@ def straddr(a): return a is None and '#<none>' or str(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')
+TESTADDRS = [InetAddress('1.2.3.4')]
 
 CONFSYNTAX = [
   ('COMMENT', RX.compile(r'^\s*($|[;#])')),
@@ -167,7 +169,7 @@ class Config (object):
     if T._debug: print '# reread config'
 
     ## Initial state.
-    testaddr = None
+    testaddrs = {}
     groups = {}
     grpname = None
     grplist = []
@@ -215,9 +217,10 @@ class Config (object):
                   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
+                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)
@@ -230,6 +233,7 @@ class Config (object):
             ## 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])
@@ -237,6 +241,7 @@ class Config (object):
                 raise ConfigError(me._file, lno,
                                   "invalid IP address `%s': %s" %
                                   (spec[i], e))
+              af = peer.AF
               i += 1
 
             ## Parse the list of local networks.
@@ -254,22 +259,33 @@ class Config (object):
             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 address if necessary.
-    if testaddr is None: testaddr = TESTADDR
+    ## Fill in the default test addresses if necessary.
+    for a in TESTADDRS: testaddrs.setdefault(a.AF, a)
 
     ## Done.
     if grpname is not None: groups[grpname] = grplist
-    me.testaddr = testaddr
+    me.testaddrs = testaddrs
     me.groups = groups
 
 ### This will be a configuration file.
 CF = None
 
 def cmd_showconfig():
-  T.svcinfo('test-addr=%s' % CF.testaddr)
+  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)
@@ -288,12 +304,12 @@ def localaddr(peer):
   """
   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.sockaddr(1))
       addr = sk.getsockname()
-      return InetAddress.from_sockaddr(addr)[0]
+      return type(peer).from_sockaddr(addr)[0]
     except S.error:
       return None
   finally:
@@ -348,16 +364,18 @@ def kickpeers():
     ## 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
-    else:
-      addr = None
+      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 addr: print '#   local address = %s' % straddr(addr)
-    else: print '#   offline'
+    elif not locals: print '#   offline'
+    else:
+      for local in locals.itervalues():
+        print '#   local %s address = %s' % (local.AFNAME, local)
 
     ## Now decide what to do.
     changes = []
@@ -369,7 +387,8 @@ def kickpeers():
       want = None
       matchp = False
       for t, p, nn in pp:
-        if p is None or not upness: ip = addr
+        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' % (