$OpenBSD: patch-mininet_openbsd_node_py,v 1.3 2017/12/07 06:33:40 akoshibe Exp $

Index: mininet/openbsd/node.py
--- mininet/openbsd/node.py.orig
+++ mininet/openbsd/node.py
@@ -0,0 +1,161 @@
+"""
+Node: rdomain(4) based node. This is somewhat more similar to Linux's network
+namespace moreso than a jail since it creates a separate network address space
+only.
+
+Mininet 'hosts' are created by running shells within rdomains. Links are made of
+pair(4)s patched together.
+
+This is a collection of helpers that call the right commands to manipulate these
+components.
+"""
+import signal
+import re
+from os import killpg
+
+from subprocess import PIPE, Popen
+from mininet.basenode import BaseNode
+from mininet.log import debug, info, warn
+from mininet.util import quietRun
+
+from mininet.openbsd.util import moveIntf
+from mininet.openbsd.intf import Intf
+
+class Node( BaseNode ):
+    """A virtual network node that manipulates and tracks rdomains. Because of
+       the property of rdomains, an OpenBSD node will always come with at least
+       one pair interface if inNamespace=True."""
+
+    index=1     # rdomain ID, can only go to 255
+    builtins='. : [ alias bg break builtin cd command continue echo eval exec' \
+             ' exit export false fc fg getopts jobs kill let print pwd read' \
+             ' readonly return set shift suspend test times trap true typeset' \
+             ' ulimit umask unalias unset wait whence'
+
+    def __init__( self, name, inNamespace=True, **params ):
+        BaseNode.__init__( self, name, inNamespace, **params )
+        # No renaming, supply map of assigned interface names to real names
+        self.portNames = {}
+
+    def getShell( self, master, slave, mnopts=None ):
+        """
+        Starts a shell used by the node to run commands. If inNamespace=True,
+        a pair interface is created, assigned to an rdomain, and a shell is
+        exec'd in the rdomain.
+        """
+        execcmd = [ 'mnexec' ]
+        opts = '-cd' if mnopts is None else mnopts
+
+        if self.inNamespace:
+            # create the pair tied to an rdomain
+            self.pair, self.rdid = 'pair%d' % Intf.next(), Node.index
+            Node.index += 1
+            if Node.index == 256:
+                error( 'Exceeded supported number of hosts (255)' )
+                exit( 1 )
+            rcmd = [ 'ifconfig', self.pair, 'create', 'description',
+                     '"%s"' % self.name, 'rdomain', '%d' % self.rdid ]
+            Popen( rcmd, stdout=PIPE )
+            execcmd = [ 'route', '-T%d' % self.rdid, 'exec' ] + execcmd
+        else:
+            self.pair = None
+            self.rdid = None
+
+        # -s: pass $* to shell, and make process easy to find in ps. The prompt
+        # is set to sentinel chr( 127 ).
+        cmd = execcmd + [ opts, 'env', 'PS1=' + chr( 127 ), '/bin/ksh',
+                          '-is', 'mininet:' + self.name ]
+        return Popen( cmd, stdin=slave, stdout=slave, stderr=slave,
+                      close_fds=False )
+
+    def isShellBuiltin( self, cmd ):
+        "Return True if cmd is a builtin."
+        space = cmd.find( ' ' )
+        if space > 0:
+            cmd = cmd[ :space]
+        return cmd in Node.builtins
+
+    def mountPrivateDirs( self ):
+        "mount private directories"
+        # **Not applicable until further notice**
+        pass
+
+    def unmountPrivateDirs( self ):
+        "mount private directories -  overridden"
+        # **Not applicable until further notice**
+        pass
+
+    def terminate( self ):
+        """ Cleanup when node is killed.  """
+        #self.unmountPrivateDirs()
+        if self.rdid:
+            lo = 'lo%s' % self.rdid
+            Popen( [ 'ifconfig', self.pair, 'destroy' ] )
+            # cleanup loopbacks that are left
+            Popen( [ 'ifconfig', lo, 'rdomain', '0', 'destroy' ] )
+        if self.shell:
+            if self.shell.poll() is None:
+                killpg( self.shell.pid, signal.SIGHUP )
+        self.cleanup()
+
+    def popen( self, *args, **kwargs ):
+        """Return a Popen() object in our namespace
+           args: Popen() args, single list, or string
+           kwargs: Popen() keyword args"""
+        defaults = { 'stdout': PIPE, 'stderr': PIPE,
+                     'mncmd':
+                     [ 'route', '-T%d' % self.rdid, 'exec' ] if self.rdid else
+                     [ 'mnexec', '-d' ] }
+        defaults.update( kwargs )
+        if len( args ) == 1:
+            if isinstance( args[ 0 ], list ):
+                # popen([cmd, arg1, arg2...])
+                cmd = args[ 0 ]
+            elif isinstance( args[ 0 ], basestring ):
+                # popen("cmd arg1 arg2...")
+                cmd = args[ 0 ].split()
+            else:
+                raise Exception( 'popen() requires a string or list' )
+        elif len( args ) > 0:
+            # popen( cmd, arg1, arg2... )
+            cmd = list( args )
+        # Attach to our namespace  using mnexec -a
+        cmd = defaults.pop( 'mncmd' ) + cmd
+        # Shell requires a string, not a list!
+        if defaults.get( 'shell', False ):
+            cmd = ' '.join( cmd )
+        popen = self._popen( cmd, **defaults )
+        return popen
+
+    def sendInt( self, intr=chr( 3 ) ):
+        "Interrupt running command."
+        quietRun( 'kill -2 %s' % self.lastPid )
+
+    def setHostRoute( self, ip, intf ):
+        """Add route to host.
+           ip: IP address as dotted decimal
+           intf: string, interface name
+           intfs: interface map of names to Intf"""
+        # add stronger checks for interface lookup
+        cmd = 'route -T%s add -host %s %s'
+        quietRun( cmd % ( self.rdid, ip, self.intfs( intf ).IP() ) )
+
+    def setDefaultRoute( self, intf=None ):
+        """Set the default route to go through intf.
+           intf: Intf or {dev <intfname> via <gw-ip> ...}"""
+        # Note setParam won't call us if intf is none
+        if isinstance( intf, basestring ):
+            argv = intf.split(' ')
+            if 'via' not in argv[ 0 ]:
+                warn( '%s: setDefaultRoute takes a port name but we got: %s\n' %
+                      ( self.name, intf ) )
+                return
+            params = argv[ -1 ]
+        else:
+            params = intf.IP()
+        quietRun( 'route -T%d change default %s' % ( self.rdid, params ) )
+
+
+    def addIntf( self, intf, port=None, moveIntfFn=moveIntf ):
+        self.portNames[ intf.name ] = intf.realname
+        super( Node, self ).addIntf( intf, port, moveIntfFn )
