[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Begin refactoring path selection.
Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv14130/lib/mixminion
Modified Files:
ClientDirectory.py ServerInfo.py
Log Message:
Begin refactoring path selection.
The new design is less insanely brilliant than I once wanted it to be;
this is probably okay. Premature OO makes for scarry systems.
Index: ClientDirectory.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientDirectory.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- ClientDirectory.py 28 Sep 2003 05:27:55 -0000 1.1
+++ ClientDirectory.py 6 Oct 2003 20:55:06 -0000 1.2
@@ -17,6 +17,7 @@
import socket
import stat
import time
+import types
import urllib2
import mixminion.ClientMain #XXXX
@@ -86,21 +87,6 @@
finally:
mixminion.ClientMain.clientUnlock() # XXXX
- # Mixminion 0.0.1 used an obsolete directory-full-of-servers in
- # DIR/servers. If there's nothing there, we remove it. Otherwise,
- # we warn.
- # XXXX010 Eventually, we can remove this.
- sdir = os.path.join(self.dir,"servers")
- if os.path.exists(sdir):
- if os.listdir(sdir):
- LOG.warn("Skipping obsolete server directory %s", sdir)
- else:
- try:
- LOG.warn("Removing obsolete server directory %s", sdir)
- os.rmdir(sdir)
- except OSError, e:
- LOG.warn("Failed: %s", e)
-
def updateDirectory(self, forceDownload=0, now=None):
"""Download a directory from the network as needed."""
if now is None:
@@ -556,6 +542,42 @@
else:
return None
+ def generatePaths(self, nPaths, pathSpec, exitAddress, startAt=None, endAt=None,
+ prng=None):
+ """Return a list of pairs of lists of serverinfo DOCDOC."""
+
+ paths = []
+ lastHop = exitAddress.getLastHop()
+ fixedLastHop = pathSpec.getFixedLastServer(startAt,endAt)
+ if exitAddress.isSSFragmented:
+ # We _must_ have a single common last hop.
+ if not lastHop and not fixedLastHop:
+ fixedLastHop = exitAddress.pickExitServer(self,startAt,endAt)
+
+ for _ in xrange(nPaths):
+ p1 = []
+ p2 = []
+ for p in pathSpec.path1:
+ p1.extend(p.getServerNames())
+ for p in pathSpec.path2:
+ p2.extend(p.getServerNames())
+ n1 = len(p1)
+ p = p1+p2
+ # Make the exit hop _not_ be None; deal with getPath brokenness.
+ #XXXX refactor this.
+ if lastHop:
+ p.append(lastHop)
+ elif fixedLastHop:
+ assert p[-1] == None
+ p[-1] = fixedLastHop
+ elif p[-1] == None and not exitAddress.isReply:
+ p[-1] = exitAddress.pickExitServer(self, startAt, endAt)
+
+ path = self.getPath(None, p, startAt=startAt, endAt=endAt)
+ paths.append( (path[:n1], path[n1:]) )
+
+ return paths
+
def getPath(self, endCap, template, startAt=None, endAt=None, prng=None):
"""Workhorse method for path selection. Given a template, and
a capability that must be supported by the exit node, return
@@ -656,6 +678,24 @@
return servers
+ def validatePath2(self, pathSpec, exitAddress, startAt=None, endAt=None):
+ """DOCDOC
+ takes pathspec; raises UIError or does nothing."""
+ if startAt is None: startAt = time.time()
+ if endAt is None: endAt = startAt+self.DEFAULT_REQUIRED_LIFETIME
+
+ p = pathSpec.path1+pathSpec.path2
+ for e in p:
+ e.validate(self, startAt, endAt)
+ fs = p[-1].getFixedServer()
+ lh = exitAddress.getLastHop()
+ if lh is not None:
+ fs = self.getServerInfo(lh, startAt, endAt)
+ if fs is None:
+ raise UIError("No known server descriptor named %s",lh)
+ if fs is not None:
+ exitAddress.checkSupportedBySever(fs)
+
def checkClientVersion(self):
"""Check the current client's version against the stated version in
the most recently downloaded directory; print a warning if this
@@ -905,3 +945,258 @@
defaultNHops=defaultNHops)
assert path1 == []
return path2
+
+#----------------------------------------------------------------------
+
+KNOWN_STRING_EXIT_TYPES = [
+ "mbox", "smtp", "drop"
+]
+
+class ExitAddress:
+ #FFFF Perhaps this crams too much into ExitAddress.
+ def __init__(self,exitType=None,exitAddress=None,lastHop=None,isReply=0,
+ isSSFragmented=0):
+ if isReply:
+ assert exitType is None
+ assert exitAddress is None
+ else:
+ assert exitType is not None
+ assert exitAddress is not None
+ if exitType in (MBOX_TYPE, 'mbox'):
+ assert lastHop
+ if type(exitType) == types.StringType:
+ if exitType not in KNOWN_STRING_EXIT_TYPES:
+ raise UIError("Unknown exit type: %r"%exitType)
+ elif type(exitType) == types.IntType:
+ if not (0 <= exitType <0xFFFF):
+ raise UIError("Exit type 0x%04X is out of range."%exitType)
+ else:
+ raise UIError("Unknown exit type: %r"%exitType)
+ self.exitType = exitType
+ self.exitAddress = exitAddress
+ self.lastHop = lastHop
+ self.isReply = isReply
+ self.isSSFragmented = isSSFragmented #server-side frag reassembly only.
+ self.nFragments = self.exitSize = 0
+ self.headers = {}
+ def setFragmented(self, isSSFragmented, nFragments):
+ self.isSSFragmented = isFragmented
+ self.nFragments = nFragments
+ def setExitSize(self, exitSize):
+ self.exitSize = exitSize
+ def setHeaders(self, headers):
+ self.headers = headers
+ def isReply(self):
+ return self.isReply
+ def getLastHop(self):
+ return self.lastHop
+ def isSupportedByServer(self, desc):
+ try:
+ self.checkSupportedByServer(desc)
+ return 1
+ except UIError:
+ return 0
+ def checkSupportedByServer(self, desc):
+ if self.isReply:
+ return
+ nickname = desc.getNickname()
+ if self.isSSFragmented:
+ dfsec = desc['Delivery/Fragmented']
+ if not dfsec.get("Version"):
+ raise UIError("Server %s doesn't support fragment reassembly."
+ %nickname)
+ if self.nFragments > dfsec.get("Maximum-Fragments",0):
+ raise UIError("Too many fragments for server %s to reassemble."
+ %nickname)
+ if self.exitType in ('smtp', SMTP_TYPE):
+ ssec = desc['Delivery/SMTP']
+ if not ssec.get("Version"):
+ raise UIError("Server %s doesn't support SMTP"%nickname)
+ if self.headers.has_key("FROM") and not ssec['Allow-From']:
+ raise UIError("Server %s doesn't support user-supplied From"%
+ nickname)
+ if floorDiv(self.exitSize,1024) > ssec['Maximum-Size']:
+ raise UIError("Message to long for server %s to deliver."%
+ nickname)
+ elif self.exitType in ('mbox', MBOX_TYPE):
+ msec = desc['Delivery/SMTP']
+ if not msec.get("Version"):
+ raise UIError("Server %s doesn't support MBOX"%nickname)
+ if self.headers.has_key("FROM") and not msec['Allow-From']:
+ raise UIError("Server %s doesn't support user-supplied From"%
+ nickname)
+ if floorDiv(self.exitSize,1024) > msec['Maximum-Size']:
+ raise UIError("Message to long for server %s to deliver."%
+ nickname)
+ def pickExitServer(self, directory, startAt, endAt):
+ """DOCDOC"""
+
+ assert 0
+
+ def getRouting(self, desc):
+ """DOCDOC"""
+ #XXXX
+ assert 0
+
+class PathElement:
+ def validate(self, directory, start, end):
+ raise NotImplemented
+ def getServers(self, directory, start, end):
+ raise NotImplemented
+ def getFixedServer(self, directory, start, end):
+ raise NotImplemented
+ def getServerNames(self):
+ raise NotImplemented
+
+class ServerPathElement(PathElement):
+ def __init__(self, nickname):
+ self.nickname = nickname
+ def validate(self, directory, start, end):
+ if None == directory.getServerInfo(self.nickname, start, end):
+ raise UIError("No valid server found with name %r",self.nickname)
+ def getFixedServer(self, directory, start, end):
+ return directory.getServerInfo(self.nickname, start, end)
+ def getServerNames(self):
+ return [ self.nickname ]
+
+class DescriptorPathElement(PathElement):
+ def __init__(self, desc):
+ self.desc = desc
+ def validate(self, directory, start, end):
+ if not self.desc.isValidFrom(start, end):
+ raise UIError("Server %r is not valid during given time range",
+ self.desc.getNickname())
+ def getFixedServer(self, directory, start, end):
+ return self.desc
+ def getServerNames(self):
+ return [ self.desc ]
+
+class RandomServersPathElement(PathElement):
+ def __init__(self, n=None, approx=None):
+ assert not (n and approx)
+ self.n=n
+ self.approx=approx
+ def validate(self, directory, start, end):
+ pass
+ def getFixedServer(self, directory, start, end):
+ return None
+ def getServerNames(self):
+ if self.n:
+ n = self.n
+ else:
+ prng = mixminion.Crypto.getCommonPRNG()
+ n = int(prng.getNormal(self.approx,1.5)+0.5)
+ return [ None ] * self.n
+
+#----------------------------------------------------------------------
+class PathSpecifier:
+ def __init__(self, path1, path2, isReply, isSURB):
+ if isSURB:
+ assert path2 and not path1
+ elif isReply:
+ assert path1 and not path2
+ else:
+ assert path1 and path2
+ self.path1=path1
+ self.path2=path2
+ self.isReply=isReply
+ self.isSURB=isSURB
+ def getFixedLastServer(self,startAt,endAt):
+ """DOCDOC"""
+ if self.path2:
+ return self.path2[-1].getFixedServer(startAt,endAt)
+ else:
+ return None
+
+#----------------------------------------------------------------------
+def parsePath2(config, path, nHops=None, isReply=0, isSURB=0,
+ defaultNHops=None):
+ """DOCDOC ; Returns a pathSpecifier.
+ """
+ halfPath = isReply or isSURB
+ if not path:
+ path = '*'
+ # Break path into a list of entries of the form:
+ # Nickname
+ # or "<swap>"
+ # or "?"
+ p = []
+ while path:
+ if path[0] == "'":
+ m = re.match(r"'([^']+|\\')*'", path)
+ if not m:
+ raise UIError("Mismatched quotes in path.")
+ p.append(m.group(1).replace("\\'", "'"))
+ path = path[m.end():]
+ if path and path[0] not in ":,":
+ raise UIError("Invalid quotes in path.")
+ elif path[0] == '"':
+ m = re.match(r'"([^"]+|\\")*"', path)
+ if not m:
+ raise UIError("Mismatched quotes in path.")
+ p.append(m.group(1).replace('\\"', '"'))
+ path = path[m.end():]
+ if path and path[0] not in ":,":
+ raise UIError("Invalid quotes in path.")
+ else:
+ m = re.match(r"[^,:]+",path)
+ if not m:
+ raise UIError("Invalid path")
+ p.append(m.group(0))
+ path = path[m.end():]
+ if not path:
+ break
+ elif path[0] == ',':
+ path = path[1:]
+ elif path[0] == ':':
+ path = path[1:]
+ p.append("<swap>")
+
+ pathEntries = []
+ approxHops = 0 # Not including star.
+ for ent in p:
+ if re.match(r'\*(\d+)', ent):
+ pathEntries.append(RandomServersPathElement(n=int(ent[1:])))
+ nHops += int(ent[1:])
+ elif re.match(r'\~(\d+)', ent):
+ pathEntries.append(RandomServersPathElement(approx=int(ent[1:])))
+ nHops += int(ent[1:])
+ elif ent == '*':
+ pathEntries.append("*")
+ elif ent == '<swap>':
+ pathEntries.append("<swap>")
+ else:
+ pathEntries.append(ServerPathElement(ent))
+ nHops += 1
+
+ # If there's a variable-length wildcard...
+ if "*" in path:
+ # Find out how many hops we should have.
+ starPos = pathEntries.index("*")
+ myNHops = nHops or defaultNHops or 6
+ extraHops = max(myNHops-approxHops, 0)
+ pathEntries[starPos:starPos+1] = RandomServersPathElement(n=extraHops)
+
+ # Figure out how long the first leg should be.
+ if "<swap>" in pathElements:
+ # Calculate colon position
+ if halfPath:
+ raise UIError("Can't specify swap point with replies")
+ colonPos = pathElements.index("<swap>")
+ firstLegLen = colonPos
+ del pathElements[colonPos]
+ elif isReply:
+ firstLegLen = len(pathElements)
+ elif isSURB:
+ firstLegLen = 0
+ else:
+ firstLegLen = ceilDiv(len(pathElements), 2)
+
+ # Split the path into 2 legs.
+ path1, path2 = pathElements[:firstLegLen], pathElements[firstLegLen:]
+ if not halfPath and len(path1)+len(path2) < 2:
+ raise UIError("Path is too short")
+ if not halfPath and (not path1 or not path2):
+ raise UIError("Each leg of the path must have at least 1 hop")
+
+ return PathSpecifier(path1, path2, isReply, isSURB)
Index: ServerInfo.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ServerInfo.py,v
retrieving revision 1.55
retrieving revision 1.56
diff -u -d -r1.55 -r1.56
--- ServerInfo.py 12 Sep 2003 15:52:46 -0000 1.55
+++ ServerInfo.py 6 Oct 2003 20:55:06 -0000 1.56
@@ -266,6 +266,10 @@
def getIdentity(self):
return self['Server']['Identity']
+ def canDeliverTo(self, otherDesc):
+ #DOCDOC
+ return 1
+
def getCaps(self):
# FFFF refactor this once we have client addresses.
caps = []