Index: fedd/federation/emulab_shared_nat_segment.py
===================================================================
--- fedd/federation/emulab_shared_nat_segment.py	(revision d75005b9a70e7f97c5414bfa01d7e54e5494da33)
+++ fedd/federation/emulab_shared_nat_segment.py	(revision d75005b9a70e7f97c5414bfa01d7e54e5494da33)
@@ -0,0 +1,240 @@
+#!/usr/local/bin/python
+
+import sys, os
+import re
+import os.path
+
+import tempfile
+import subprocess
+import logging 
+import time
+import signal
+
+import util
+
+import xmlrpclib
+
+import emulab_segment
+
+
+class start_segment(emulab_segment.start_segment):
+    def __init__(self, log=None, keyfile=None, debug=False, boss=None,
+	    ops=None, cert=None):
+	emulab_segment.start_segment.__init__(self, log=log, keyfile=keyfile,
+		debug=debug, boss=boss, ops=ops, cert=cert)
+
+    def __call__(self, parent, eid, pid, user, tclfile, tmpdir, timeout=0, 
+	    gid=None):
+	"""
+	Start a sub-experiment on a federant.
+
+	Get the current state, and terminate the experiment if it exists. The
+	group membership of the experiment is difficult to determine or change,
+	so start with a clean slate.  Create a new one and ship data
+	and configs and start the experiment.  There are small ordering
+	differences based on the initial state of the sub-experiment.
+	"""
+
+	# exports_segment has established a clean dummy experiment.
+
+	if not self.set_up_experiment_filespace(user, self.ops, 
+		pid, eid, tmpdir):
+	    return False
+
+	# Put the file into a string to pass to emulab.
+	try:
+	    tcl = "".join([ l for l in open(tclfile,"r")])
+	except EnvironmentError, e:
+	    self.log.error("Can't read %s: %s" % (tclfile, e))
+	    return False
+	
+	# Stage the new configuration
+	if not self.modify_exp(pid, eid, tcl):
+	    self.log.error("modify failed")
+	    return False
+
+	if not self.swap_exp(pid, eid, 'in'):
+	    self.log.error("swap in failed")
+	    return False
+	# Everything has gone OK.
+	self.get_mapping(pid,eid)
+	return True
+
+class exports_segment(emulab_segment.exports_segment):
+    '''
+    Class to export parameters from this segment.  The shared NAT allocates
+    externally accessible ports in 2 steps.  Provisional ports are assigned
+    when the experiment is created or modified and then preserved when possible
+    across further mods.  This class creates a very simple experiment to make
+    those provisional allocations and then installs them in the connInfo list.
+    Note that this class takes on some of the initial steps from start_segment
+    - clearing out old experiments and creating the dummy and initial
+    experiment.
+    '''
+    def __init__(self, log=None, keyfile=None, debug=False, boss=None,
+	    ops=None, cert=None):
+	emulab_segment.exports_segment.__init__(self, log=log, keyfile=keyfile,
+		debug=debug, boss=boss, ops=ops, cert=cert)
+
+    def make_portal_experiment(self, pid, eid, tmpdir, gid, ports):
+	ns_data = \
+'''
+set ns [new Simulator]
+source tb_compat.tcl
+
+'''
+	for p in ports:
+	    ns_data += 'set %s [$ns node]\n' % p
+	    ns_data += \
+		'tb-allow-external %s shared nat 0/0/tcp rdr 22/tcp\n' % p
+	ns_data += \
+'''	
+$ns rtproto Session
+$ns run 
+'''
+	# If ports is empty, just create the standard null experiment
+	if len(ports) == 0:
+	    ns_data = None
+
+	state = self.get_state(pid, eid)
+
+	if state != 'none':
+	    self.terminate_exp(pid, eid)
+
+	return self.make_null_experiment(pid, eid, tmpdir, gid, ns_data)
+
+    def get_assignments(self, pid, eid, user, tmpdir, ports):
+	'''
+	Copy the output of the shared NAT risky experiment setup output to the
+	temporary directory and parse it for preliminary (and hopefully
+	permanent) port assignments.  That output is a pseudo-xmlrpc response.
+	'''
+	# No ports? Nothing to do here.
+	if len(ports) == 0:
+	    return True
+
+	remote_risk = os.path.join('/proj', pid, 'exp', eid, 'tbdata',
+		'%s.risk' % eid)
+	local_risk = os.path.join(tmpdir, 'risk')
+
+	try:
+	    self.scp_file_from(remote_risk, user, self.ops, local_risk)
+	    f = open(local_risk)
+	    params, method = xmlrpclib.loads(f.read())
+	    f.close()
+	    # params should be a single element tuple containing the "xmlrpc"
+	    # request as a dict
+	    if not isinstance(params, tuple):
+		self.log.error('misformatted risk file: params not a tuple')
+		return False
+	    params = params[0]
+
+	    if 'assignments' not in params:
+		self.log.error('misformatted risk file: No arguments in params')
+		return False
+
+	    for k, v in params['assignments'].items():
+		if not k.endswith('/22/tcp'):
+		    continue
+		# Get the portal name from the SSH port assignment
+		p = k[0:k.index('/22/tcp')]
+		if p in ports:
+		    ports[p]  = v
+	except Exception, e:
+	    self.log.error('Error getting assignments: %s' % e)
+	    return False
+	return True
+
+
+    def __call__(self, parent, eid, pid, user, connInfo, tmpdir, timeout=0,
+	    gid=None):
+	'''
+	Create an experiment containing only portals and read the ssh_ports
+	from it.  The shared NAT properly marshals DNS names.
+	'''
+
+	ports = { }
+
+	for c in connInfo:
+	    for p in c.get('parameter', []):
+		# Only set output parameters
+		if  p.get('type', '') != 'output':
+		    continue
+		name = p.get('name', '')
+		k = p.get('key', None)
+		if name == 'peer':
+		    if k is None or k.index('/') == -1:
+			self.log.debug('Bad key for peer: %s' % k)
+			return False
+
+		    portal_name = k[k.index('/')+1:]
+
+		    if parent.nat_portal:
+			value = parent.nat_portal
+		    elif k and k.index('/') != -1:
+			value = "%s.%s.%s%s" % \
+			    (portal_name, eid, pid, parent.domain)
+		    else:
+			self.log.error('Bad export request: %s' % p)
+			continue
+		    p['value'] = value
+		    ports[portal_name] = None
+		    self.log.debug('Assigning %s to %s' % (k, value))
+		    self.log.debug('Adding entry for %s to ports' % portal_name)
+		elif name == 'ssh_port':
+		    # not yet
+		    continue
+		else:
+		    self.log.error("Unknown export parameter: %s" % name)
+		    return False
+
+	if not self.make_portal_experiment(pid, eid, tmpdir, gid, ports):
+	    return False
+
+	if not self.get_assignments(pid, eid, user, tmpdir, ports):
+	    return False
+
+	# Through these again to set ssh_ports
+	for c in connInfo:
+	    for p in c.get('parameter', []):
+		# Only set output parameters
+		if  p.get('type', '') != 'output':
+		    continue
+		name = p.get('name', '')
+		k = p.get('key', None)
+		if name != 'ssh_port':
+		    continue
+		if k is None or k.index('/') == -1:
+		    self.log.debug('Bad key for ssh port: %s' % k)
+		    return False
+
+		# The portal name is the string following the slash in the key,
+		# with the trainling (5 character) -port removed.
+		portal_name = k[k.index('/')+1:-5]
+		if portal_name not in ports or ports[portal_name] is None:
+		    self.log.error('No port assigned to %s' % portal_name)
+		    return False
+		value = ports[portal_name]
+		p['value'] = value
+		self.log.debug('Assigning %s to %s' % (k, value))
+	
+	return True
+
+# These are all simple aliases to the main emulab_segment line.
+class stop_segment(emulab_segment.stop_segment):
+    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
+	    ops=None, cert=None):
+	emulab_segment.stop_segment.__init__(self, log=log, keyfile=keyfile,
+		debug=debug, boss=boss, ops=ops, cert=cert)
+
+class info_segment(emulab_segment.info_segment):
+    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
+	    ops=None, cert=None):
+	emulab_segment.info_segment.__init__(self, log=log, keyfile=keyfile,
+		debug=debug, boss=boss, ops=ops, cert=cert)
+
+class operation_segment(emulab_segment.operation_segment):
+    def __init__(self, log=None, keyfile=None, debug=False, boss=None, 
+	    ops=None, cert=None):
+	emulab_segment.operation_segment.__init__(self, log=log,
+		keyfile=keyfile, debug=debug, boss=boss, ops=ops, cert=cert)
