QoS recording and marking 53/11053/9
authorNeale Ranns <nranns@cisco.com>
Tue, 27 Feb 2018 11:45:38 +0000 (03:45 -0800)
committerDamjan Marion <dmarion.lists@gmail.com>
Fri, 16 Mar 2018 20:28:50 +0000 (20:28 +0000)
Change-Id: Ie5a50def4ec1e4a3b3404a8b6ab9ec248bc16744
Signed-off-by: Neale Ranns <nranns@cisco.com>
24 files changed:
src/plugins/nat/nat64.h
src/plugins/nat/nat66.h
src/vnet.am
src/vnet/buffer.c
src/vnet/buffer.h
src/vnet/ethernet/packet.h
src/vnet/ip/ip.h
src/vnet/ip/ip6_packet.h
src/vnet/ip/lookup.c
src/vnet/map/map.h
src/vnet/mpls/mpls_output.c
src/vnet/qos/qos.api [new file with mode: 0644]
src/vnet/qos/qos_api.c [new file with mode: 0644]
src/vnet/qos/qos_egress_map.c [new file with mode: 0644]
src/vnet/qos/qos_egress_map.h [new file with mode: 0644]
src/vnet/qos/qos_mark.c [new file with mode: 0644]
src/vnet/qos/qos_mark.h [new file with mode: 0644]
src/vnet/qos/qos_record.c [new file with mode: 0644]
src/vnet/qos/qos_record.h [new file with mode: 0644]
src/vnet/qos/qos_types.c [new file with mode: 0644]
src/vnet/qos/qos_types.h [new file with mode: 0644]
src/vnet/vnet_all_api_h.h
test/test_qos.py [new file with mode: 0644]
test/vpp_papi_provider.py

index e2334a5..addba7a 100644 (file)
@@ -377,9 +377,6 @@ u32 nat64_get_worker_in2out (ip6_address_t * addr);
  */
 u32 nat64_get_worker_out2in (ip4_header_t * ip);
 
-#define u8_ptr_add(ptr, index) (((u8 *)ptr) + index)
-#define u16_net_add(u, val) clib_host_to_net_u16(clib_net_to_host_u16(u) + (val))
-
 #endif /* __included_nat64_h__ */
 
 /*
index d2ddd0c..ac5557f 100644 (file)
@@ -75,9 +75,6 @@ int nat66_static_mapping_add_del (ip6_address_t * l_addr,
                                  ip6_address_t * e_addr, u32 vrf_id,
                                  u8 is_add);
 
-#define u8_ptr_add(ptr, index) (((u8 *)ptr) + index)
-#define u16_net_add(u, val) clib_host_to_net_u16(clib_net_to_host_u16(u) + (val))
-
 #endif /* __included_nat66_h__ */
 
 /*
index 4f96847..567262f 100644 (file)
@@ -348,7 +348,7 @@ libvnet_la_SOURCES +=                               \
  vnet/ip/ip_frag.c                             \
  vnet/ip/ip.c                                  \
  vnet/ip/ip_init.c                             \
- vnet/ip/ip_in_out_acl.c                               \
+ vnet/ip/ip_in_out_acl.c                       \
  vnet/ip/lookup.c                              \
  vnet/ip/ping.c                                        \
  vnet/ip/punt_api.c                            \
@@ -1197,6 +1197,19 @@ libvnet_la_SOURCES +=                    \
   vnet/util/refcount.c                  \
   vnet/util/trajectory.c
 
+########################################
+# QoS
+########################################
+
+libvnet_la_SOURCES +=                          \
+  vnet/qos/qos_types.c                         \
+  vnet/qos/qos_api.c                           \
+  vnet/qos/qos_egress_map.c                    \
+  vnet/qos/qos_record.c                                \
+  vnet/qos/qos_mark.c
+
+API_FILES += vnet/qos/qos.api
+
 ########################################
 # BIER
 ########################################
index f12adf7..f793fb4 100644 (file)
@@ -38,6 +38,10 @@ format_vnet_buffer (u8 * s, va_list * args)
   if (b->flags & VNET_BUFFER_F_L4_HDR_OFFSET_VALID)
     a = format (a, "l4-hdr-offset %d ", vnet_buffer (b)->l4_hdr_offset);
 
+  if (b->flags & VNET_BUFFER_F_QOS_DATA_VALID)
+    a = format (a, "qos %d.%d ",
+               vnet_buffer2 (b)->qos.bits, vnet_buffer2 (b)->qos.source);
+
   s = format (s, "%U", format_vlib_buffer, b);
   if (a)
     s = format (s, "\n%U%v", format_white_space, indent, a);
index 7a4bc24..e785145 100644 (file)
@@ -63,7 +63,8 @@
   _(15, L3_HDR_OFFSET_VALID, 0)                                \
   _(16, L4_HDR_OFFSET_VALID, 0)                                \
   _(17, FLOW_REPORT, "flow-report")                    \
-  _(18, IS_DVR, "dvr")
+  _(18, IS_DVR, "dvr")                                  \
+  _(19, QOS_DATA_VALID, 0)
 
 #define VNET_BUFFER_FLAGS_VLAN_BITS \
   (VNET_BUFFER_F_VLAN_1_DEEP | VNET_BUFFER_F_VLAN_2_DEEP)
@@ -186,6 +187,8 @@ typedef struct
       u8 ttl;
       u8 exp;
       u8 first;
+      /* Rewrite length */
+      u32 save_rewrite_length;
       /*
        * BIER - the nubmer of bytes in the header.
        *  the len field inthe header is not authoritative. It's the
@@ -362,16 +365,29 @@ STATIC_ASSERT (sizeof (vnet_buffer_opaque_t) <=
 /* Full cache line (64 bytes) of additional space */
 typedef struct
 {
+  /**
+   * QoS marking data that needs to persist from the recording nodes
+   * (nominally in the ingress path) to the marking node (in the
+   * egress path)
+   */
+  struct
+  {
+    u8 bits;
+    u8 source;
+  } qos;
+
+  u8 __unused[2];
+
   union
   {
-#if VLIB_BUFFER_TRACE_TRAJECTORY > 0
-    /* buffer trajectory tracing */
     struct
     {
+#if VLIB_BUFFER_TRACE_TRAJECTORY > 0
+      /* buffer trajectory tracing */
       u16 *trajectory_trace;
-    };
 #endif
-    u32 unused[12];
+    };
+    u32 unused[11];
   };
 } vnet_buffer_opaque2_t;
 
index 964cf63..09f5e7f 100644 (file)
@@ -91,6 +91,15 @@ typedef struct
   u16 type;
 } ethernet_vlan_header_t;
 
+always_inline void
+ethernet_vlan_header_set_priority_net_order (ethernet_vlan_header_t * h,
+                                            u8 prio)
+{
+  u8 *bytes = (u8 *) (&h->priority_cfi_and_id);
+
+  bytes[0] &= 0x1f;
+  bytes[0] |= (prio & 0x7) << 5;
+}
 
 /* VLAN with ethertype first and vlan id second */
 typedef struct
index 747b438..50965d4 100644 (file)
@@ -65,6 +65,9 @@
 #include <vnet/ip/icmp6.h>
 #include <vnet/classify/vnet_classify.h>
 
+#define u8_ptr_add(ptr, index) (((u8 *)ptr) + index)
+#define u16_net_add(u, val) clib_host_to_net_u16(clib_net_to_host_u16(u) + (val))
+
 /* Per protocol info. */
 typedef struct
 {
index 9916f30..70c3dab 100644 (file)
@@ -360,6 +360,7 @@ ip6_set_traffic_class_network_order (ip6_header_t * ip6, u8 dscp)
 {
   u32 tmp =
     clib_net_to_host_u32 (ip6->ip_version_traffic_class_and_flow_label);
+  tmp &= 0xf00fffff;
   tmp |= (dscp << 20);
   ip6->ip_version_traffic_class_and_flow_label = clib_host_to_net_u32 (tmp);
 }
index 256000a..3bbf2ab 100644 (file)
@@ -322,7 +322,7 @@ const ip46_address_t zero_addr = {
             0, 0},
 };
 
-clib_error_t *
+static clib_error_t *
 vnet_ip_route_cmd (vlib_main_t * vm,
                   unformat_input_t * main_input, vlib_cli_command_t * cmd)
 {
index 7243e81..5700cc1 100644 (file)
@@ -523,10 +523,6 @@ int map_ip6_reass_conf_lifetime(u16 lifetime_ms);
 int map_ip6_reass_conf_buffers(u32 buffers);
 #define MAP_IP6_REASS_CONF_BUFFERS_MAX (0xffffffff)
 
-
-#define u8_ptr_add(ptr, index) (((u8 *)ptr) + index)
-#define u16_net_add(u, val) clib_host_to_net_u16(clib_net_to_host_u16(u) + (val))
-
 static_always_inline void
 ip4_map_t_embedded_address (map_domain_t *d,
                                 ip6_address_t *ip6, const ip4_address_t *ip4)
index 22e6c10..b7bb769 100644 (file)
@@ -125,6 +125,8 @@ mpls_output_inline (vlib_main_t * vm,
           /* Update packet buffer attributes/set output interface. */
           rw_len0 = adj0[0].rewrite_header.data_bytes;
           rw_len1 = adj1[0].rewrite_header.data_bytes;
+         vnet_buffer (p0)->mpls.save_rewrite_length = rw_len0;
+         vnet_buffer (p1)->mpls.save_rewrite_length = rw_len1;
 
           /* Bump the adj counters for packet and bytes */
           vlib_increment_combined_counter
@@ -238,7 +240,8 @@ mpls_output_inline (vlib_main_t * vm,
           
           /* Update packet buffer attributes/set output interface. */
           rw_len0 = adj0[0].rewrite_header.data_bytes;
-          
+          vnet_buffer (p0)->mpls.save_rewrite_length = rw_len0;
+
           vlib_increment_combined_counter
               (&adjacency_counters,
                thread_index,
diff --git a/src/vnet/qos/qos.api b/src/vnet/qos/qos.api
new file mode 100644 (file)
index 0000000..a7bd19a
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+
+    This file defines QoS record and mark API messages which are generally
+    called through a shared memory interface.
+*/
+
+option version = "1.0.0";
+
+/** \brief Enable/Disable QoS recording
+    The QoS bits from the packet at the specified input layer are copied
+    into the packet. Recording should be used in conjunction with marking
+    @param sw_if_index - The interface on which recording is enabled.
+    @param enable - enable=1 or disable the feautre
+    @param input_source - The input source/layer at which the QoS bits
+                          are copied from the packet. See qos_source_t.
+*/
+autoreply define qos_record_enable_disable
+{
+  u32 client_index;
+  u32 context;
+  u32 sw_if_index;
+  u8 input_source;
+  u8 enable;
+};
+
+/** \brief A row within a QoS map
+    Each value translates from an input value to an output.
+*/
+typeonly define qos_egress_map_row
+{
+  u8  outputs[256];
+};
+
+/** \brief Update a QoS Map
+    A QoS map, translates from the QoS value in the packet set by the 'record'
+    feature, to the value used for output in the 'mark' feature.
+    There is one row in the map for each input/record source.
+    The MAP is then applied to the egress interface at for a given output source
+    @param map_id - client provided identifier for the map
+    @param rows - one row (per-input source) of output values
+*/
+autoreply define qos_egress_map_update
+{
+  u32 client_index;
+  u32 context;
+  u32 map_id;
+  vl_api_qos_egress_map_row_t rows[4];
+};
+
+/** \brief Delete a Qos Map
+    @param map_id - ID of the map to delete
+*/
+autoreply define qos_egress_map_delete
+{
+  u32 client_index;
+  u32 context;
+  u32 map_id;
+};
+
+/** \brief Enable/Disable QoS marking
+    The QoS bits from the packet are mapped (using the desired egress map)
+    into the header of the 'output-source'. Marking should be used in
+    conjunction with recording
+    @param sw_if_index - The interface on which recording is enabled.
+    @param enable - enable=1 or disable the feautre
+    @param output_source - The output source/layer at which the QoS bits
+                           are written into the packet. See qos_source_t.
+    @param map_id - The ID of the MAP in which the translation from input
+                    to output is performed.
+*/
+autoreply define qos_mark_enable_disable
+{
+  u32 client_index;
+  u32 context;
+  u32 map_id;
+  u32 sw_if_index;
+  u8 output_source;
+  u8 enable;
+};
+
+/*
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_api.c b/src/vnet/qos/qos_api.c
new file mode 100644 (file)
index 0000000..cdb9e46
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+#include <vnet/vnet.h>
+#include <vlibmemory/api.h>
+#include <vnet/api_errno.h>
+
+#include <vnet/qos/qos_record.h>
+#include <vnet/qos/qos_mark.h>
+#include <vnet/qos/qos_egress_map.h>
+
+#include <vnet/vnet_msg_enum.h>
+
+#define vl_typedefs            /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_typedefs
+
+#define vl_endianfun           /* define message structures */
+#include <vnet/vnet_all_api_h.h>
+#undef vl_endianfun
+
+/* instantiate all the print functions we know about */
+#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
+#define vl_printfun
+#include <vnet/vnet_all_api_h.h>
+#undef vl_printfun
+
+#include <vlibapi/api_helper_macros.h>
+
+
+#define foreach_qos_api_msg                                             \
+  _(QOS_RECORD_ENABLE_DISABLE, qos_record_enable_disable)               \
+  _(QOS_EGRESS_MAP_DELETE, qos_egress_map_delete)                       \
+  _(QOS_EGRESS_MAP_UPDATE, qos_egress_map_update)                       \
+  _(QOS_MARK_ENABLE_DISABLE, qos_mark_enable_disable)
+
+void
+vl_api_qos_record_enable_disable_t_handler (vl_api_qos_record_enable_disable_t
+                                           * mp)
+{
+  vl_api_qos_record_enable_disable_reply_t *rmp;
+  int rv = 0;
+
+  if (mp->input_source > QOS_N_SOURCES)
+    rv = VNET_API_ERROR_INVALID_VALUE;
+  else
+    {
+      if (mp->enable)
+       rv = qos_record_enable (ntohl (mp->sw_if_index), mp->input_source);
+      else
+       rv = qos_record_disable (ntohl (mp->sw_if_index), mp->input_source);
+    }
+
+  REPLY_MACRO (VL_API_QOS_RECORD_ENABLE_DISABLE_REPLY);
+}
+
+void
+vl_api_qos_egress_map_update_t_handler (vl_api_qos_egress_map_update_t * mp)
+{
+  vl_api_qos_egress_map_update_reply_t *rmp;
+  qos_source_t qs;
+  int rv = 0;
+
+  FOR_EACH_QOS_SOURCE (qs)
+  {
+    qos_egress_map_update (ntohl (mp->map_id), qs, &mp->rows[qs].outputs[0]);
+  }
+
+  REPLY_MACRO (VL_API_QOS_EGRESS_MAP_UPDATE_REPLY);
+}
+
+void
+vl_api_qos_egress_map_delete_t_handler (vl_api_qos_egress_map_delete_t * mp)
+{
+  vl_api_qos_egress_map_delete_reply_t *rmp;
+  int rv = 0;
+
+  qos_egress_map_delete (ntohl (mp->map_id));
+
+  REPLY_MACRO (VL_API_QOS_EGRESS_MAP_DELETE_REPLY);
+}
+
+void
+  vl_api_qos_mark_enable_disable_t_handler
+  (vl_api_qos_mark_enable_disable_t * mp)
+{
+  vl_api_qos_mark_enable_disable_reply_t *rmp;
+  int rv = 0;
+
+  if (mp->output_source > QOS_N_SOURCES)
+    rv = VNET_API_ERROR_INVALID_VALUE;
+  else
+    {
+      if (mp->enable)
+       rv = qos_mark_enable (ntohl (mp->sw_if_index),
+                             mp->output_source, ntohl (mp->map_id));
+      else
+       rv = qos_mark_disable (ntohl (mp->sw_if_index), mp->output_source);
+    }
+
+  REPLY_MACRO (VL_API_QOS_MARK_ENABLE_DISABLE_REPLY);
+}
+
+#define vl_msg_name_crc_list
+#include <vnet/qos/qos.api.h>
+#undef vl_msg_name_crc_list
+
+static void
+setup_message_id_table (api_main_t * am)
+{
+#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
+  foreach_vl_msg_name_crc_qos;
+#undef _
+}
+
+static clib_error_t *
+qos_api_hookup (vlib_main_t * vm)
+{
+  api_main_t *am = &api_main;
+
+#define _(N,n)                                                  \
+    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
+                           vl_api_##n##_t_handler,              \
+                           vl_noop_handler,                     \
+                           vl_api_##n##_t_endian,               \
+                           vl_api_##n##_t_print,                \
+                           sizeof(vl_api_##n##_t), 1);
+  foreach_qos_api_msg;
+#undef _
+
+  /*
+   * Set up the (msg_name, crc, message-id) table
+   */
+  setup_message_id_table (am);
+
+  return 0;
+}
+
+VLIB_API_INIT_FUNCTION (qos_api_hookup);
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_egress_map.c b/src/vnet/qos/qos_egress_map.c
new file mode 100644 (file)
index 0000000..3656439
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+#include <vnet/qos/qos_egress_map.h>
+#include <vnet/qos/qos_mark.h>
+
+/**
+ * Pool from which to allocate table
+ */
+qos_egress_map_t *qem_pool;
+
+/**
+ * DB to map user table-IDs to internal table indicies.
+ */
+uword *qem_db;
+
+index_t
+qos_egress_map_find (qos_egress_map_id_t mid)
+{
+  uword *p = NULL;
+
+  p = hash_get (qem_db, mid);
+
+  if (NULL != p)
+    return p[0];
+
+  return (INDEX_INVALID);
+}
+
+qos_egress_map_t *
+qos_egress_map_find_i (qos_egress_map_id_t mid)
+{
+  index_t qemi;
+
+  qemi = qos_egress_map_find (mid);
+
+  if (INDEX_INVALID != qemi)
+    {
+      return (pool_elt_at_index (qem_pool, qemi));
+    }
+
+  return (NULL);
+}
+
+static qos_egress_map_t *
+qos_egress_map_find_or_create (qos_egress_map_id_t mid)
+{
+  qos_egress_map_t *qem;
+
+  /*
+   * Find the existing or create a new table
+   */
+  qem = qos_egress_map_find_i (mid);
+
+  if (NULL == qem)
+    {
+      index_t qemi;
+
+      pool_get_aligned (qem_pool, qem, CLIB_CACHE_LINE_BYTES);
+      qemi = qem - qem_pool;
+
+      memset (qem, 0, sizeof (*qem));
+      hash_set (qem_db, mid, qemi);
+    }
+
+  return (qem);
+}
+
+void
+qos_egress_map_update (qos_egress_map_id_t mid,
+                      qos_source_t input_source, qos_bits_t * values)
+{
+  qos_egress_map_t *qem;
+
+  qem = qos_egress_map_find_or_create (mid);
+
+  clib_memcpy (qem->qem_output[input_source],
+              values, sizeof (qem->qem_output[input_source]));
+}
+
+void
+qos_egress_map_delete (qos_egress_map_id_t mid)
+{
+  qos_egress_map_t *qem;
+
+  qem = qos_egress_map_find_i (mid);
+  hash_unset (qem_db, mid);
+
+  if (NULL != qem)
+    {
+      pool_put (qem_pool, qem);
+    }
+}
+
+static clib_error_t *
+qos_egress_map_update_cli (vlib_main_t * vm,
+                          unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  qos_egress_map_id_t map_id;
+  qos_egress_map_t *qem;
+  u8 add;
+
+  add = 1;
+  map_id = ~0;
+  qem = NULL;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "delete") || unformat (input, "del"))
+       add = 0;
+      else if (unformat (input, "id %d", &map_id))
+       qem = qos_egress_map_find_or_create (map_id);
+      else
+       {
+         int qs, qi, qo;
+
+         if (NULL == qem)
+           return clib_error_return (0, "map-id must be specified");
+
+         while (unformat
+                (input, "[%U][%d]=%d", unformat_qos_source, &qs, &qi, &qo))
+           qem->qem_output[qs][qi] = qo;
+         break;
+       }
+    }
+
+  if (!add)
+    qos_egress_map_delete (map_id);
+
+  return (NULL);
+}
+
+/*?
+ * Update a Egress Qos Map table
+ *
+ * @cliexpar
+ * @cliexcmd{qos egress map id 0 [ip][4]=4}
+ ?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (qos_egress_map_update_command, static) = {
+  .path = "qos egress map",
+  .short_help = "qos egress map id %d [delete] {[SOURCE][INPUT]=OUTPUT}",
+  .function = qos_egress_map_update_cli,
+  .is_mp_safe = 1,
+};
+/* *INDENT-ON* */
+
+u8 *
+format_qos_egress_map (u8 * s, va_list * args)
+{
+  qos_egress_map_t *qem = va_arg (*args, qos_egress_map_t *);
+  u32 indent = va_arg (*args, u32);
+  int qs;
+  u32 ii;
+
+  FOR_EACH_QOS_SOURCE (qs)
+  {
+    s = format (s, "%U%U:[",
+               format_white_space, indent, format_qos_source, qs);
+
+    for (ii = 0; ii < ARRAY_LEN (qem->qem_output[qs]) - 1; ii++)
+      {
+       s = format (s, "%d,", qem->qem_output[qs][ii]);
+      }
+    s = format (s, "%d]\n", qem->qem_output[qs][ii]);
+  }
+
+  return (s);
+}
+
+static clib_error_t *
+qos_egress_map_show (vlib_main_t * vm,
+                    unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  qos_egress_map_id_t map_id;
+  qos_egress_map_t *qem;
+  clib_error_t *error;
+
+  map_id = ~0;
+  qem = NULL;
+  error = NULL;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "id %d", &map_id))
+       ;
+      else
+       {
+         error = unformat_parse_error (input);
+         goto done;
+       }
+    }
+
+  if (~0 == map_id)
+    {
+      index_t qemi;
+
+      /* *INDENT-OFF* */
+      hash_foreach(map_id, qemi, qem_db,
+      ({
+          vlib_cli_output (vm, " Map-ID:%d\n%U",
+                           map_id,
+                           format_qos_egress_map,
+                           pool_elt_at_index(qem_pool, qemi), 2);
+      }));
+      /* *INDENT-ON* */
+    }
+  else
+    {
+      qem = qos_egress_map_find_i (map_id);
+
+      if (NULL == qem)
+       {
+         error = clib_error_return (0, "No Map for ID %d", map_id);
+       }
+      else
+       {
+         vlib_cli_output (vm, " Map-ID:%d\n%U",
+                          map_id, format_qos_egress_map, qem, 2);
+       }
+    }
+
+done:
+  return (error);
+}
+
+/*?
+ * Show Egress Qos Maps
+ *
+ * @cliexpar
+ * @cliexcmd{show qos egress map}
+ ?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (qos_egress_map_show_command, static) = {
+  .path = "show qos egress map",
+  .short_help = "show qos egress map id %d",
+  .function = qos_egress_map_show,
+  .is_mp_safe = 1,
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_egress_map.h b/src/vnet/qos/qos_egress_map.h
new file mode 100644 (file)
index 0000000..f4b282c
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+/**
+ * A QOS egress map translates from the COS bits stored in the packet's
+ * meta-data into a per-protocol COS value
+ */
+
+#ifndef __QOS_EGRESS_MAP_H__
+#define __QOS_EGRESS_MAP_H__
+
+#include <vnet/qos/qos_types.h>
+#include <vnet/dpo/dpo.h>
+
+/**
+ * An attempt at type safety
+ */
+typedef u32 qos_egress_map_id_t;
+
+/**
+ * For a given output source a table maps each value of every input sorce.
+ */
+typedef struct qos_egress_map_t_
+{
+    /**
+     * The array of output mapped values;
+     *   output = eq_qos[input-source][input-value]
+     */
+  qos_bits_t qem_output[QOS_N_SOURCES][256];
+} qos_egress_map_t;
+
+extern u8 *format_qos_egress_map (u8 * s, va_list * args);
+
+/**
+ * Add a qos-egress map to an interface. If sw_if_index = ~0
+ * then the configuration is for the 'default' table.
+ * If the table is ~0, this is a removal.
+ * the egress mapping is applied. For example, is output is MPLS then
+ * the QoS markings will occur for MPLS pakcets.
+ */
+extern void qos_egress_map_update (qos_egress_map_id_t tid,
+                                  qos_source_t input_source,
+                                  qos_bits_t * values);
+extern void qos_egress_map_delete (qos_egress_map_id_t tid);
+
+/**
+ * Get the VPP QoS map index from the user's map-ID
+ */
+extern index_t qos_egress_map_find (qos_egress_map_id_t tid);
+
+/**
+ * Data-plane functions
+ */
+
+/**
+ * Pool from which to allocate map
+ */
+extern qos_egress_map_t *qem_pool;
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_mark.c b/src/vnet/qos/qos_mark.c
new file mode 100644 (file)
index 0000000..cec7192
--- /dev/null
@@ -0,0 +1,447 @@
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/ip/ip.h>
+#include <vnet/feature/feature.h>
+#include <vnet/qos/qos_egress_map.h>
+#include <vnet/qos/qos_mark.h>
+
+/**
+ * per-interface vector of which MAP is used by which interface
+ * for each output source
+ */
+index_t *qos_mark_configs[QOS_N_SOURCES];
+
+void
+qos_mark_ip_enable_disable (u32 sw_if_index, u8 enable)
+{
+  vnet_feature_enable_disable ("ip6-output", "ip6-qos-mark",
+                              sw_if_index, enable, NULL, 0);
+  vnet_feature_enable_disable ("ip4-output", "ip4-qos-mark",
+                              sw_if_index, enable, NULL, 0);
+}
+
+void
+qos_mark_vlan_enable_disable (u32 sw_if_index, u8 enable)
+{
+  vnet_feature_enable_disable ("interface-output", "vlan-qos-mark",
+                              sw_if_index, enable, NULL, 0);
+}
+
+void
+qos_mark_mpls_enable_disable (u32 sw_if_index, u8 enable)
+{
+  vnet_feature_enable_disable ("mpls-output", "mpls-qos-mark",
+                              sw_if_index, enable, NULL, 0);
+}
+
+static void
+qos_egress_map_feature_config (u32 sw_if_index, qos_source_t qs, u8 enable)
+{
+  switch (qs)
+    {
+    case QOS_SOURCE_EXT:
+      ASSERT (0);
+      break;
+    case QOS_SOURCE_VLAN:
+      qos_mark_vlan_enable_disable (sw_if_index, enable);
+      break;
+    case QOS_SOURCE_MPLS:
+      qos_mark_mpls_enable_disable (sw_if_index, enable);
+      break;
+    case QOS_SOURCE_IP:
+      qos_mark_ip_enable_disable (sw_if_index, enable);
+      break;
+    }
+}
+
+always_inline qos_egress_map_t *
+qos_egress_map_interface (u32 sw_if_index, qos_source_t output_source)
+{
+  ASSERT (vec_len (qos_mark_configs[output_source]) > sw_if_index);
+
+  return pool_elt_at_index (qem_pool,
+                           qos_mark_configs[output_source][sw_if_index]);
+}
+
+/**
+ * per-packet trace data
+ */
+typedef struct qos_mark_trace_t_
+{
+  /* per-pkt trace data */
+  qos_bits_t bits;
+  qos_source_t input;
+  u32 used;
+} qos_mark_trace_t;
+
+static inline uword
+qos_mark_inline (vlib_main_t * vm,
+                vlib_node_runtime_t * node,
+                vlib_frame_t * frame, qos_source_t output_source, int is_ip6)
+{
+  u32 n_left_from, *from, *to_next, next_index;
+
+  next_index = 0;
+  n_left_from = frame->n_vectors;
+  from = vlib_frame_vector_args (frame);
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         qos_source_t input_source0;
+         ethernet_vlan_header_t *vlan0;
+         u32 sw_if_index0, next0, bi0;
+         qos_egress_map_t *qem0;
+         ip4_header_t *ip4_0;
+         ip6_header_t *ip6_0;
+         vlib_buffer_t *b0;
+         qos_bits_t qos0;
+         u8 *mpls_bytes_0;
+         u8 eos0;
+
+         next0 = 0;
+         bi0 = from[0];
+         to_next[0] = bi0;
+         from += 1;
+         to_next += 1;
+         n_left_from -= 1;
+         n_left_to_next -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_TX];
+         input_source0 = vnet_buffer2 (b0)->qos.source;
+
+         qem0 = qos_egress_map_interface (sw_if_index0, output_source);
+         qos0 = qem0->qem_output[input_source0][vnet_buffer2 (b0)->qos.bits];
+
+         if (PREDICT_TRUE (b0->flags & VNET_BUFFER_F_QOS_DATA_VALID))
+           {
+             /* there is a source of QoS recording for this packet */
+             if (QOS_SOURCE_IP == output_source)
+               {
+                 if (is_ip6)
+                   {
+                     ip6_0 = (vlib_buffer_get_current (b0) +
+                              vnet_buffer (b0)->ip.save_rewrite_length);
+
+                     ip6_set_traffic_class_network_order (ip6_0, qos0);
+                   }
+                 else
+                   {
+                     ip4_0 = (vlib_buffer_get_current (b0) +
+                              vnet_buffer (b0)->ip.save_rewrite_length);
+                     if (PREDICT_FALSE (qos0 != ip4_0->tos))
+                       {
+                         ip4_0->tos = qos0;
+                         ip4_0->checksum = ip4_header_checksum (ip4_0);
+                       }
+                   }
+               }
+             else if (QOS_SOURCE_MPLS == output_source)
+               {
+                 mpls_bytes_0 = (vlib_buffer_get_current (b0) +
+                                 vnet_buffer (b0)->mpls.save_rewrite_length);
+
+                 /* apply to all the labels in the stack */
+                 do
+                   {
+                     /* clear out the old COS bts */
+                     mpls_bytes_0[2] &= 0xf1;
+                     /* OR in 3 bits of the mapped value */
+                     mpls_bytes_0[2] |= (qos0 & 0x7) << 1;
+                     eos0 = mpls_bytes_0[2] & 0x1;
+                     mpls_bytes_0 += 4;
+                   }
+                 while (!eos0);
+               }
+             else if (QOS_SOURCE_VLAN == output_source)
+               {
+                 vlan0 = (vlib_buffer_get_current (b0) +
+                          sizeof (ethernet_header_t));
+
+                 ethernet_vlan_header_set_priority_net_order (vlan0, qos0);
+               }
+           }
+         vnet_feature_next (sw_if_index0, &next0, b0);
+
+         if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
+           {
+             qos_mark_trace_t *t =
+               vlib_add_trace (vm, node, b0, sizeof (*t));
+             t->bits = qos0;
+             t->input = input_source0;
+             t->used = (b0->flags & VNET_BUFFER_F_QOS_DATA_VALID);
+           }
+
+         /* verify speculative enqueue, maybe switch current next frame */
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+       }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+/* packet trace format function */
+static u8 *
+format_qos_mark_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  qos_mark_trace_t *t = va_arg (*args, qos_mark_trace_t *);
+
+  s = format (s, "source:%U qos:%d used:%s",
+             format_qos_source, t->input, t->bits, (t->used ? "yes" : "no"));
+
+  return s;
+}
+
+static inline uword
+ip4_qos_mark (vlib_main_t * vm, vlib_node_runtime_t * node,
+             vlib_frame_t * frame)
+{
+  return (qos_mark_inline (vm, node, frame, QOS_SOURCE_IP, 0));
+}
+
+static inline uword
+ip6_qos_mark (vlib_main_t * vm, vlib_node_runtime_t * node,
+             vlib_frame_t * frame)
+{
+  return (qos_mark_inline (vm, node, frame, QOS_SOURCE_IP, 1));
+}
+
+static inline uword
+mpls_qos_mark (vlib_main_t * vm, vlib_node_runtime_t * node,
+              vlib_frame_t * frame)
+{
+  return (qos_mark_inline (vm, node, frame, QOS_SOURCE_MPLS, 0));
+}
+
+static inline uword
+vlan_qos_mark (vlib_main_t * vm, vlib_node_runtime_t * node,
+              vlib_frame_t * frame)
+{
+  return (qos_mark_inline (vm, node, frame, QOS_SOURCE_VLAN, 0));
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (ip4_qos_mark_node) = {
+  .function = ip4_qos_mark,
+  .name = "ip4-qos-mark",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_mark_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "ip4-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (ip4_qos_mark_node, ip4_qos_mark);
+
+VNET_FEATURE_INIT (ip4_qos_mark_node, static) = {
+    .arc_name = "ip4-output",
+    .node_name = "ip4-qos-mark",
+};
+
+VLIB_REGISTER_NODE (ip6_qos_mark_node) = {
+  .function = ip6_qos_mark,
+  .name = "ip6-qos-mark",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_mark_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "ip6-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (ip6_qos_mark_node, ip6_qos_mark);
+
+VNET_FEATURE_INIT (ip6_qos_mark_node, static) = {
+    .arc_name = "ip6-output",
+    .node_name = "ip6-qos-mark",
+};
+
+VLIB_REGISTER_NODE (mpls_qos_mark_node) = {
+  .function = mpls_qos_mark,
+  .name = "mpls-qos-mark",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_mark_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "mpls-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (mpls_qos_mark_node, mpls_qos_mark);
+
+VNET_FEATURE_INIT (mpls_qos_mark_node, static) = {
+    .arc_name = "mpls-output",
+    .node_name = "mpls-qos-mark",
+};
+VLIB_REGISTER_NODE (vlan_qos_mark_node) = {
+  .function = vlan_qos_mark,
+  .name = "vlan-qos-mark",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_mark_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "error-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (vlan_qos_mark_node, vlan_qos_mark);
+
+VNET_FEATURE_INIT (vlan_qos_mark_node, static) = {
+    .arc_name = "interface-output",
+    .node_name = "vlan-qos-mark",
+};
+/* *INDENT-ON* */
+
+int
+qos_mark_enable (u32 sw_if_index,
+                qos_source_t output_source, qos_egress_map_id_t mid)
+{
+  index_t qemi;
+
+  vec_validate_init_empty (qos_mark_configs[output_source],
+                          sw_if_index, INDEX_INVALID);
+
+  qemi = qos_egress_map_find (mid);
+
+  if (INDEX_INVALID == qemi)
+    return VNET_API_ERROR_NO_SUCH_TABLE;
+
+  if (INDEX_INVALID == qos_mark_configs[output_source][sw_if_index])
+    {
+      qos_egress_map_feature_config (sw_if_index, output_source, 1);
+    }
+
+  qos_mark_configs[output_source][sw_if_index] = qemi;
+
+  return (0);
+}
+
+int
+qos_mark_disable (u32 sw_if_index, qos_source_t output_source)
+{
+  if (vec_len (qos_mark_configs[output_source]) < sw_if_index)
+    return VNET_API_ERROR_NO_MATCHING_INTERFACE;
+  if (INDEX_INVALID == qos_mark_configs[output_source][sw_if_index])
+    return VNET_API_ERROR_VALUE_EXIST;
+
+  if (INDEX_INVALID != qos_mark_configs[output_source][sw_if_index])
+    {
+      qos_egress_map_feature_config (sw_if_index, output_source, 0);
+    }
+
+  qos_mark_configs[output_source][sw_if_index] = INDEX_INVALID;
+
+  return (0);
+}
+
+static clib_error_t *
+qos_mark_cli (vlib_main_t * vm,
+             unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  qos_egress_map_id_t map_id;
+  u32 sw_if_index, qs;
+  vnet_main_t *vnm;
+  int rv, enable;
+
+  vnm = vnet_get_main ();
+  map_id = ~0;
+  qs = 0xff;
+  enable = 1;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "id %d", &map_id))
+       ;
+      else if (unformat (input, "disable"))
+       enable = 0;
+      else if (unformat (input, "%U", unformat_qos_source, &qs))
+       ;
+      else if (unformat (input, "%U",
+                        unformat_vnet_sw_interface, vnm, &sw_if_index))
+       ;
+      else
+       break;
+    }
+
+  if (~0 == sw_if_index)
+    return clib_error_return (0, "interface must be specified");
+  if (0xff == qs)
+    return clib_error_return (0, "output location must be specified");
+
+  if (enable)
+    rv = qos_mark_enable (sw_if_index, qs, map_id);
+  else
+    rv = qos_mark_disable (sw_if_index, qs);
+
+  if (0 == rv)
+    return (NULL);
+
+  return clib_error_return (0, "Failed to map interface");
+}
+
+/*?
+ * Apply a QoS egress mapping table to an interface for QoS marking packets
+ * at the given output protocol.
+ *
+ * @cliexpar
+ * @cliexcmd{qos egress interface GigEthernet0/9/0 id 0 output ip}
+ ?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (qos_egress_map_interface_command, static) = {
+  .path = "qos mark",
+  .short_help = "qos mark <SOURCE> <INTERFACE> id <MAP>",
+  .function = qos_mark_cli,
+  .is_mp_safe = 1,
+};
+/* *INDENT-ON* */
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+* Local Variables:
+* eval: (c-set-style "gnu")
+* End:
+*
+*/
diff --git a/src/vnet/qos/qos_mark.h b/src/vnet/qos/qos_mark.h
new file mode 100644 (file)
index 0000000..f1705d1
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __QOS_MARK_H__
+#define __QOS_MARK_H__
+
+#include <vnet/qos/qos_egress_map.h>
+
+/**
+ * enable QoS marking by associating a MAP with an interface.
+ * The output_source specifies which protocol/header the QoS value
+ * will be written into
+ */
+extern int qos_mark_enable (u32 sw_if_index,
+                           qos_source_t output_source,
+                           qos_egress_map_id_t tid);
+extern int qos_mark_disable (u32 sw_if_index, qos_source_t output_source);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_record.c b/src/vnet/qos/qos_record.c
new file mode 100644 (file)
index 0000000..047bd25
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <vnet/qos/qos_record.h>
+#include <vnet/ip/ip.h>
+#include <vnet/ip/ip6_to_ip4.h>
+#include <vnet/feature/feature.h>
+#include <vnet/qos/qos_types.h>
+
+/**
+ * Per-interface, per-protocol vector of feature on/off configurations
+ */
+static u8 *qos_record_configs[QOS_N_SOURCES];
+
+static void
+qos_record_feature_config (u32 sw_if_index,
+                          qos_source_t input_source, u8 enable)
+{
+  switch (input_source)
+    {
+    case QOS_SOURCE_IP:
+      vnet_feature_enable_disable ("ip6-unicast", "ip6-qos-record",
+                                  sw_if_index, enable, NULL, 0);
+      vnet_feature_enable_disable ("ip6-multicast", "ip6-qos-record",
+                                  sw_if_index, enable, NULL, 0);
+      vnet_feature_enable_disable ("ip4-unicast", "ip4-qos-record",
+                                  sw_if_index, enable, NULL, 0);
+      vnet_feature_enable_disable ("ip4-multicast", "ip4-qos-record",
+                                  sw_if_index, enable, NULL, 0);
+      break;
+    case QOS_SOURCE_MPLS:
+    case QOS_SOURCE_VLAN:
+    case QOS_SOURCE_EXT:
+      // not implemented yet
+      break;
+    }
+}
+
+int
+qos_record_enable (u32 sw_if_index, qos_source_t input_source)
+{
+  vec_validate (qos_record_configs[input_source], sw_if_index);
+
+  if (0 == qos_record_configs[input_source][sw_if_index])
+    {
+      qos_record_feature_config (sw_if_index, input_source, 1);
+    }
+
+  qos_record_configs[input_source][sw_if_index]++;
+  return (0);
+}
+
+int
+qos_record_disable (u32 sw_if_index, qos_source_t input_source)
+{
+  if (vec_len (qos_record_configs[input_source]) < sw_if_index)
+    return VNET_API_ERROR_NO_MATCHING_INTERFACE;
+
+  if (0 == qos_record_configs[input_source][sw_if_index])
+    return VNET_API_ERROR_VALUE_EXIST;
+
+  qos_record_configs[input_source][sw_if_index]--;
+
+  if (0 == qos_record_configs[input_source][sw_if_index])
+    {
+      qos_record_feature_config (sw_if_index, input_source, 0);
+    }
+
+  return (0);
+}
+
+/*
+ * Disable recording feautre for all protocols when the interface
+ * is deleted
+ */
+static clib_error_t *
+qos_record_ip_interface_add_del (vnet_main_t * vnm,
+                                u32 sw_if_index, u32 is_add)
+{
+  if (!is_add)
+    {
+      qos_source_t qs;
+
+      FOR_EACH_QOS_SOURCE (qs)
+      {
+       qos_record_disable (sw_if_index, qs);
+      }
+    }
+
+  return (NULL);
+}
+
+VNET_SW_INTERFACE_ADD_DEL_FUNCTION (qos_record_ip_interface_add_del);
+
+/**
+ * per-packet trace data
+ */
+typedef struct qos_record_trace_t_
+{
+  /* per-pkt trace data */
+  qos_bits_t bits;
+} qos_record_trace_t;
+
+static inline uword
+qos_record_inline (vlib_main_t * vm,
+                  vlib_node_runtime_t * node,
+                  vlib_frame_t * frame, int is_ip6)
+{
+  u32 n_left_from, *from, *to_next, next_index;
+
+  next_index = 0;
+  n_left_from = frame->n_vectors;
+  from = vlib_frame_vector_args (frame);
+
+  while (n_left_from > 0)
+    {
+      u32 n_left_to_next;
+
+      vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
+
+      while (n_left_from > 0 && n_left_to_next > 0)
+       {
+         ip4_header_t *ip4_0;
+         ip6_header_t *ip6_0;
+         vlib_buffer_t *b0;
+         u32 sw_if_index0, next0, bi0;
+         qos_bits_t qos0;
+
+         next0 = 0;
+         bi0 = from[0];
+         to_next[0] = bi0;
+         from += 1;
+         to_next += 1;
+         n_left_from -= 1;
+         n_left_to_next -= 1;
+
+         b0 = vlib_get_buffer (vm, bi0);
+         if (is_ip6)
+           {
+             ip6_0 = vlib_buffer_get_current (b0);
+             qos0 = ip6_traffic_class_network_order (ip6_0);
+           }
+         else
+           {
+             ip4_0 = vlib_buffer_get_current (b0);
+             qos0 = ip4_0->tos;
+           }
+         vnet_buffer2 (b0)->qos.bits = qos0;
+         vnet_buffer2 (b0)->qos.source = QOS_SOURCE_IP;
+         b0->flags |= VNET_BUFFER_F_QOS_DATA_VALID;
+         sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
+
+         vnet_feature_next (sw_if_index0, &next0, b0);
+
+         if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) &&
+                            (b0->flags & VLIB_BUFFER_IS_TRACED)))
+           {
+             qos_record_trace_t *t =
+               vlib_add_trace (vm, node, b0, sizeof (*t));
+             t->bits = qos0;
+           }
+
+         /* verify speculative enqueue, maybe switch current next frame */
+         vlib_validate_buffer_enqueue_x1 (vm, node, next_index,
+                                          to_next, n_left_to_next,
+                                          bi0, next0);
+       }
+
+      vlib_put_next_frame (vm, node, next_index, n_left_to_next);
+    }
+
+  return frame->n_vectors;
+}
+
+/* packet trace format function */
+static u8 *
+format_qos_record_trace (u8 * s, va_list * args)
+{
+  CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
+  CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  qos_record_trace_t *t = va_arg (*args, qos_record_trace_t *);
+
+  s = format (s, "qos:%d", t->bits);
+
+  return s;
+}
+
+static inline uword
+ip4_qos_record (vlib_main_t * vm, vlib_node_runtime_t * node,
+               vlib_frame_t * frame)
+{
+  return (qos_record_inline (vm, node, frame, 0));
+}
+
+static inline uword
+ip6_qos_record (vlib_main_t * vm, vlib_node_runtime_t * node,
+               vlib_frame_t * frame)
+{
+  return (qos_record_inline (vm, node, frame, 1));
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (ip4_qos_record_node) = {
+  .function = ip4_qos_record,
+  .name = "ip4-qos-record",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_record_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "ip4-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (ip4_qos_record_node, ip4_qos_record);
+
+VNET_FEATURE_INIT (ip4_qos_record_node, static) = {
+    .arc_name = "ip4-unicast",
+    .node_name = "ip4-qos-record",
+};
+
+VLIB_REGISTER_NODE (ip6_qos_record_node) = {
+  .function = ip6_qos_record,
+  .name = "ip6-qos-record",
+  .vector_size = sizeof (u32),
+  .format_trace = format_qos_record_trace,
+  .type = VLIB_NODE_TYPE_INTERNAL,
+
+  .n_errors = 0,
+  .n_next_nodes = 1,
+
+  .next_nodes = {
+    [0] = "ip6-drop",
+  },
+};
+
+VLIB_NODE_FUNCTION_MULTIARCH (ip6_qos_record_node, ip6_qos_record);
+
+VNET_FEATURE_INIT (ip6_qos_record_node, static) = {
+    .arc_name = "ip6-unicast",
+    .node_name = "ip6-qos-record",
+};
+/* *INDENT-ON* */
+
+
+static clib_error_t *
+qos_record_cli (vlib_main_t * vm,
+               unformat_input_t * input, vlib_cli_command_t * cmd)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  u32 sw_if_index, qs;
+  u8 enable;
+
+  qs = 0xff;
+  enable = 1;
+  sw_if_index = ~0;
+
+  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (input, "%U", unformat_vnet_sw_interface,
+                   vnm, &sw_if_index))
+       ;
+      else if (unformat (input, "%U", unformat_qos_source, &qs))
+       ;
+      else if (unformat (input, "enable"))
+       enable = 1;
+      else if (unformat (input, "disable"))
+       enable = 0;
+      else
+       break;
+    }
+
+  if (~0 == sw_if_index)
+    return clib_error_return (0, "interface must be specified");
+  if (0xff == qs)
+    return clib_error_return (0, "input location must be specified");
+
+  if (enable)
+    qos_record_enable (sw_if_index, qs);
+  else
+    qos_record_disable (sw_if_index, qs);
+
+  return (NULL);
+}
+
+/*?
+ * Enable QoS bit recording on an interface using the packet's input DSCP bits
+ * Which input QoS bits to use are either; IP, MPLS or VLAN. If more than
+ * one protocol is chosen (which is foolish) the higer layers override the
+ * lower.
+ *
+ * @cliexpar
+ * @cliexcmd{qos record ip GigEthernet0/1/0}
+ ?*/
+/* *INDENT-OFF* */
+VLIB_CLI_COMMAND (qos_record_command, static) = {
+  .path = "qos record",
+  .short_help = "qos record <record-source> <INTERFACE> [disable]",
+  .function = qos_record_cli,
+  .is_mp_safe = 1,
+};
+/* *INDENT-ON* */
+
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_record.h b/src/vnet/qos/qos_record.h
new file mode 100644 (file)
index 0000000..e5726d6
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __QOS_RECORD_H__
+#define __QOS_RECORD_H__
+
+#include <vnet/qos/qos_types.h>
+
+extern int qos_record_disable (u32 sw_if_index, qos_source_t input_source);
+extern int qos_record_enable (u32 sw_if_index, qos_source_t input_source);
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_types.c b/src/vnet/qos/qos_types.c
new file mode 100644 (file)
index 0000000..9fe10cb
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+/**
+ * QoS tyeps
+ */
+
+#include <vnet/qos/qos_types.h>
+
+static const char *qos_source_names[] = QOS_SOURCE_NAMES;
+
+u8 *
+format_qos_source (u8 * s, va_list * args)
+{
+  int qs = va_arg (*args, int);
+
+  return (format (s, "%s", qos_source_names[qs]));
+}
+
+uword
+unformat_qos_source (unformat_input_t * input, va_list * args)
+{
+  int *qs = va_arg (*args, int *);
+
+  if (unformat (input, "ip"))
+    *qs = QOS_SOURCE_IP;
+  else if (unformat (input, "mpls"))
+    *qs = QOS_SOURCE_MPLS;
+  else if (unformat (input, "ext"))
+    *qs = QOS_SOURCE_EXT;
+  else if (unformat (input, "vlan"))
+    *qs = QOS_SOURCE_VLAN;
+  else
+    return 0;
+
+  return 1;
+}
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
diff --git a/src/vnet/qos/qos_types.h b/src/vnet/qos/qos_types.h
new file mode 100644 (file)
index 0000000..6934d04
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ *------------------------------------------------------------------
+ * Copyright (c) 2018 Cisco and/or its affiliates.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *------------------------------------------------------------------
+ */
+
+/**
+ * QoS tyeps
+ */
+
+#ifndef __QOS_TYPES_H__
+#define __QOS_TYPES_H__
+
+#include <vnet/vnet.h>
+
+/**
+ * Sources for the QoS bits in the packet
+ */
+typedef enum qos_source_t_
+{
+  /**
+   * Some external source, e.g. a plugin.
+   */
+  QOS_SOURCE_EXT,
+  QOS_SOURCE_VLAN,
+  QOS_SOURCE_MPLS,
+  QOS_SOURCE_IP,
+} __attribute__ ((packed)) qos_source_t;
+
+/**
+ * The maximum number of sources. defined outside the enum so switch
+ * statements don't need to handle a non-value nor use a default label
+ */
+#define QOS_N_SOURCES (QOS_SOURCE_IP + 1)
+
+#define QOS_SOURCE_NAMES {                   \
+    [QOS_SOURCE_EXT] = "ext",                \
+    [QOS_SOURCE_IP] = "IP",                  \
+    [QOS_SOURCE_MPLS] = "MPLS",              \
+    [QOS_SOURCE_VLAN] = "VLAN",              \
+}
+
+#define FOR_EACH_QOS_SOURCE(_src)    \
+    for (_src = QOS_SOURCE_EXT;      \
+         _src <= QOS_SOURCE_IP;      \
+         _src++)
+
+/**
+ * format/unformat QoS source types
+ */
+extern u8 *format_qos_source (u8 * s, va_list * args);
+extern uword unformat_qos_source (unformat_input_t * input, va_list * args);
+
+/**
+ * Type, er, safety for us water based entities
+ */
+typedef u8 qos_bits_t;
+
+#endif
+
+/*
+ * fd.io coding-style-patch-verification: ON
+ *
+ * Local Variables:
+ * eval: (c-set-style "gnu")
+ * End:
+ */
index 44fb758..fea92e7 100644 (file)
@@ -71,6 +71,7 @@
 #include <vnet/pg/pg.api.h>
 #include <vnet/feature/feature.api.h>
 #include <vnet/sctp/sctp.api.h>
+#include <vnet/qos/qos.api.h>
 
 /*
  * fd.io coding-style-patch-verification: ON
diff --git a/test/test_qos.py b/test/test_qos.py
new file mode 100644 (file)
index 0000000..a940bd3
--- /dev/null
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+
+import unittest
+import socket
+import struct
+
+from framework import VppTestCase, VppTestRunner
+from vpp_object import VppObject
+from vpp_papi_provider import QOS_SOURCE
+from vpp_ip_route import VppIpRoute, VppRoutePath, VppMplsRoute
+from vpp_sub_interface import VppSubInterface, VppDot1QSubint
+
+from scapy.packet import Raw
+from scapy.layers.l2 import Ether, Dot1Q
+from scapy.layers.inet import IP, UDP
+from scapy.layers.inet6 import IPv6
+from scapy.contrib.mpls import MPLS
+
+
+class TestQOS(VppTestCase):
+    """ QOS Test Case """
+
+    def setUp(self):
+        super(TestQOS, self).setUp()
+
+        self.create_pg_interfaces(range(5))
+
+        for i in self.pg_interfaces:
+            i.admin_up()
+            i.config_ip4()
+            i.resolve_arp()
+            i.config_ip6()
+            i.resolve_ndp()
+
+    def tearDown(self):
+        for i in self.pg_interfaces:
+            i.unconfig_ip4()
+            i.unconfig_ip6()
+
+        super(TestQOS, self).tearDown()
+
+    def test_qos_ip(self):
+        """ QoS Mark IP """
+
+        #
+        # for table 1 map the n=0xff possible values of input QoS mark,
+        # n to 1-n
+        #
+        output = [chr(0)] * 256
+        for i in range(0, 255):
+            output[i] = chr(255 - i)
+        os = ''.join(output)
+        rows = [{'outputs': os},
+                {'outputs': os},
+                {'outputs': os},
+                {'outputs': os}]
+
+        self.vapi.qos_egress_map_update(1, rows)
+
+        #
+        # For table 2 (and up) use the value n for everything
+        #
+        output = [chr(2)] * 256
+        os = ''.join(output)
+        rows = [{'outputs': os},
+                {'outputs': os},
+                {'outputs': os},
+                {'outputs': os}]
+
+        self.vapi.qos_egress_map_update(2, rows)
+
+        output = [chr(3)] * 256
+        os = ''.join(output)
+        rows = [{'outputs': os},
+                {'outputs': os},
+                {'outputs': os},
+                {'outputs': os}]
+
+        self.vapi.qos_egress_map_update(3, rows)
+
+        output = [chr(4)] * 256
+        os = ''.join(output)
+        rows = [{'outputs': os},
+                {'outputs': os},
+                {'outputs': os},
+                {'outputs': os}]
+        self.vapi.qos_egress_map_update(4, rows)
+        self.vapi.qos_egress_map_update(5, rows)
+        self.vapi.qos_egress_map_update(6, rows)
+        self.vapi.qos_egress_map_update(7, rows)
+
+        self.logger.info(self.vapi.cli("sh qos eg map"))
+
+        #
+        # Bind interface pgN to table n
+        #
+        self.vapi.qos_mark_enable_disable(self.pg1.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          1,
+                                          1)
+        self.vapi.qos_mark_enable_disable(self.pg2.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          2,
+                                          1)
+        self.vapi.qos_mark_enable_disable(self.pg3.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          3,
+                                          1)
+        self.vapi.qos_mark_enable_disable(self.pg4.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          4,
+                                          1)
+
+        #
+        # packets ingress on Pg0
+        #
+        p_v4 = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4, tos=1) /
+                UDP(sport=1234, dport=1234) /
+                Raw(chr(100) * 65))
+        p_v6 = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+                IPv6(src=self.pg0.remote_ip6, dst=self.pg1.remote_ip6,
+                     tc=1) /
+                UDP(sport=1234, dport=1234) /
+                Raw(chr(100) * 65))
+
+        #
+        # Since we have not yet enabled the recording of the input QoS
+        # from the input iP header, the egress packet's ToS will be unchanged
+        #
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 1)
+        rx = self.send_and_expect(self.pg0, p_v6 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IPv6].tc, 1)
+
+        #
+        # Enable QoS recrding on IP input for pg0
+        #
+        self.vapi.qos_record_enable_disable(self.pg0.sw_if_index,
+                                            QOS_SOURCE.IP,
+                                            1)
+
+        #
+        # send the same packets, this time expect the input TOS of 1
+        # to be mapped to pg1's egress value of 254
+        #
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 254)
+        rx = self.send_and_expect(self.pg0, p_v6 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IPv6].tc, 254)
+
+        #
+        # different input ToS to test the mapping
+        #
+        p_v4[IP].tos = 127
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 128)
+        p_v6[IPv6].tc = 127
+        rx = self.send_and_expect(self.pg0, p_v6 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IPv6].tc, 128)
+
+        p_v4[IP].tos = 254
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 1)
+        p_v6[IPv6].tc = 254
+        rx = self.send_and_expect(self.pg0, p_v6 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IPv6].tc, 1)
+
+        #
+        # send packets out the other interfaces to test the maps are
+        # correctly applied
+        #
+        p_v4[IP].dst = self.pg2.remote_ip4
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg2)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 2)
+
+        p_v4[IP].dst = self.pg3.remote_ip4
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg3)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 3)
+
+        p_v6[IPv6].dst = self.pg3.remote_ip6
+        rx = self.send_and_expect(self.pg0, p_v6 * 65, self.pg3)
+        for p in rx:
+            self.assertEqual(p[IPv6].tc, 3)
+
+        #
+        # remove the map on pg2 and pg3, now expect an unchanged IP tos
+        #
+        self.vapi.qos_mark_enable_disable(self.pg2.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          2,
+                                          0)
+        self.vapi.qos_mark_enable_disable(self.pg3.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          3,
+                                          0)
+        self.logger.info(self.vapi.cli("sh int feat pg2"))
+
+        p_v4[IP].dst = self.pg2.remote_ip4
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg2)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 254)
+
+        p_v4[IP].dst = self.pg3.remote_ip4
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg3)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 254)
+
+        #
+        # still mapping out of pg1
+        #
+        p_v4[IP].dst = self.pg1.remote_ip4
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 1)
+
+        #
+        # disable the input recording on pg0
+        #
+        self.vapi.qos_record_enable_disable(self.pg0.sw_if_index,
+                                            QOS_SOURCE.IP,
+                                            0)
+
+        #
+        # back to an unchanged TOS value
+        #
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 254)
+
+        #
+        # disable the egress map on pg1 and pg4
+        #
+        self.vapi.qos_mark_enable_disable(self.pg1.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          1,
+                                          0)
+        self.vapi.qos_mark_enable_disable(self.pg4.sw_if_index,
+                                          QOS_SOURCE.IP,
+                                          4,
+                                          0)
+
+        #
+        # unchanged Tos on pg1
+        #
+        rx = self.send_and_expect(self.pg0, p_v4 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[IP].tos, 254)
+
+        #
+        # clean-up the masp
+        #
+        self.vapi.qos_egress_map_delete(1)
+        self.vapi.qos_egress_map_delete(4)
+        self.vapi.qos_egress_map_delete(2)
+        self.vapi.qos_egress_map_delete(3)
+        self.vapi.qos_egress_map_delete(5)
+        self.vapi.qos_egress_map_delete(6)
+        self.vapi.qos_egress_map_delete(7)
+
+    def test_qos_mpls(self):
+        """ QoS Mark MPLS """
+
+        #
+        # 255 QoS for all input values
+        #
+        output = [chr(255)] * 256
+        os = ''.join(output)
+        rows = [{'outputs': os},
+                {'outputs': os},
+                {'outputs': os},
+                {'outputs': os}]
+
+        self.vapi.qos_egress_map_update(1, rows)
+
+        #
+        # a route with 1 MPLS label
+        #
+        route_10_0_0_1 = VppIpRoute(self, "10.0.0.1", 32,
+                                    [VppRoutePath(self.pg1.remote_ip4,
+                                                  self.pg1.sw_if_index,
+                                                  labels=[32])])
+        route_10_0_0_1.add_vpp_config()
+
+        #
+        # a route with 3 MPLS labels
+        #
+        route_10_0_0_3 = VppIpRoute(self, "10.0.0.3", 32,
+                                    [VppRoutePath(self.pg1.remote_ip4,
+                                                  self.pg1.sw_if_index,
+                                                  labels=[63, 33, 34])])
+        route_10_0_0_3.add_vpp_config()
+
+        #
+        # enable IP QoS recording on the input Pg0 and MPLS egress marking
+        # on Pg1
+        #
+        self.vapi.qos_record_enable_disable(self.pg0.sw_if_index,
+                                            QOS_SOURCE.IP,
+                                            1)
+        self.vapi.qos_mark_enable_disable(self.pg1.sw_if_index,
+                                          QOS_SOURCE.MPLS,
+                                          1,
+                                          1)
+
+        #
+        # packet that will get one label added and 3 labels added resp.
+        #
+        p_1 = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+               IP(src=self.pg0.remote_ip4, dst="10.0.0.1", tos=1) /
+               UDP(sport=1234, dport=1234) /
+               Raw(chr(100) * 65))
+        p_3 = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) /
+               IP(src=self.pg0.remote_ip4, dst="10.0.0.3", tos=1) /
+               UDP(sport=1234, dport=1234) /
+               Raw(chr(100) * 65))
+
+        rx = self.send_and_expect(self.pg0, p_1 * 65, self.pg1)
+
+        #
+        # only 3 bits of ToS value in MPLS make sure tos is correct
+        # and the label and EOS bit have not been corrupted
+        #
+        for p in rx:
+            self.assertEqual(p[MPLS].cos, 7)
+            self.assertEqual(p[MPLS].label, 32)
+            self.assertEqual(p[MPLS].s, 1)
+        rx = self.send_and_expect(self.pg0, p_3 * 65, self.pg1)
+        for p in rx:
+            self.assertEqual(p[MPLS].cos, 7)
+            self.assertEqual(p[MPLS].label, 63)
+            self.assertEqual(p[MPLS].s, 0)
+            h = p[MPLS].payload
+            self.assertEqual(h[MPLS].cos, 7)
+            self.assertEqual(h[MPLS].label, 33)
+            self.assertEqual(h[MPLS].s, 0)
+            h = h[MPLS].payload
+            self.assertEqual(h[MPLS].cos, 7)
+            self.assertEqual(h[MPLS].label, 34)
+            self.assertEqual(h[MPLS].s, 1)
+
+        #
+        # cleanup
+        #
+        self.vapi.qos_record_enable_disable(self.pg0.sw_if_index,
+                                            QOS_SOURCE.IP,
+                                            0)
+        self.vapi.qos_mark_enable_disable(self.pg1.sw_if_index,
+                                          QOS_SOURCE.MPLS,
+                                          1,
+                                          0)
+        self.vapi.qos_egress_map_delete(1)
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
index bd770ef..da59bc8 100644 (file)
@@ -36,6 +36,13 @@ class L2_VTR_OP:
     L2_TRANSLATE_2_2 = 8
 
 
+class QOS_SOURCE:
+    EXT = 0
+    VLAN = 1
+    MPLS = 2
+    IP = 3
+
+
 class UnexpectedApiReturnValueError(Exception):
     """ exception raised when the API return value is unexpected """
     pass
@@ -3255,3 +3262,32 @@ class VppPapiProvider(object):
         """ IPIP tunnel Delete """
         return self.api(self.papi.ipip_del_tunnel,
                         {'sw_if_index': sw_if_index})
+
+    def qos_egress_map_update(self, id, outputs):
+        """ QOS egress map update """
+        return self.api(self.papi.qos_egress_map_update,
+                        {'map_id': id,
+                         'rows': outputs})
+
+    def qos_egress_map_delete(self, id):
+        """ QOS egress map delete """
+        return self.api(self.papi.qos_egress_map_delete,
+                        {'map_id': id})
+
+    def qos_mark_enable_disable(self, sw_if_index,
+                                output_source,
+                                map_id,
+                                enable):
+        """ QOS Mark Enable/Disable """
+        return self.api(self.papi.qos_mark_enable_disable,
+                        {'map_id': map_id,
+                         'sw_if_index': sw_if_index,
+                         'output_source': output_source,
+                         'enable': enable})
+
+    def qos_record_enable_disable(self, sw_if_index, input_source, enable):
+        """ IP QoS recording Enble/Disable """
+        return self.api(self.papi.qos_record_enable_disable,
+                        {'sw_if_index': sw_if_index,
+                         'input_source': input_source,
+                         'enable': enable})