Added tests for double tagged subscribers.

Change-Id: If90a7d51500bfcbe7a9095ad06e9bf45e8372e8d
diff --git a/accton/accton_util.py b/accton/accton_util.py
index ac729a4..29cb64d 100755
--- a/accton/accton_util.py
+++ b/accton/accton_util.py
@@ -38,6 +38,7 @@
 VLAN_TABLE_FLAG_ONLY_STACKED=5
 VLAN_TABLE_FLAG_PRIORITY=6
 VLAN_TABLE_FLAG_ONLY_UNTAG_PRIORITY=7
+VLAN_TABLE_FLAG_ONLY_POP_VLAN=8
 
 PORT_FLOW_TABLE=0
 VLAN_FLOW_TABLE=10
@@ -52,6 +53,13 @@
 BRIDGE_FLOW_TABLE=50
 ACL_FLOW_TABLE=60
 
+EGRESS_VLAN_FLOW_TABLE=210
+EGRESS_VLAN_1_FLOW_TABLE=211
+EGRESS_MAINTENANCE_POINT_FLOW_TABLE=226
+EGRESS_DSCP_TABLE=230
+EGRESS_TPID_TABLE=235
+EGRESS_SOURCE_MAC_LEARNING_TABLE=254
+
 def convertIP4toStr(ip_addr):
     a=(ip_addr&0xff000000)>>24
     b=(ip_addr&0x00ff0000)>>16
@@ -336,8 +344,12 @@
     ctrl.message_send(request)
     return request
 
-def add_l3_unicast_group(ctrl, port, vlanid, id, src_mac, dst_mac, send_barrier=False):
-    group_id = encode_l2_interface_group_id(vlanid, port)
+def add_l3_unicast_group(ctrl, port, vlanid, id, src_mac, dst_mac, send_barrier=False, gid=None):
+
+    if (not gid):
+        group_id = encode_l2_interface_group_id(vlanid, port)
+    else:
+        group_id = gid
 
     action=[]
     if src_mac is not None:
@@ -708,8 +720,39 @@
         logging.info("Add vlan %d tagged packets on port %d and go to table 20" %( vlan_id, of_port))
         ctrl.message_send(request)
 
+
+def add_one_egress_vlan_table_flow(ctrl, of_port, match_vlan, inner_vlan, outer_vlan):
+
+    # used for translating single to double tagged packets only
+
+    match = ofp.match()
+    match.oxm_list.append(ofp.oxm.exp4ByteValue(ofp.oxm.OFDPA_EXP_TYPE_ACTSET_OUTPUT, of_port))
+    match.oxm_list.append(ofp.oxm.exp1ByteValue(ofp.oxm.OFDPA_EXP_TYPE_ALLOW_VLAN_TRANSLATION, 1))
+    match.oxm_list.append(ofp.oxm.vlan_vid_masked(0x1000+match_vlan,0x1fff))
+
+    actions=[]
+    actions.append(ofp.action.set_field(ofp.oxm.vlan_vid(0x1000+inner_vlan)))
+    actions.append(ofp.action.push_vlan(0x8100))
+    actions.append(ofp.action.set_field(ofp.oxm.vlan_vid(0x1000+outer_vlan)))
+
+    request = ofp.message.flow_add(
+            table_id=EGRESS_VLAN_FLOW_TABLE,
+            cookie=42,
+            match=match,
+            instructions=[
+                ofp.instruction.apply_actions(
+                     actions=actions
+                ),
+                ofp.instruction.goto_table(EGRESS_DSCP_TABLE)
+            ],
+            priority=0)
+
+    ctrl.message_send(request)
+
+    return
+
 def add_one_vlan_table_flow(ctrl, of_port, out_vlan_id=1, vlan_id=1, vrf=0, flag=VLAN_TABLE_FLAG_ONLY_BOTH, send_barrier=False):
-    # table 10: vlan
+
     # goto to table 20
     if (flag == VLAN_TABLE_FLAG_ONLY_TAG) or (flag == VLAN_TABLE_FLAG_ONLY_BOTH) or (flag == 4):
         match = ofp.match()
@@ -737,6 +780,7 @@
         ctrl.message_send(request)
 
     if (flag == VLAN_TABLE_FLAG_ONLY_UNTAG) or (flag == VLAN_TABLE_FLAG_ONLY_BOTH):
+
         match = ofp.match()
         match.oxm_list.append(ofp.oxm.in_port(of_port))
         match.oxm_list.append(ofp.oxm.vlan_vid_masked(0, 0x1fff))
@@ -745,6 +789,7 @@
         if vrf!=0:
             actions.append(ofp.action.set_field(ofp.oxm.exp2ByteValue(exp_type=1, value=vrf)))
 
+        # actions.append(ofp.action.push_vlan(0x8100))
         actions.append(ofp.action.set_field(ofp.oxm.vlan_vid(0x1000+vlan_id)))
 
         request = ofp.message.flow_add(
@@ -796,8 +841,9 @@
         match.oxm_list.append(ofp.oxm.vlan_vid_masked(0x1000+vlan_id,0x1fff))
 
         actions=[]
-        actions.append(ofp.action.set_field(ofp.oxm.exp2ByteValue(exp_type=ofp.oxm.OFDPA_EXP_TYPE_OVID, value=0x1000+vlan_id)))
+        # actions.append(ofp.action.set_field(ofp.oxm.exp2ByteValue(exp_type=ofp.oxm.OFDPA_EXP_TYPE_OVID, value=0x1000+vlan_id)))
         actions.append(ofp.action.pop_vlan())
+        actions.append(ofp.action.set_field(ofp.oxm.exp2ByteValue(exp_type=ofp.oxm.OFDPA_EXP_TYPE_OVID, value=0x1000+vlan_id)))
 
         request = ofp.message.flow_add(
             table_id=10,
@@ -908,6 +954,7 @@
     return request
 
 def add_one_vlan_1_table_flow(ctrl, of_port, new_outer_vlan_id=-1, outer_vlan_id=1, inner_vlan_id=1, flag=VLAN_TABLE_FLAG_ONLY_TAG, send_barrier=False):
+
     # table 11: vlan 1 table
     # goto to table 20
     if flag == VLAN_TABLE_FLAG_ONLY_TAG:
@@ -938,6 +985,7 @@
         ctrl.message_send(request)
 
     if flag == VLAN_TABLE_FLAG_ONLY_UNTAG:
+
         match = ofp.match()
         match.oxm_list.append(ofp.oxm.in_port(of_port))
         match.oxm_list.append(ofp.oxm.vlan_vid_masked(0x1000+inner_vlan_id,0x1fff))
@@ -958,6 +1006,33 @@
         logging.info("Add vlan 1 double tagged %d-%d packets on port %d and go to table %d" %( outer_vlan_id, inner_vlan_id, of_port, TERMINATION_FLOW_TABLE))
         ctrl.message_send(request)
 
+    if flag == VLAN_TABLE_FLAG_ONLY_POP_VLAN:
+
+        print("INSTALLIN IN TABLE 11!")
+
+        match = ofp.match()
+        match.oxm_list.append(ofp.oxm.in_port(of_port))
+        match.oxm_list.append(ofp.oxm.vlan_vid_masked(0x1000+inner_vlan_id,0x1fff))
+        match.oxm_list.append(ofp.oxm.exp2ByteValue(ofp.oxm.OFDPA_EXP_TYPE_OVID, 0x1000+outer_vlan_id))
+
+        actions=[]
+        actions.append(ofp.action.pop_vlan())
+
+        request = ofp.message.flow_add(
+            table_id=11,
+            cookie=42,
+            match=match,
+            instructions=[
+                ofp.instruction.apply_actions(
+                     actions=actions
+                ),
+                ofp.instruction.goto_table(TERMINATION_FLOW_TABLE)
+            ],
+            priority=0)
+        logging.info("Add vlan 1 double tagged %d-%d packets on port %d and go to table %d" %( outer_vlan_id, inner_vlan_id, of_port, TERMINATION_FLOW_TABLE))
+        ctrl.message_send(request)
+
+
     if send_barrier:
         do_barrier(ctrl)
 
diff --git a/ofdpa/flows.py b/ofdpa/flows.py
index 6ca721b..28261d9 100755
--- a/ofdpa/flows.py
+++ b/ofdpa/flows.py
@@ -2508,4 +2508,447 @@
             delete_all_flows( self.controller )
             delete_all_groups( self.controller )
 
+class DoubleToUntagged( base_tests.SimpleDataPlane ):
+    """
+         Verify MPLS IP VPN Initiation from /24 rule using ECMP
+         where we receive double tagged packets and output untagged
+         packets.
+
+         Each double tagged packet represents a subscriber where Outer tag is pon
+         and inner tag is the subrscriber tag.
+    """
+
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc ]
+            input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+            input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+            input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+            output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+            output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+            dip = 0xc0a80001
+            ports = config[ "port_map" ].keys( )
+
+            inner_vlan = 66
+            outer_vlan = 77
+            id = 10
+            mpls_label = 152
+
+            port = ports[0]
+            out_port = ports[1]
+
+            # add l2 interface group
+            l2_gid, l2_msg = add_one_l2_interface_group( self.controller, out_port, inner_vlan, False, True )
+
+            # add MPLS interface group
+            mpls_gid, mpls_msg = add_mpls_intf_group( self.controller, l2_gid, output_dst_mac, input_dst_mac,
+                    inner_vlan, id )
+
+            # add MPLS L3 VPN group
+            mpls_label_gid, mpls_label_msg = add_mpls_label_group( self.controller,
+                    subtype=OFDPA_MPLS_GROUP_SUBTYPE_L3_VPN_LABEL, index=id, ref_gid=mpls_gid,
+                    push_mpls_header=True, set_mpls_label=mpls_label, set_bos=1, set_ttl=32 )
+            ecmp_msg = add_l3_ecmp_group( self.controller, id, [ mpls_label_gid ] )
+
+            do_barrier( self.controller )
+
+            # add vlan flow table
+            add_one_vlan_table_flow( self.controller, port, 1, outer_vlan, vrf=0,
+                    flag=VLAN_TABLE_FLAG_ONLY_STACKED )
+
+            add_one_vlan_1_table_flow( self.controller, port, outer_vlan_id=outer_vlan, inner_vlan_id=inner_vlan,
+                    flag=VLAN_TABLE_FLAG_ONLY_UNTAG )
+
+            # add termination flow
+            if config["switch_type"] == "qmx":
+                add_termination_flow( self.controller, 0, 0x0800, input_dst_mac, inner_vlan )
+            else:
+                add_termination_flow( self.controller, port, 0x0800, input_dst_mac, inner_vlan )
+
+            # add_unicast_routing_flow(self.controller, 0x0800, dst_ip, 0, mpls_label_gid, vrf=2)
+            add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffff00, ecmp_msg.group_id,
+                    vrf=0 )
+            Groups._put( l2_gid )
+            Groups._put( mpls_gid )
+            Groups._put( mpls_label_gid )
+            Groups._put( ecmp_msg.group_id )
+
+            do_barrier( self.controller )
+
+            ip_src = '192.168.5.5'
+            ip_dst = '192.168.0.5'
+            parsed_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=True, vlan_vid=inner_vlan, outer_vlan=outer_vlan,
+                    eth_dst=input_dst_mac_str, eth_src=input_src_mac_str, ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+            pkt = str( parsed_pkt )
+
+            # print("Expected %s" % format_packet(pkt))
+
+            self.dataplane.send( port, pkt )
+
+            # build expect packet
+            label = (mpls_label, 0, 1, 32)
+            exp_pkt = mpls_packet( pktlen=96, dl_vlan_enable=False, ip_ttl=63,
+                    ip_src=ip_src, ip_dst=ip_dst, eth_dst=output_dst_mac_str, eth_src=input_dst_mac_str,
+                    label=[ label ] )
+            pkt = str( exp_pkt )
+            verify_packet( self, pkt, out_port )
+            verify_no_other_packets( self )
+
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class DoubleToUntaggedMultipleSubscribers( base_tests.SimpleDataPlane ):
+    """
+         Verify MPLS IP VPN Initiation from /24 rule using ECMP
+         where we receive double tagged packets and output untagged
+         packets.
+
+         Each double tagged packet represents a subscriber where Outer tag is pon
+         and inner tag is the subrscriber tag.
+    """
+
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            # each entry represents a subscriber [id, ip in hex, inner_vlan, outer_vlan, ip in dot form]
+            subscriber_info = [ [10, 0xc0a80001, 10, 100, "192.168.0.1"],
+                                [20, 0xc0a80002, 10, 101, "192.168.0.2"],
+                                [30, 0xc0a80003, 11, 100, "192.168.0.3"],
+                                [40, 0xc0a80004, 11, 101, "192.168.0.4"]]
+
+            print("")
+
+            for sub_info in subscriber_info:
+
+                print("Initializing rules for subscriber with id {0}".format(sub_info[0]))
+
+                input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, sub_info[0] ]
+                input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+                input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+                input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+                output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+                output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+                dip = sub_info[1]
+                ports = config[ "port_map" ].keys( )
+
+                inner_vlan = sub_info[2]
+                outer_vlan = sub_info[3]
+                id = 10
+                mpls_label = 152
+
+                port = ports[0]
+                out_port = ports[1]
+
+                # add l2 interface group
+                l2_gid, l2_msg = add_one_l2_interface_group( self.controller, out_port, inner_vlan, False, True )
+
+                # add MPLS interface group
+                mpls_gid, mpls_msg = add_mpls_intf_group( self.controller, l2_gid, output_dst_mac, input_dst_mac,
+                        inner_vlan, id )
+
+                # add MPLS L3 VPN group
+                mpls_label_gid, mpls_label_msg = add_mpls_label_group( self.controller,
+                        subtype=OFDPA_MPLS_GROUP_SUBTYPE_L3_VPN_LABEL, index=id, ref_gid=mpls_gid,
+                        push_mpls_header=True, set_mpls_label=mpls_label, set_bos=1, set_ttl=32 )
+                ecmp_msg = add_l3_ecmp_group( self.controller, id, [ mpls_label_gid ] )
+
+                do_barrier( self.controller )
+
+                # add vlan flow table
+                add_one_vlan_table_flow( self.controller, port, 1, outer_vlan, vrf=0,
+                        flag=VLAN_TABLE_FLAG_ONLY_STACKED )
+
+                add_one_vlan_1_table_flow( self.controller, port, outer_vlan_id=outer_vlan, inner_vlan_id=inner_vlan,
+                        flag=VLAN_TABLE_FLAG_ONLY_UNTAG )
+
+                # add termination flow
+                if config["switch_type"] == "qmx":
+                    add_termination_flow( self.controller, 0, 0x0800, input_dst_mac, inner_vlan )
+                else:
+                    add_termination_flow( self.controller, port, 0x0800, input_dst_mac, inner_vlan )
+
+                # add_unicast_routing_flow(self.controller, 0x0800, dst_ip, 0, mpls_label_gid, vrf=2)
+                add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffff00, ecmp_msg.group_id,
+                        vrf=0 )
+                Groups._put( l2_gid )
+                Groups._put( mpls_gid )
+                Groups._put( mpls_label_gid )
+                Groups._put( ecmp_msg.group_id )
+
+                do_barrier( self.controller )
+
+            for sub_info in subscriber_info:
+
+                print("Sending packet for subscriber with id {0}".format(sub_info[0]))
+
+                input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, sub_info[0] ]
+                input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+                input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+                input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+                output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+                output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+                dip = sub_info[1]
+                ports = config[ "port_map" ].keys( )
+
+                inner_vlan = sub_info[2]
+                outer_vlan = sub_info[3]
+                id = 10
+                mpls_label = 152
+
+                port = ports[0]
+                out_port = ports[1]
+
+                ip_src = sub_info[4]
+                ip_dst = '192.168.0.{}'.format(sub_info[0])
+                parsed_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=True, vlan_vid=inner_vlan, outer_vlan=outer_vlan,
+                        eth_dst=input_dst_mac_str, eth_src=input_src_mac_str, ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+                pkt = str( parsed_pkt )
+
+                # print("Sent %s" % format_packet(pkt))
+
+                self.dataplane.send( port, pkt )
+
+                # build expect packet
+                label = (mpls_label, 0, 1, 32)
+                exp_pkt = mpls_packet( pktlen=96, dl_vlan_enable=False, ip_ttl=63,
+                        ip_src=ip_src, ip_dst=ip_dst, eth_dst=output_dst_mac_str, eth_src=input_dst_mac_str,
+                        label=[ label ] )
+                pkt = str( exp_pkt )
+                verify_packet( self, pkt, out_port )
+                verify_no_other_packets( self )
+
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+
+class UntaggedToDouble ( base_tests.SimpleDataPlane ):
+    """
+        Verify google senario where we need to go from
+        an untagged packet to a double tagged packet.
+
+        This is used for a single subscriber.
+    """
+
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc ]
+            input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+            input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+            input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+            output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+            output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+            dip = 0xc0a80001
+            ports = config[ "port_map" ].keys( )
+
+            inner_vlan = 66
+            outer_vlan = 77
+            id = 10
+            mpls_label = 152
+
+            port = ports[0]
+            out_port = ports[1]
+
+            # add l2 unfiltered interface group
+            l2_gid, l2_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+            l3_msg = add_l3_unicast_group( self.controller, out_port, vlanid=4094, id=id,
+                        src_mac=input_dst_mac, dst_mac=output_dst_mac, gid=l2_gid)
+
+            do_barrier( self.controller )
+
+            # add vlan flow table
+            add_one_vlan_table_flow( self.controller, port, 1, 4094,
+                    flag=VLAN_TABLE_FLAG_ONLY_BOTH )
+
+            # add termination flow
+            if config["switch_type"] == "qmx":
+                add_termination_flow( self.controller, 0, 0x0800, input_dst_mac, 4094 )
+            else:
+                add_termination_flow( self.controller, port, 0x0800, input_dst_mac, 4094 )
+
+            add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffffff, l3_msg.group_id,
+                    vrf=0 )
+
+            add_one_egress_vlan_table_flow( self.controller, out_port, 4094 , inner_vlan, outer_vlan)
+
+            Groups._put( l2_gid )
+            Groups._put( l3_msg.group_id )
+
+            do_barrier( self.controller )
+
+            ip_src = '192.168.5.5'
+            ip_dst = '192.168.0.1'
+            parsed_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=False,
+                    eth_dst=input_dst_mac_str, eth_src=input_src_mac_str, ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+            pkt = str( parsed_pkt )
+
+            # print("Input Packet %s" % format_packet(pkt))
+
+            self.dataplane.send( port, pkt )
+
+            # build expect packet
+            exp_pkt = simple_tcp_packet( pktlen=108, dl_vlan_enable=True, vlan_vid=inner_vlan, outer_vlan=outer_vlan,
+                    eth_dst=output_dst_mac_str, eth_src=input_dst_mac_str, ip_ttl=63, ip_src=ip_src, ip_dst=ip_dst )
+            pkt = str( exp_pkt )
+
+            # print("Expected Packet %s" % format_packet(pkt))
+
+            verify_packet( self, pkt, out_port )
+            verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
+
+class UntaggedToDoubleMultipleSubscribers ( base_tests.SimpleDataPlane ):
+    """
+        Verify google senario where we need to go from
+        an untagged packet to a double tagged packet.
+
+        This is used for multiple subscribers.
+
+        However, this solution does not scale, since we assign an internal vlan to each subscriber
+        used in L3 Unicast Group in order to differentiate between them in the Egress Vlan Table.
+    """
+
+    def runTest( self ):
+        Groups = Queue.LifoQueue( )
+        try:
+            if len( config[ "port_map" ] ) < 2:
+                logging.info( "Port count less than 2, can't run this case" )
+                return
+
+            # each entry represents a subscriber [id, ip in hex, inner_vlan, outer_vlan, ip in dot form, internal vlan]
+            subscriber_info = [[1, 0xc0a80001, 10, 100, "192.168.0.1", 4000],
+                               [2, 0xc0a80002, 10, 101, "192.168.0.2", 4001],
+                               [3, 0xc0a80003, 11, 100, "192.168.0.3", 4002],
+                               [4, 0xc0a80004, 11, 101, "192.168.0.4", 4003]]
+
+            print("")
+
+            for sub_info in subscriber_info:
+
+                print("Initializing rules for subscriber with id {0}".format(sub_info[0]))
+
+                input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, sub_info[0] ]
+                input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+                input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+                input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+                output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+                output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+                dip = sub_info[1]
+                ports = config[ "port_map" ].keys( )
+
+                inner_vlan = sub_info[2]
+                outer_vlan = sub_info[3]
+                internal_vlan = sub_info[5]
+                id = sub_info[0] + 10
+
+                port = ports[0]
+                out_port = ports[1]
+
+                # add l2 unfiltered interface group
+                l2_gid, l2_msg = add_one_l2_unfiltered_group( self.controller, out_port, True)
+
+                l3_msg = add_l3_unicast_group( self.controller, out_port, vlanid=internal_vlan, id=id,
+                            src_mac=input_dst_mac, dst_mac=output_dst_mac, gid=l2_gid)
+
+                do_barrier( self.controller )
+
+                # add vlan flow table
+                add_one_vlan_table_flow( self.controller, port, 1, 4094,
+                        flag=VLAN_TABLE_FLAG_ONLY_BOTH )
+
+                # add termination flow
+                if config["switch_type"] == "qmx":
+                    add_termination_flow( self.controller, 0, 0x0800, input_dst_mac, 4094 )
+                else:
+                    add_termination_flow( self.controller, port, 0x0800, input_dst_mac, 4094 )
+
+                add_unicast_routing_flow( self.controller, 0x0800, dip, 0xffffffff, l3_msg.group_id,
+                        vrf=0 )
+
+                add_one_egress_vlan_table_flow( self.controller, out_port, internal_vlan, inner_vlan, outer_vlan)
+
+                Groups._put( l2_gid )
+                Groups._put( l3_msg.group_id )
+                do_barrier( self.controller )
+
+            for sub_info in subscriber_info:
+
+                print("Sending packet for subscriber with id {0}".format(sub_info[0]))
+
+                input_src_mac = [ 0x00, 0x00, 0x00, 0xcc, 0xcc, sub_info[0] ]
+                input_src_mac_str = ':'.join( [ '%02X' % x for x in input_src_mac ] )
+
+                input_dst_mac = [ 0x00, 0x00, 0x00, 0x22, 0x22, 0x00 ]
+                input_dst_mac_str = ':'.join( [ '%02X' % x for x in input_dst_mac ] )
+
+                output_dst_mac = [ 0x00, 0x00, 0x00, 0x33, 0x33, 0x00 ]
+                output_dst_mac_str = ':'.join( [ '%02X' % x for x in output_dst_mac ] )
+
+                dip = sub_info[1]
+                ports = config[ "port_map" ].keys( )
+
+                inner_vlan = sub_info[2]
+                outer_vlan = sub_info[3]
+                internal_vlan = sub_info[5]
+
+                id = sub_info[0] + 10
+                ip_src = '192.168.5.5'
+                ip_dst = '192.168.0.{}'.format(sub_info[0])
+                parsed_pkt = simple_tcp_packet( pktlen=100, dl_vlan_enable=False,
+                        eth_dst=input_dst_mac_str, eth_src=input_src_mac_str, ip_ttl=64, ip_src=ip_src, ip_dst=ip_dst )
+                pkt = str( parsed_pkt )
+
+                # print("Input Packet %s" % format_packet(pkt))
+
+                self.dataplane.send( port, pkt )
+
+                # build expect packet
+                exp_pkt = simple_tcp_packet( pktlen=108, dl_vlan_enable=True, vlan_vid=inner_vlan, outer_vlan=outer_vlan,
+                        eth_dst=output_dst_mac_str, eth_src=input_dst_mac_str, ip_ttl=63, ip_src=ip_src, ip_dst=ip_dst )
+                pkt = str( exp_pkt )
+
+                # print("Expected Packet %s" % format_packet(pkt))
+
+                verify_packet( self, pkt, out_port )
+                verify_no_other_packets( self )
+        finally:
+            delete_all_flows( self.controller )
+            delete_groups( self.controller, Groups )
+            delete_all_groups( self.controller )
 
diff --git a/src/python/loxi/of13/action.py b/src/python/loxi/of13/action.py
index aa8a04d..a7d6697 100755
--- a/src/python/loxi/of13/action.py
+++ b/src/python/loxi/of13/action.py
@@ -2118,4 +2118,4 @@
             q.breakable()
         q.text('}')
 
-ofdpa.subtypes[OFDPA_ACT_OAM_SET_COUNTER_FIELDS] = ofdpa_oam_set_counter_field
\ No newline at end of file
+ofdpa.subtypes[OFDPA_ACT_OAM_SET_COUNTER_FIELDS] = ofdpa_oam_set_counter_field
diff --git a/src/python/loxi/of13/oxm.py b/src/python/loxi/of13/oxm.py
index 8f5448c..fa62add 100755
--- a/src/python/loxi/of13/oxm.py
+++ b/src/python/loxi/of13/oxm.py
@@ -6164,7 +6164,9 @@
 OFDPA_EXP_TYPE_RXFCL        =18
 OFDPA_EXP_TYPE_MPLS_TYPE    = 23
 OFDPA_EXP_TYPE_RX_TIMESAMP  =19
-OFDPA_EXP_TYPE_ACTSET_OUTPUT=42
+
+OFDPA_EXP_TYPE_ACTSET_OUTPUT=43
+OFDPA_EXP_TYPE_ALLOW_VLAN_TRANSLATION=24
 
 VPWS                        = 1
 TUNNEL_ID_BASE              = 0x10000
diff --git a/src/python/oftest/testutils.py b/src/python/oftest/testutils.py
index 7953bfe..57fe0b6 100755
--- a/src/python/oftest/testutils.py
+++ b/src/python/oftest/testutils.py
@@ -125,6 +125,7 @@
                       eth_src='00:06:07:08:09:0a',
                       dl_vlan_enable=False,
                       vlan_vid=0,
+                      outer_vlan=None,
                       vlan_pcp=0,
                       dl_vlan_cfi=0,
                       ip_src='192.168.0.1',
@@ -165,10 +166,13 @@
 
     # Note Dot1Q.id is really CFI
     if (dl_vlan_enable):
-        pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \
-            scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid)/ \
-            scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
-            scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
+        pkt = scapy.Ether( dst=eth_dst, src=eth_src )
+        if outer_vlan:
+            pkt = pkt/scapy.Dot1Q(prio=vlan_pcp, id=dl_vlan_cfi, vlan=outer_vlan)
+
+        pkt = pkt/scapy.Dot1Q( prio=vlan_pcp, id=dl_vlan_cfi, vlan=vlan_vid )/ \
+              scapy.IP(src=ip_src, dst=ip_dst, tos=ip_tos, ttl=ip_ttl, ihl=ip_ihl)/ \
+              scapy.TCP(sport=tcp_sport, dport=tcp_dport, flags=tcp_flags)
     else:
         if not ip_options:
             pkt = scapy.Ether(dst=eth_dst, src=eth_src)/ \