[SDFAB-861] Add a testcase to UP4 with PFCP agent

    - Due to only being able to add one application filter per ue
      session with the pfcpsim, created a new testcase to individually
      test the apllication filters from the params file plus the default
      application filter
    - Using a Mock SMF to send messages to the pfcp agent
    - Add a parameter to topo file to use mock_smf or p4rt cli to add
      flows
    - Modify ue ips to work around smf limitations
    - Modify teids for UL and DL to be different. This reduces
      differences between smf and p4rtcli generated flows

Change-Id: I1ba3ef43919dd375f5e5bf54e97f61c09c7323d9
diff --git a/TestON/docker/Dockerfile b/TestON/docker/Dockerfile
index a05f3a8..04450d6 100644
--- a/TestON/docker/Dockerfile
+++ b/TestON/docker/Dockerfile
@@ -34,7 +34,8 @@
     openssh-server \
     curl \
     supervisor \
-    git
+    git \
+    iproute2
 ADD requirements.txt /
 
 COPY --from=trex-builder /output /
diff --git a/TestON/drivers/common/cli/mocksmfdriver.py b/TestON/drivers/common/cli/mocksmfdriver.py
new file mode 100644
index 0000000..6ff9390
--- /dev/null
+++ b/TestON/drivers/common/cli/mocksmfdriver.py
@@ -0,0 +1,277 @@
+"""
+Copyright 2022 Open Networking Foundation (ONF)
+
+Please refer questions to either the onos test mailing list at <onos-test@onosproject.org>,
+the System Testing Plans and Results wiki page at <https://wiki.onosproject.org/x/voMg>,
+or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
+
+"""
+
+import pexpect
+import os
+from drivers.common.clidriver import CLI
+
+
+class MockSMFDriver( CLI ):
+    """
+    Runs commands using the mock smf program to send messages to a UPF.
+
+    """
+
+    def __init__( self ):
+        """
+        Initialize client
+        """
+        super( MockSMFDriver, self ).__init__()
+        self.name = None
+        self.handle = None
+        self.prompt = "\$"
+        self.mock_smf_path = None
+
+    def connect( self, **connectargs ):
+        """
+        Creates the ssh handle for the mock smf
+        """
+        try:
+            for key in connectargs:
+                vars( self )[ key ] = connectargs[ key ]
+            self.name = self.options.get( "name", "" )
+            self.mock_smf_path = self.options.get( "mock_smf_path", None )
+            try:
+                if os.getenv( str( self.ip_address ) ) is not None:
+                    self.ip_address = os.getenv( str( self.ip_address ) )
+                else:
+                    main.log.info( self.name + ": ip set to " + self.ip_address )
+            except KeyError:
+                main.log.info( self.name + ": Invalid host name," +
+                              "defaulting to 'localhost' instead" )
+                self.ip_address = 'localhost'
+            except Exception as e:
+                main.log.error( "Uncaught exception: " + str( e ) )
+
+            self.handle = super( MockSMFDriver, self ).connect(
+                user_name=self.user_name,
+                ip_address=self.ip_address,
+                port=None,
+                pwd=self.pwd )
+            if self.handle:
+                main.log.info( "Connection successful to the host " +
+                               self.user_name +
+                               "@" +
+                               self.ip_address )
+                self.handle.sendline( "" )
+                self.handle.expect( self.prompt )
+                return main.TRUE
+            else:
+                main.log.error( "Connection failed to " +
+                                self.user_name +
+                                "@" +
+                                self.ip_address )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def startSMF( self, pcap_file="mock_smf.pcap", timeout=10 ):
+        """
+        Start Mock SMF and connect to the given UPF address.
+        """
+        #TODO: add interface option?
+        try:
+            main.log.debug( self.name + ": Starting Mock SMF CLI" )
+            # TODO: Add pcap logging folder and other options
+            cmd = "pfcpsim &"
+            #if pcap_file:
+                # TODO: Start pcap separately for go based mock smf
+            if self.mock_smf_path:
+                self.handle.sendline( "cd " + self.mock_smf_path )
+                self.handle.expect( self.prompt )
+                main.log.debug( self.handle.before )
+            self.handle.sendline( cmd )
+            i = self.handle.expect( [ "command not found",
+                                      "unknown",
+                                      "password for",
+                                      self.prompt,
+                                      pexpect.TIMEOUT ], timeout )
+            #TODO refactor this
+            if i == 2:
+                main.log.debug( "%s: Sudo asking for password" % self.name )
+                self.handle.sendline( self.pwd if self.pwd else "jenkins"  )
+                j = self.handle.expect( [ "not found", self.prompt ] )
+                if j == 0:
+                    main.log.error( "%s: Error starting mock smf" % self.name )
+                    main.log.debug( self.handle.before + str( self.handle.after ) )
+                    main.cleanAndExit()
+            elif i == 3:
+                # Exit backgrounded pcfpsim, even if test aborts early
+                self.preDisconnect = self.stop
+            else:
+                main.log.error( "%s: Error starting mock smf" % self.name )
+                main.log.debug( self.handle.before + str( self.handle.after ) )
+                main.cleanAndExit()
+            return main.TRUE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def sendline( self, cmd, timeout=10 ):
+        """
+        Handles the cli output from the mock smf. Returns main.TRUE if no error, else main.FALSE
+        """
+        try:
+            main.log.debug( "%s: Sending %s" % ( self.name, cmd ) )
+            self.handle.sendline( "pfcpctl %s" % cmd )
+            i = self.handle.expect( [ "command not found",
+                                      "unknown",
+                                      "ERRO",
+                                      "FATA",
+                                      self.prompt,
+                                      pexpect.TIMEOUT ], timeout )
+            if i == 4:
+                return main.TRUE
+            else:
+                main.log.error( "%s: Error with mock smf cmd: %s" % ( self.name, cmd ) )
+                output = self.handle.before + str( self.handle.after )
+                if i < 3:
+                    # If not timeout, make sure we get rest of prompt from buffer
+                    self.handle.expect( [ self.prompt,  pexpect.TIMEOUT ], timeout )
+                    output += self.handle.before
+                main.log.debug( "%s:%s" % ( self.name, output ) )
+                return main.FALSE
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def configure( self, n3_addr, upf_addr, upf_port=None ):
+        """
+        Configure pfcpsim to connect to upf
+        """
+        try:
+            cmd = "service configure --n3-addr %s --remote-peer-addr %s%s" % (n3_addr,
+                    upf_addr,
+                    "" if not upf_port else ":%s" % upf_port)
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def associate( self ):
+        """
+        Setup PFCP Association
+        """
+        try:
+            cmd = "service associate"
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def disassociate( self ):
+        """
+        Teardown PFCP Association
+        """
+        try:
+            cmd = "service disassociate"
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def create( self, ue_pool, gnb_addr, session_count=1,
+                base_id=1, sdf=[] ):
+        """
+        Create PFCP Session(s)
+
+        Arguements:
+            - ue_pool: The IPv4 prefix from which UE addresses will be drawn
+            - gnb_addr: The IPv4 address of the eNodeB
+        Optional Arguments:
+            - session_count: The number of sessions for which UE flows should be
+                             created. Defaults to 1
+            - base_id: The first id to use for the IDs. Further IDs will be
+                       generated by incrementing. Defaults to 1
+            - sdf: The sdf filter string to pass to pfcpctl
+        """
+        try:
+            cmd = "session create --count %s --ue-pool %s --gnb-addr %s --baseID %s %s" % (
+                  session_count, ue_pool, gnb_addr, base_id,
+                  " ".join( sdf ) )
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def modify( self, ue_pool, gnb_addr, session_count=1,
+                base_id=1, buffering=False, notifycp=False ):
+        """
+        Modify PFCP Sessions(s)
+
+        Arguements:
+            - ue_pool: The IPv4 prefix from which UE addresses will be drawn
+            - gnb_addr: The IPv4 address of the eNodeB
+        Optional Arguments:
+            - session_count: The number of sessions for which UE flows should be
+                             created. Defaults to 1
+            - base_id: The first id to use for the IDs. Further IDs will be
+                       generated by incrementing. Defaults to 1
+            - buffering: If this argument is present, downlink FARs will have the
+                         buffering flag set to true. Defaults to False
+            - notifycp: If this argument is present, downlink FARs will have the notify
+                        CP flag set to true. Defaults to False
+        """
+        try:
+            cmd = "session modify --count %s --ue-pool %s --gnb-addr %s --baseID %s %s %s" % (
+                  session_count, ue_pool, gnb_addr, base_id,
+                  "--buffer" if buffering else "",
+                  "--notifycp" if notifycp else "" )
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def delete( self, session_count=1, base_id=1 ):
+        """
+        Delete PFPC Session(s)
+
+        Arguements:
+            - session_count: The number of sessions for which UE flows should be
+                             created. Defaults to 1
+            - base_id: The first id to use for the IDs. Further IDs will be
+                       generated by incrementing. Defaults to 1
+        """
+        try:
+            cmd = "session delete --count %s --baseID %s" % (
+                  session_count, base_id )
+            return self.sendline( cmd )
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
+    def stop( self ):
+        """
+        Exits Mock SMF
+        """
+        try:
+            self.handle.sendline( "fg" )
+            self.handle.send( "\x03" )
+            self.clearBuffer()
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+
diff --git a/TestON/drivers/common/cli/onosclusterdriver.py b/TestON/drivers/common/cli/onosclusterdriver.py
index d096d1b..e2fd28d 100755
--- a/TestON/drivers/common/cli/onosclusterdriver.py
+++ b/TestON/drivers/common/cli/onosclusterdriver.py
@@ -28,6 +28,7 @@
 or the System Testing Guide page at <https://wiki.onosproject.org/x/WYQg>
 
 """
+from distutils.util import strtobool
 import pexpect
 import os
 from drivers.common.clidriver import CLI
@@ -68,10 +69,13 @@
         if hasattr( self.p4rtUp4, name ):
             main.log.debug( "%s: Using UP4 driver's attribute for '%s'" % ( self.name, name ) )
             return getattr( self.p4rtUp4, name )
+        if hasattr( self.mock_smf, name ):
+            main.log.debug( "%s: Using mock smf driver's attribute for '%s'" % ( self.name, name ) )
+            return getattr( self.mock_smf, name )
         raise AttributeError( "Could not find the attribute %s in %r or it's component handles" % ( name, self ) )
 
     def __init__( self, name, ipAddress, CLI=None, REST=None, Bench=None, pos=None,
-                  userName=None, server=None, k8s=None, p4rtUp4=None, dockerPrompt=None ):
+                  userName=None, server=None, k8s=None, p4rtUp4=None, mock_smf=None, dockerPrompt=None ):
         # TODO: validate these arguments
         self.name = str( name )
         self.ipAddress = ipAddress
@@ -85,6 +89,7 @@
         self.server = server
         self.k8s = k8s
         self.p4rtUp4 = p4rtUp4
+        self.mock_smf = mock_smf
         self.dockerPrompt = dockerPrompt
 
 class OnosClusterDriver( CLI ):
@@ -106,6 +111,7 @@
         self.nodePass = None
         self.nodes = []
         self.up4Port = None
+        self.mock_smf = None
         super( OnosClusterDriver, self ).__init__()
 
     def connect( self, **connectargs ):
@@ -153,6 +159,8 @@
                 elif key == "up4_port":
                     # Defining up4_port triggers the creation of the P4RuntimeCliDriver component
                     self.up4Port = self.options[ key ]
+                elif key == "mock_smf":
+                    self.mock_smf = strtobool( self.options[ key ] )
 
             self.home = self.checkOptions( self.home, "~/onos" )
             self.karafUser = self.checkOptions( self.karafUser, self.user_name )
@@ -169,6 +177,7 @@
             self.maxNodes = int( self.checkOptions( self.maxNodes, 100 ) )
             self.kubeConfig = self.checkOptions( self.kubeConfig, None )
             self.up4Port = self.checkOptions( self.up4Port, None )
+            self.mock_smf = self.checkOptions( self.mock_smf, None )
 
             self.name = self.options[ 'name' ]
 
@@ -266,7 +275,7 @@
                             elif self.up4Port and port == int( self.up4Port ):
                                 node.p4rtUp4.p4rtPort = localPort
                         # Set kubeconfig for all components
-                        for shell in [ node.CLI, node.Bench, node.k8s, node.p4rtUp4  ]:
+                        for shell in [ node.CLI, node.Bench, node.k8s, node.p4rtUp4, node.mock_smf  ]:
                             if shell:
                                 shell.setEnv( "KUBECONFIG", value=kubectl.kubeConfig )
                         main.log.info( "Setting up port forward for pod %s: [ %s ]" % ( self.podNames[ index ], portsList ) )
@@ -529,6 +538,49 @@
             main.log.error( name + " component already exists!" )
             main.cleanAndExit()
 
+    def setMockSMFOptions( self, name, ipAddress ):
+        """
+        Parse the cluster options to create a mock smf component with the given name
+
+        Arguments:
+            name - The name of the P4RuntimeCLI component
+            ipAddress - The ip address of the ONOS instance
+        """
+        main.componentDictionary[name] = main.componentDictionary[self.name].copy()
+        main.componentDictionary[name]['type'] = "MockSMFDriver"
+        main.componentDictionary[name]['host'] = ipAddress
+        main.componentDictionary[name]['connect_order'] = str( int( main.componentDictionary[name]['connect_order'] ) + 1 )
+
+    def createMockSMFComponent( self, name, ipAddress ):
+        """
+        Creates a new mock smf component. This will be connected to the node
+        ONOS is running on.
+
+        Arguments:
+            name - The string of the name of this component. The new component
+                   will be assigned to main.<name> .
+                   In addition, main.<name>.name = str( name )
+            ipAddress - The ip address of the server
+        """
+        try:
+            # look to see if this component already exists
+            getattr( main, name )
+        except AttributeError:
+            # namespace is clear, creating component
+            self.setMockSMFOptions( name, ipAddress )
+            return main.componentInit( name )
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            main.cleanAndExit()
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            main.cleanAndExit()
+        else:
+            # namespace is not clear!
+            main.log.error( name + " component already exists!" )
+            main.cleanAndExit()
+
     def createComponents( self, prefix='', createServer=True ):
         """
         Creates a CLI and REST component for each nodes in the cluster
@@ -540,6 +592,7 @@
         serverPrefix = prefix + "server"
         k8sPrefix = prefix + "k8s"
         up4Prefix = prefix + "up4cl"
+        smfPrefix = prefix + "smf"
         for i in xrange( 1, self.maxNodes + 1 ):
             cliName = cliPrefix + str( i  )
             restName = restPrefix + str( i )
@@ -549,6 +602,8 @@
                 k8sName = k8sPrefix + str( i )
             if self.up4Port:
                 up4Name = up4Prefix + str( i )
+            if self.mock_smf:
+                smfName = smfPrefix + str( i )
 
             # Unfortunately this means we need to have a cell set beofre running TestON,
             # Even if it is just the entire possible cluster size
@@ -560,9 +615,11 @@
             server = self.createServerComponent( serverName, ip ) if createServer else None
             k8s = self.createServerComponent( k8sName, ip ) if self.kubeConfig else None
             p4rtUp4 = self.createP4rtCLIComponent( up4Name, ip ) if self.up4Port else None
+            smf = self.createMockSMFComponent( smfName, ip ) if self.mock_smf else None
+            self.mock_smf = smf
             if self.kubeConfig:
                 k8s.kubeConfig = self.kubeConfig
                 k8s.podName = None
             self.nodes.append( Controller( prefix + str( i ), ip, cli, rest, bench, i - 1,
                                            self.user_name, server=server, k8s=k8s,
-                                           p4rtUp4=p4rtUp4, dockerPrompt=self.dockerPrompt ) )
+                                           p4rtUp4=p4rtUp4, mock_smf=smf, dockerPrompt=self.dockerPrompt ) )
diff --git a/TestON/drivers/common/cli/p4runtimeclidriver.py b/TestON/drivers/common/cli/p4runtimeclidriver.py
index b44c2cb..9f371dc 100644
--- a/TestON/drivers/common/cli/p4runtimeclidriver.py
+++ b/TestON/drivers/common/cli/p4runtimeclidriver.py
@@ -125,7 +125,7 @@
             self.preDisconnect = self.stopP4RtClient
         except pexpect.TIMEOUT:
             main.log.exception(self.name + ": Command timed out")
-            return main.FALSE
+            main.cleanAndExit()
         except pexpect.EOF:
             main.log.exception(self.name + ": connection closed.")
             main.cleanAndExit()
diff --git a/TestON/drivers/common/clidriver.py b/TestON/drivers/common/clidriver.py
index 1e4d233..4f081ea 100644
--- a/TestON/drivers/common/clidriver.py
+++ b/TestON/drivers/common/clidriver.py
@@ -156,7 +156,7 @@
 
     def disconnect( self ):
         result = self.preDisconnect()
-        result = super( CLI, self ).disconnect( self )
+        result = super( CLI, self ).disconnect( )
         result = main.TRUE
 
     def Prompt( self ):
@@ -1220,6 +1220,49 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             return None
 
+    def kubectlCmd( self, cmd, kubeconfig=None, namespace=None ):
+        """
+        Run an arbitrary command using kubectl
+        Arguments:
+        - cmd: Command string to send to kubectl
+        Optional Arguments:
+        - kubeconfig: The path to a kubeconfig file
+        - namespace: The namespace to search in
+        Returns a string of the node name or None
+        """
+        try:
+            self.handle.sendline( "" )
+            self.handle.expect( self.prompt )
+            main.log.debug( self.handle.before + self.handle.after )
+            cmdStr = "kubectl %s %s %s" % (
+                        "--kubeconfig %s" % kubeconfig if kubeconfig else "",
+                        "-n %s" % namespace if namespace else "",
+                        cmd )
+            main.log.info( self.name + ": sending: " + repr( cmdStr ) )
+            self.handle.sendline( cmdStr )
+            i = self.handle.expect( [ "not found", "error", "The connection to the server", self.prompt ] )
+            if i == 3:
+                output = self.handle.before
+                main.log.debug( self.name + ": " + output )
+                output = output.splitlines()
+                main.log.warn( output )
+                return output[1] if len( output ) == 3 else None
+            else:
+                main.log.error( self.name + ": Error executing command" )
+                main.log.debug( self.name + ": " + self.handle.before + str( self.handle.after ) )
+                return None
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return None
+        except pexpect.TIMEOUT:
+            main.log.exception( self.name + ": TIMEOUT exception found" )
+            main.log.error( self.name + ":    " + self.handle.before )
+            return None
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return None
+
     def sternLogs( self, podString, dstPath, kubeconfig=None, namespace=None, since='1h', wait=60 ):
         """
         Use stern to get the logs from a pod
@@ -1425,7 +1468,7 @@
         except pexpect.TIMEOUT:
             main.log.exception( self.name + ": TIMEOUT exception found" )
             main.log.error( self.name + ":    " + self.handle.before )
-            return main.FALSE
+            return self.checkPortForward( podName, portsList, kubeconfig, namespace )
         except Exception:
             main.log.exception( self.name + ": Uncaught exception!" )
             return main.FALSE
@@ -1459,6 +1502,7 @@
                 main.log.warn( "%s: port-forwarding session to %s closed, attempting to reestablish." % ( self.name, podName ) )
                 return self.kubectlPortForward( podName, portsList, kubeconfig, namespace )
             elif i == 1:
+                main.log.debug( "%s: We seem to still be in port-forwarding session" % self.name )
                 # Still in a command, port-forward is probably still active
                 return main.TRUE
         except pexpect.EOF:
@@ -1665,6 +1709,58 @@
             main.log.exception( self.name + ": Uncaught exception!" )
             return main.FALSE
 
+    def kubectlGetServiceIP( self, serviceName, kubeconfig=None, namespace=None, timeout=240 ):
+        try:
+            cmdStr = "kubectl %s %s get service %s " \
+                     "--output=jsonpath='{.spec.clusterIP}{\"\\n\"}'" % (
+                "--kubeconfig %s" % kubeconfig if kubeconfig else "",
+                "-n %s" % namespace if namespace else "",
+                serviceName )
+            main.log.info( self.name + ": sending: " + repr( cmdStr ) )
+            self.handle.sendline( cmdStr )
+            self.handle.expect( self.prompt, timeout=timeout )
+            output = self.handle.before
+            clusterIP = output.splitlines()
+            main.log.debug( repr( clusterIP ) )
+            return clusterIP[-2]
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return None
+        except pexpect.TIMEOUT:
+            main.log.exception( self.name + ": TIMEOUT exception found" )
+            main.log.error( self.name + ":    " + self.handle.before )
+            return None
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return None
+
+    def kubectlGetNodePort( self, serviceName, kubeconfig=None, namespace=None, timeout=240 ):
+        try:
+            cmdStr = "kubectl %s %s get service %s " \
+                     "--output=jsonpath='{.spec.ports[*].nodePort}{\"\\n\"}'" % (
+                "--kubeconfig %s" % kubeconfig if kubeconfig else "",
+                "-n %s" % namespace if namespace else "",
+                serviceName )
+            main.log.info( self.name + ": sending: " + repr( cmdStr ) )
+            self.handle.sendline( cmdStr )
+            self.handle.expect( self.prompt, timeout=timeout )
+            output = self.handle.before
+            clusterIP = output.splitlines()
+            main.log.debug( repr( clusterIP ) )
+            return clusterIP[-2]
+        except pexpect.EOF:
+            main.log.error( self.name + ": EOF exception found" )
+            main.log.error( self.name + ":     " + self.handle.before )
+            return None
+        except pexpect.TIMEOUT:
+            main.log.exception( self.name + ": TIMEOUT exception found" )
+            main.log.error( self.name + ":    " + self.handle.before )
+            return None
+        except Exception:
+            main.log.exception( self.name + ": Uncaught exception!" )
+            return None
+
     def clearBuffer(self):
         i = 0
         response = ''
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
index e1f522c..184de59 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.params
@@ -1,5 +1,5 @@
 <PARAMS>
-    <testcases>1,2,3,4,5</testcases>
+    <testcases>1,2,3,4,5,11</testcases>
 
     <GRAPH>
         <nodeCluster>pairedleaves</nodeCluster>
@@ -18,6 +18,7 @@
 
     <UP4>
         <pdn_host>MgmtServer</pdn_host>
+        <ue_range>10.240.0.0/16</ue_range>
         <enodebs>
             <enodeb_1>
                 <host>Compute3</host>
@@ -36,24 +37,24 @@
         <slice_id>1</slice_id>
         <ues>
             <ue1>
-                <ue_address>10.240.0.1</ue_address>
-                <teid>100</teid>
+                <ue_address>10.240.0.10</ue_address>
+                <teid>10</teid>
                 <up_id>10</up_id>
                 <down_id>11</down_id>
                 <tc>0</tc>
                 <five_g>False</five_g>
             </ue1>
             <ue2>
-                <ue_address>10.240.0.2</ue_address>
-                <teid>200</teid>
+                <ue_address>10.240.0.20</ue_address>
+                <teid>20</teid>
                 <up_id>20</up_id>
                 <down_id>21</down_id>
                 <tc>0</tc>
                 <five_g>False</five_g>
             </ue2>
             <ue3>
-                <ue_address>10.240.0.3</ue_address>
-                <teid>250</teid>
+                <ue_address>10.240.0.30</ue_address>
+                <teid>30</teid>
                 <up_id>30</up_id>
                 <down_id>31</down_id>
                 <tc>0</tc>
@@ -67,7 +68,7 @@
                 <!-- MgmtServer -->
                 <ip_prefix>10.32.11.1/32</ip_prefix>
                 <ip_proto>17</ip_proto>
-                <port_range>80..80</port_range>
+                <port_range>80..400</port_range>
                 <priority>20</priority>
                 <action>allow</action>
             </allowPort>
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
index 1b4b88e..88035d7 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.py
@@ -32,10 +32,16 @@
 
         main.step("Start scapy and p4rt client")
         # Use the first available ONOS instance CLI
-        onos_cli = main.Cluster.active(0).CLI
+        node = main.Cluster.active(0)
+        onos_cli = node.CLI
+        kubeconfig = node.k8s.kubeConfig
+        namespace = main.params['kubernetes']['namespace']
         up4 = UP4()
-        # Get the P4RT client connected to UP4 in the first available ONOS instance
-        up4.setup(main.Cluster.active(0).p4rtUp4)
+        nodeIP = node.Bench.kubectlCmd("get node %s --output=jsonpath='{.metadata.annotations.rke\.cattle\.io/external-ip}{\"\\n\"}'" % node.Bench.kubectlGetPodNode("pfcp-agent-0", kubeconfig, namespace))
+        # Connect P4RT and the mock smf if configured
+        up4.setup(node.p4rtUp4, mock_smf=node.mock_smf,
+                  pfcpAddress=nodeIP,
+                  pfcpPort=node.Bench.kubectlGetNodePort("pfcp-agent", kubeconfig, namespace))
 
         main.step("Program and Verify UPF entities via UP4")
         up4.attachUes()
@@ -697,3 +703,80 @@
         # Teardown
         run.cleanup(main)
 
+    def CASE11(self, main):
+        main.case("Fabric UPF traffic terminated in the fabric using pfcpsim to generate UE sessions")
+        """
+        Program UPF entities for UEs
+        Verify UPF entities
+        Generate traffic from UE to PDN
+        Verify traffic received from PDN
+        Generate traffic from PDN to UE
+        Verify traffic received from UE
+        Remove UPF entities for UEs
+        Verify removed UPF entities
+        """
+        try:
+            from tests.USECASE.SegmentRouting.dependencies.up4 import UP4
+            from tests.USECASE.SegmentRouting.dependencies.Testcaselib import \
+                Testcaselib as run
+        except ImportError as e:
+            main.log.error("Import not found. Exiting the test")
+            main.log.error(e)
+            main.cleanAndExit()
+        n_switches = int(main.params["TOPO"]["switchNum"])
+
+        run.initTest(main)
+        main.log.info(main.Cluster.numCtrls)
+        main.Cluster.setRunningNode(3)
+        run.installOnos(main, skipPackage=True, cliSleep=5)
+
+        main.step("Start scapy and p4rt client")
+        # Use the first available ONOS instance CLI
+        node = main.Cluster.active(0)
+        onos_cli = node.CLI
+        kubeconfig = node.k8s.kubeConfig
+        namespace = main.params['kubernetes']['namespace']
+
+        # Use pfcpctl instead of pr4untime to add ue sessions
+        smf = main.ONOScell.createMockSMFComponent( "pfcpsim", node.ip_address )
+        main.ONOScell.mock_smf = True
+        node.mock_smf = smf
+
+        up4 = UP4()
+        nodeIP = node.Bench.kubectlCmd("get node %s --output=jsonpath='{.metadata.annotations.rke\.cattle\.io/external-ip}{\"\\n\"}'" % node.Bench.kubectlGetPodNode("pfcp-agent-0", kubeconfig, namespace))
+
+        # Connect P4RT and the mock smf if configured
+        up4.setup(node.p4rtUp4, mock_smf=node.mock_smf,
+                  pfcpAddress=nodeIP,
+                  pfcpPort=node.Bench.kubectlGetNodePort("pfcp-agent", kubeconfig, namespace))
+        app_filters = up4.app_filters
+        app_filters.update( up4.DEFAULT_APP)
+        for name, app in app_filters.iteritems():
+            up4.app_filters = { name: app }
+            main.step("Program and Verify UPF entities via pfcp for app: %s" % name)
+            up4.attachUes()
+            up4.verifyUp4Flow(onos_cli)
+
+            # ------- Test Upstream traffic (enb->pdn)
+            for app_filter_name in up4.app_filters:
+                main.step("Test upstream traffic %s" % app_filter_name)
+                up4.testUpstreamTraffic(app_filter_name=app_filter_name)
+
+            # ------- Test Downstream traffic (pdn->enb)
+            for app_filter_name in up4.app_filters:
+                main.step("Test downstream traffic %s" % app_filter_name)
+                up4.testDownstreamTraffic(app_filter_name=app_filter_name)
+
+            main.step("Remove and Verify UPF entities via pfcp for app: %s" % name)
+            up4.detachUes()
+            up4.verifyNoUesFlow(onos_cli)
+
+            # hack to make sure next iteration uses different seids
+            for ue_name in up4.emulated_ues:
+                up4.emulated_ues[ue_name]["seid"] = None
+        main.step("Stop scapy and p4rt client")
+        up4.teardown()
+
+        run.cleanup(main)
+
+
diff --git a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
index 731c259..56bfdbb 100644
--- a/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
+++ b/TestON/tests/USECASE/SegmentRouting/UP4/UP4.topo
@@ -24,6 +24,7 @@
                 <onos_home>~/onos/</onos_home>  # defines where onos home is on the target cell machine. Defaults to entry in "home" if empty.
                 <nodes> 3 </nodes>  # number of nodes in the cluster
                 <up4_port>51001</up4_port> # Port where the UP4 P4Runtime server is listening
+                <mock_smf>False</mock_smf>
             </COMPONENTS>
         </ONOScell>
 
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
index 45b01b7..73ffa98 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/Testcaselib.py
@@ -571,7 +571,7 @@
             count = main.Cluster.active( 0 ).CLI.checkFlowCount()
         utilities.assertEquals(
                 expect=True,
-                actual=( count >= minFlowCount ),
+                actual=( int( count ) >= int( minFlowCount ) ),
                 onpass="Flow count looks correct; found %s, expecting at least %s" % ( count, minFlowCount ),
                 onfail="Flow count looks wrong; found %s, expecting at least %s" % ( count, minFlowCount ) )
 
@@ -1210,7 +1210,7 @@
         time.sleep( sleep )
 
     @staticmethod
-    def cleanup( main, copyKarafLog=True, removeHostComponent=False ):
+    def cleanup( main, copyKarafLog=False, removeHostComponent=False ):
         """
         Stop Onos-cluster.
         Stops Mininet
diff --git a/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
index 39621e2..82b9c78 100644
--- a/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
+++ b/TestON/tests/USECASE/SegmentRouting/dependencies/up4.py
@@ -1,5 +1,7 @@
 from distutils.util import strtobool
+import ipaddress as ip
 import copy
+import re
 
 FALSE = '0'
 TRUE = '1'
@@ -15,6 +17,7 @@
 GPDU_PORT = 2152
 
 DEFAULT_APP_ID = 0
+DEFAULT_APP_NAME = "Default"
 DEFAULT_SESSION_METER_IDX = 0
 DEFAULT_APP_METER_IDX = 0
 
@@ -69,23 +72,33 @@
         self.pdn_interface = None
         self.router_mac = None
         self.emulated_ues = {}
-        self.app_filters = {}
+        self.DEFAULT_APP = {DEFAULT_APP_NAME: {'ip_prefix': None, 'ip_proto': None, 'port_range': None,
+                            'app_id': DEFAULT_APP_ID, 'action': 'allow' } }
+        self.app_filters = self.DEFAULT_APP
         self.up4_client = None
+        self.mock_smf = None
         self.no_host = False
         self.slice_id = None
+        self.next_seid = 1
 
-    def setup(self, p4rt_client, no_host=False):
+    def setup(self, p4rt_client, mock_smf=None, no_host=False, pfcpAddress=None, pfcpPort=8805, app_filters=True):
         """
-        Set up P4RT and scapy on eNB and PDN hosts
+        Set up P4RT and scapy on eNB and PDN hosts. If mock_smf is not None, will also connect
+        to the pfcp agent using a mock smf and the test will use this to create ue sessions instead of P4RT
         :param p4rt_client: a P4RuntimeCliDriver component
+        :param mock_smf: The optional mocksmfdriver component to use
         :param no_host: True if you don't want to start scapy on the hosts
+        :param pfcpAddress: Address of the PFCP agent to connect to when using mock smf
+        :param pfcpPort: Port of the PFCP agent to connect to when using mock smf
+        :param app_filters: If True, will add app_filters defined in the params file, else use the default application filter
         :return:
         """
         self.s1u_address = main.params["UP4"]["s1u_address"]
         self.emulated_ues = main.params["UP4"]['ues']
-        self.app_filters = main.params["UP4"]['app_filters']
+        self.app_filters = main.params["UP4"]['app_filters'] if app_filters else self.DEFAULT_APP
         self.slice_id = main.params["UP4"]['slice_id']
         self.up4_client = p4rt_client
+        self.mock_smf = mock_smf
         self.no_host = no_host
 
         # Optional Parameters
@@ -106,7 +119,15 @@
             if app_filter.get('slice_id', None) is None:
                 app_filter['slice_id'] = self.slice_id
         # Start components
-        self.up4_client.startP4RtClient()
+        if self.up4_client:
+            self.up4_client.startP4RtClient()
+        if self.mock_smf:
+            if not pfcpAddress:
+                pfcpAddress = self.mock_smf.kubectlGetServiceIP( "pfcp-agent" )
+            self.mock_smf.startSMF()
+            self.mock_smf.configure(self.s1u_address, pfcpAddress, pfcpPort)
+            # TODO Start pcap on mock_smf host
+            self.mock_smf.associate()
         if not self.no_host:
             if self.enodebs is not None:
                 for enb in self.enodebs.values():
@@ -119,7 +140,11 @@
         #  them here
 
     def teardown(self):
-        self.up4_client.stopP4RtClient()
+        if self.up4_client:
+            self.up4_client.stopP4RtClient()
+        if self.mock_smf:
+            self.mock_smf.disassociate()
+            self.mock_smf.stop()
         if not self.no_host:
             if self.enodebs is not None:
                 for enb in self.enodebs.values():
@@ -128,18 +153,37 @@
                 self.pdn_host.stopScapy()
 
     def attachUes(self):
-        for app_filter in self.app_filters.values():
-            self.insertAppFilter(**app_filter)
-        for (name, ue) in self.emulated_ues.items():
-            ue = UP4.__sanitizeUeData(ue)
-            self.attachUe(name, **ue)
+        filter_desc = []
+        for app_name, app_filter in sorted(self.app_filters.items(), key=lambda (k, v): int(v.get('priority', 0))):
+            if self.mock_smf:
+                if app_name != DEFAULT_APP_NAME:
+                    filter_desc.append(self.__pfcpSDFString(**app_filter))
+                    self.app_filters[app_name]['app_id'] = None
+                    self.app_filters[app_name]['priority'] = None
+
+            else:
+                self.insertAppFilter(**app_filter)
+        for (ue_name, ue) in self.emulated_ues.items():
+            ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
+            if self.mock_smf:
+                if not ue.get("seid"):
+                    ue["seid"] = self.next_seid
+                    self.next_seid = self.next_seid + 1
+                    self.emulated_ues[ue_name]["seid"] = ue["seid"]
+                self.smfAttachUe(ue_name, sdf=filter_desc[-1:], **ue) # TODO: pass whole filter list once multi app support is fixed
+            else:
+                self.attachUe(ue_name, **ue)
 
     def detachUes(self):
-        for app_filter in self.app_filters.values():
-            self.removeAppFilter(**app_filter)
-        for (name, ue) in self.emulated_ues.items():
-            ue = UP4.__sanitizeUeData(ue)
-            self.detachUe(name, **ue)
+        if not self.mock_smf:
+            for app_filter in self.app_filters.values():
+                self.removeAppFilter(**app_filter)
+        for (ue_name, ue) in self.emulated_ues.items():
+            ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
+            if self.mock_smf:
+                self.smfDetachUe(ue_name, **ue)
+            else:
+                self.detachUe(ue_name, **ue)
 
     def __pickPortFromRange(self, range):
         if range is None or len(range) == 0:
@@ -157,9 +201,13 @@
         else:
             enodebs = [self.enodebs[enb] for enb in enb_names]
 
-        app_filter = self.app_filters[app_filter_name]
-        pdn_port = self.__pickPortFromRange(app_filter.get("port_range", None))
-        app_filter_should_drop = app_filter["action"] != "allow"
+        port_range = None
+        app_filter_should_drop = False
+        if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
+            app_filter = self.app_filters[app_filter_name]
+            port_range = app_filter.get("port_range", None)
+            app_filter_should_drop = app_filter["action"] != "allow"
+        pdn_port = self.__pickPortFromRange(port_range)
 
         pkt_filter_upstream = ""
         ues = []
@@ -239,9 +287,13 @@
         else:
             enodebs = [self.enodebs[enb] for enb in enb_names]
 
-        app_filter = self.app_filters[app_filter_name]
-        pdn_port = self.__pickPortFromRange(app_filter.get("port_range", None))
-        app_filter_should_drop = app_filter["action"] != "allow"
+        port_range = None
+        app_filter_should_drop = False
+        if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
+            app_filter = self.app_filters[app_filter_name]
+            port_range = app_filter.get("port_range", None)
+            app_filter_should_drop = app_filter["action"] != "allow"
+        pdn_port = self.__pickPortFromRange(port_range)
 
         pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
             GPDU_PORT, GPDU_PORT, self.s1u_address)
@@ -447,50 +499,56 @@
         fail = False
         for app_filter in self.app_filters.values():
             if not UP4.__defaultApp(**app_filter):
-                if applications.count(self.appFilterOnosString(**app_filter)) != 1:
+                if len(re.findall(self.appFilterOnosString(**app_filter), applications)) != 1:
                     failMsg.append(self.appFilterOnosString(**app_filter))
                     fail = True
         for (ue_name, ue) in self.emulated_ues.items():
-            if sessions.count(self.upUeSessionOnosString(**ue)) != 1:
+            if len(re.findall(self.upUeSessionOnosString(**ue), sessions)) != 1:
                 failMsg.append(self.upUeSessionOnosString(**ue))
                 fail = True
-            if sessions.count(self.downUeSessionOnosString(**ue)) != 1:
+            if len(re.findall(self.downUeSessionOnosString(**ue), sessions)) != 1:
                 failMsg.append(self.downUeSessionOnosString(**ue))
                 fail = True
             for app_filter in self.app_filters.values():
-                if terminations.count(self.upTerminationOnosString(app_filter=app_filter, **ue)) != 1:
+                if len(re.findall(self.upTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
                     failMsg.append(self.upTerminationOnosString(app_filter=app_filter, **ue))
                     fail = True
-                if terminations.count(self.downTerminationOnosString(app_filter=app_filter, **ue)) != 1:
+                if len(re.findall(self.downTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
                     failMsg.append(self.downTerminationOnosString(app_filter=app_filter, **ue))
                     fail = True
-            if tunn_peer.count(self.gtpTunnelPeerOnosString(ue_name, **ue)) != 1:
+            if len(re.findall(self.gtpTunnelPeerOnosString(ue_name, **ue), tunn_peer)) != 1:
                 failMsg.append(self.gtpTunnelPeerOnosString(ue_name, **ue))
                 fail = True
         return not fail
 
-    def appFilterOnosString(self, app_id, priority, slice_id=None, ip_proto=None, ip_prefix=None, port_range=None, **kwargs):
+    def appFilterOnosString(self, app_id=None, priority=None, slice_id=None, ip_proto=None, ip_prefix=None, port_range=None, **kwargs):
+        if app_id is None:
+            app_id = "\d+"
+        if priority is None:
+            priority = "\d+"
         if slice_id is None:
             slice_id = self.slice_id
         matches = []
         if ip_prefix:
             matches.append("ip_prefix=%s" % ip_prefix)
         if port_range:
-            matches.append("l4_port_range=[%s]" % port_range)
+            matches.append("l4_port_range=\[%s\]" % port_range)
         if ip_proto:
             matches.append("ip_proto=%s" % ip_proto)
         matches.append("slice_id=%s" % slice_id)
 
-        return "UpfApplication(priority=%s, Match(%s) -> Action(app_id=%s))" % (
+        return "UpfApplication\(priority=%s, Match\(%s\) -> Action\(app_id=%s\)\)" % (
             priority,
             ", ".join(matches),
             app_id
         )
 
     def upUeSessionOnosString(self, teid=None, teid_up=None, sess_meter_idx=DEFAULT_SESSION_METER_IDX, **kwargs):
-        if teid is not None:
+        if teid_up is None and teid is not None:
             teid_up = teid
-        return "UpfSessionUL(Match(tun_dst_addr={}, teid={}) -> Action(FWD, session_meter_idx={}))".format(
+        if sess_meter_idx is None:
+            sess_meter_idx = "\d+"
+        return "UpfSessionUL\(Match\(tun_dst_addr={}, teid={}\) -> Action\(FWD, session_meter_idx={}\)\)".format(
             self.s1u_address, teid_up, sess_meter_idx)
 
     def downUeSessionOnosString(self, ue_address, down_id=None,
@@ -498,19 +556,32 @@
                                 **kwargs):
         if down_id is not None:
             tunn_peer_id = down_id
-        return "UpfSessionDL(Match(ue_addr={}) -> Action(FWD,  tun_peer={}, session_meter_idx={}))".format(
+        if tunn_peer_id is None:
+            tunn_peer_id = "\d+"
+        if sess_meter_idx is None:
+            sess_meter_idx = "\d+"
+        return "UpfSessionDL\(Match\(ue_addr={}\) -> Action\(FWD,  tun_peer={}, session_meter_idx={}\)\)".format(
             ue_address, tunn_peer_id, sess_meter_idx)
 
     def upTerminationOnosString(self, ue_address, app_filter, up_id=None,
                                 ctr_id_up=None, tc=None, app_meter_idx=DEFAULT_APP_METER_IDX, **kwargs):
         if up_id is not None:
             ctr_id_up = up_id
-        if app_filter["action"] == "allow":
-            return "UpfTerminationUL(Match(ue_addr={}, app_id={}) -> Action(FWD, ctr_id={}, tc={}, app_meter_idx={}))".format(
-                ue_address, app_filter["app_id"], ctr_id_up, tc, app_meter_idx)
+        if ctr_id_up is None:
+            ctr_id_up = "\d+"
+        if tc is None or int(tc) == 0:
+            tc = "(?:0|null)"
+        if app_meter_idx is None:
+            app_meter_idx = "\d+"
+        app_id = app_filter["app_id"]
+        if app_id is None:
+            app_id = "\d+"
+        if app_filter["action"] == "deny":
+            return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, ctr_id={}, tc=null, app_meter_idx=0\)\)".format(
+                ue_address, app_id, ctr_id_up)
         else:
-            return "UpfTerminationUL(Match(ue_addr={}, app_id={}) -> Action(DROP, ctr_id={}, tc=null, app_meter_idx=0))".format(
-                ue_address, app_filter["app_id"], ctr_id_up)
+            return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, ctr_id={}, tc={}, app_meter_idx={}\)\)".format(
+                ue_address, app_id, ctr_id_up, tc, app_meter_idx)
 
     def downTerminationOnosString(self, ue_address, app_filter, teid=None,
                                   down_id=None, ctr_id_down=None, teid_down=None,
@@ -518,31 +589,43 @@
                                   **kwargs):
         if down_id is not None:
             ctr_id_down = down_id
-        if teid is not None:
+        if ctr_id_down is None:
+            ctr_id_down = "\d+"
+        if teid_down is None and teid is not None:
             teid_down = int(teid) + 1
-        if tc is None:
-            tc="null"
-        if app_filter["action"] == "allow":
-            return "UpfTerminationDL(Match(ue_addr={}, app_id={}) -> Action(FWD, teid={}, ctr_id={}, qfi={}, tc={}, app_meter_idx={}))".format(
-                ue_address, app_filter["app_id"], teid_down, ctr_id_down, tc, tc, app_meter_idx)
+        if tc is None or int(tc) == 0:
+            tc = "(?:0|null)"
+        if app_meter_idx is None:
+            app_meter_idx = "\d+"
+        app_id = app_filter["app_id"]
+        if app_id is None:
+            app_id = "\d+"
+        if app_filter["action"] == "deny":
+            return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, teid=null, ctr_id={}, qfi=null, tc=null, app_meter_idx=0\)\)".format(
+                ue_address, app_id, ctr_id_down)
         else:
-            return "UpfTerminationDL(Match(ue_addr={}, app_id={}) -> Action(DROP, teid=null, ctr_id={}, qfi=null, tc=null, app_meter_idx=0))".format(
-                ue_address, app_filter["app_id"], ctr_id_down)
+            return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, teid={}, ctr_id={}, qfi={}, tc={}, app_meter_idx={}\)\)".format(
+                ue_address, app_id, teid_down, ctr_id_down, tc, tc, app_meter_idx)
 
     def gtpTunnelPeerOnosString(self, ue_name, down_id=None, tunn_peer_id=None,
                                 **kwargs):
         if down_id is not None:
             tunn_peer_id = down_id
+        if tunn_peer_id is None:
+            tunn_peer_id = "\d+"
         enb_address = self.__getEnbAddress(ue_name)
-        return "UpfGtpTunnelPeer(tunn_peer_id={} -> src={}, dst={} src_port={})".format(
+        return "UpfGtpTunnelPeer\(tunn_peer_id={} -> src={}, dst={} src_port={}\)".format(
             tunn_peer_id, self.s1u_address, enb_address, GPDU_PORT)
 
     @staticmethod
-    def __sanitizeUeData(ue):
+    def __sanitizeUeData(ue, smf=False):
         if "five_g" in ue and type(ue["five_g"]) != bool:
             ue["five_g"] = bool(strtobool(ue["five_g"]))
         if "tc" in ue and ue["tc"] == "":
             ue["tc"] = 0
+        if smf:
+            ue["up_id"] = None
+            ue["down_id"] = None
         return ue
 
     def insertAppFilter(self, **kwargs):
@@ -553,12 +636,46 @@
         if not UP4.__defaultApp(**kwargs):
             self.__programAppFilter(op="clear", **kwargs)
 
+    def smfAttachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, sdf=[], **kwargs):
+        enb_addr = None
+        for enb_name, enb in self.enodebs.items():
+            if ue_name in enb["ues"]:
+                enb_addr = enb["enb_address"]
+                break
+        assert enb_addr, "Unknown eNodeB address"
+        ue_address = str(ip.ip_address(unicode(ue_address)) - 1)
+        base = seid
+
+        create = self.mock_smf.create(ue_pool="%s/%s" % (ue_address, prefix_len),
+                                      gnb_addr=enb_addr,
+                                      base_id=base,
+                                      sdf=sdf)
+        modify = self.mock_smf.modify(ue_pool="%s/%s" % (ue_address, prefix_len),
+                                      gnb_addr=enb_addr,
+                                      base_id=seid)
+        self.emulated_ues[ue_name]['seid'] = base
+        self.emulated_ues[ue_name]['teid'] = base
+        self.emulated_ues[ue_name]['sess_meter_idx'] = None
+        self.emulated_ues[ue_name]['app_meter_idx'] = None
+        return create and modify
+
+    def smfDetachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, **kwargs):
+        enb_addr = None
+        for enb_name, enb in self.enodebs.items():
+            if ue_name in enb["ues"]:
+                enb_addr = enb["enb_address"]
+                break
+        assert enb_addr, "Unknown eNodeB address"
+        ue_address = str( ip.ip_address( unicode( ue_address ) ) - 1 )
+
+        return self.mock_smf.delete( base_id=seid )
+
     def attachUe(self, ue_name, ue_address,
                  teid=None, up_id=None, down_id=None,
                  teid_up=None, teid_down=None,
                  ctr_id_up=None, ctr_id_down=None,
                  tunn_peer_id=None,
-                 tc=None, five_g=False):
+                 tc=None, five_g=False, **kwargs):
         self.__programUeRules(ue_name,
                               ue_address,
                               teid, up_id, down_id,
@@ -572,7 +689,7 @@
                  teid_up=None, teid_down=None,
                  ctr_id_up=None, ctr_id_down=None,
                  tunn_peer_id=None,
-                 tc=None, five_g=False):
+                 tc=None, five_g=False, **kwargs):
         self.__programUeRules(ue_name,
                               ue_address,
                               teid, up_id, down_id,
@@ -608,6 +725,26 @@
         elif op == "clear":
             self.__clear_entries(entries)
 
+    def __pfcpSDFString(self, ip_prefix=None, ip_proto=None, port_range=None,
+                        action="allow", **kwargs):
+        if ip_proto is None or str(ip_proto) == "255":
+            protoString = "ip"
+        elif str(ip_proto) == "6":
+            descList.append("tcp")
+        elif str(ip_proto) == "17":
+            protoString = "udp"
+        else:
+            #  TODO: is there a python library for this? Can we just pass the number?
+            raise NotImplemented
+        if port_range is None:
+            port_range = "any"
+        if ip_prefix is None:
+            ip_prefix = "any"
+        return "--app-filter '%s:%s:%s:%s'" % ( protoString,
+                                                ip_prefix,
+                                                port_range.replace("..","-"),
+                                                action )
+
     def __programUeRules(self, ue_name, ue_address,
                          teid=None, up_id=None, down_id=None,
                          teid_up=None, teid_down=None, ctr_id_up=None,