udp: add udp encap source port entropy support

Encode entropy value in UDP source port when requested per RFC 7510.
CLI already has "src-port-is-entropy", use zero UDP source port in API
to avoid breaking changes, since zero port is not something to be used
in wild.
Also, mark UDP encapsualtion API as mp-safe as already done for CLI.

Type: feature
Change-Id: Ieb61ee11e058179ed566ff1f251a3391eb169d52
Signed-off-by: Vladislav Grishenko <themiron@yandex-team.ru>
diff --git a/src/plugins/lisp/lisp-gpe/lisp_gpe_adjacency.c b/src/plugins/lisp/lisp-gpe/lisp_gpe_adjacency.c
index dd8a252..7c857b9 100644
--- a/src/plugins/lisp/lisp-gpe/lisp_gpe_adjacency.c
+++ b/src/plugins/lisp/lisp-gpe/lisp_gpe_adjacency.c
@@ -285,7 +285,8 @@
 
   /* Fixup the checksum and len fields in the LISP tunnel encap
    * that was applied at the midchain node */
-  ip_udp_fixup_one (vm, b, is_v4_packet (vlib_buffer_get_current (b)));
+  ip_udp_fixup_one (vm, b, is_v4_packet (vlib_buffer_get_current (b)),
+		    UDP_ENCAP_FIXUP_NONE);
 }
 
 /**
diff --git a/src/vnet/udp/udp.api b/src/vnet/udp/udp.api
index 02176be..6b468be 100644
--- a/src/vnet/udp/udp.api
+++ b/src/vnet/udp/udp.api
@@ -32,7 +32,7 @@
  * @param dst_ip - Encap destination address
  * @param src_ip - Encap source address
  * @param dst_port - Encap destination port
- * @param src_port - Encap source port
+ * @param src_port - Encap source port, 0 for entopy per rfc7510
  * @param id - VPP assigned id; ignored in add message, set in dump
  */
 typedef udp_encap
diff --git a/src/vnet/udp/udp_api.c b/src/vnet/udp/udp_api.c
index 0f2d014..ae6b5bb 100644
--- a/src/vnet/udp/udp_api.c
+++ b/src/vnet/udp/udp_api.c
@@ -99,6 +99,7 @@
 {
   vl_api_udp_encap_add_reply_t *rmp;
   ip46_address_t src_ip, dst_ip;
+  udp_encap_fixup_flags_t flags;
   u32 fib_index, table_id;
   fib_protocol_t fproto;
   ip46_type_t itype;
@@ -119,11 +120,13 @@
       goto done;
     }
 
-  uei = udp_encap_add_and_lock (fproto, fib_index,
-				&src_ip, &dst_ip,
+  flags = UDP_ENCAP_FIXUP_NONE;
+  if (mp->udp_encap.src_port == 0)
+    flags |= UDP_ENCAP_FIXUP_UDP_SRC_PORT_ENTROPY;
+
+  uei = udp_encap_add_and_lock (fproto, fib_index, &src_ip, &dst_ip,
 				ntohs (mp->udp_encap.src_port),
-				ntohs (mp->udp_encap.dst_port),
-				UDP_ENCAP_FIXUP_NONE);
+				ntohs (mp->udp_encap.dst_port), flags);
 
 done:
   /* *INDENT-OFF* */
@@ -189,11 +192,19 @@
 static clib_error_t *
 udp_api_hookup (vlib_main_t * vm)
 {
+  api_main_t *am = vlibapi_get_main ();
+
   /*
    * Set up the (msg_name, crc, message-id) table
    */
   REPLY_MSG_ID_BASE = setup_message_id_table ();
 
+  /* Mark these APIs as mp safe */
+  vl_api_set_msg_thread_safe (am, REPLY_MSG_ID_BASE + VL_API_UDP_ENCAP_ADD, 1);
+  vl_api_set_msg_thread_safe (am, REPLY_MSG_ID_BASE + VL_API_UDP_ENCAP_DEL, 1);
+  vl_api_set_msg_thread_safe (am, REPLY_MSG_ID_BASE + VL_API_UDP_ENCAP_DUMP,
+			      1);
+
   return 0;
 }
 
diff --git a/src/vnet/udp/udp_encap.c b/src/vnet/udp/udp_encap.c
index a0f5a50..ac1f855 100644
--- a/src/vnet/udp/udp_encap.c
+++ b/src/vnet/udp/udp_encap.c
@@ -195,6 +195,20 @@
   fib_node_unlock (&ue->ue_fib_node);
 }
 
+u8 *
+format_udp_encap_fixup_flags (u8 *s, va_list *args)
+{
+  udp_encap_fixup_flags_t flags = va_arg (*args, udp_encap_fixup_flags_t);
+
+  if (flags == UDP_ENCAP_FIXUP_NONE)
+    return format (s, "none");
+
+  if (flags & UDP_ENCAP_FIXUP_UDP_SRC_PORT_ENTROPY)
+    s = format (s, "%s", "src-port-is-entropy");
+
+  return (s);
+}
+
 static u8 *
 format_udp_encap_i (u8 * s, va_list * args)
 {
@@ -210,23 +224,21 @@
   s = format (s, "udp-encap:[%d]: ip-fib-index:%d ", uei, ue->ue_fib_index);
   if (FIB_PROTOCOL_IP4 == ue->ue_ip_proto)
     {
-      s = format (s, "ip:[src:%U, dst:%U] udp:[src:%d, dst:%d]",
-		  format_ip4_address,
-		  &ue->ue_hdrs.ip4.ue_ip4.src_address,
-		  format_ip4_address,
-		  &ue->ue_hdrs.ip4.ue_ip4.dst_address,
+      s = format (s, "ip:[src:%U, dst:%U] udp:[src:%d, dst:%d] flags:%U",
+		  format_ip4_address, &ue->ue_hdrs.ip4.ue_ip4.src_address,
+		  format_ip4_address, &ue->ue_hdrs.ip4.ue_ip4.dst_address,
 		  clib_net_to_host_u16 (ue->ue_hdrs.ip4.ue_udp.src_port),
-		  clib_net_to_host_u16 (ue->ue_hdrs.ip4.ue_udp.dst_port));
+		  clib_net_to_host_u16 (ue->ue_hdrs.ip4.ue_udp.dst_port),
+		  format_udp_encap_fixup_flags, ue->ue_flags);
     }
   else
     {
-      s = format (s, "ip:[src:%U, dst:%U] udp:[src:%d dst:%d]",
-		  format_ip6_address,
-		  &ue->ue_hdrs.ip6.ue_ip6.src_address,
-		  format_ip6_address,
-		  &ue->ue_hdrs.ip6.ue_ip6.dst_address,
+      s = format (s, "ip:[src:%U, dst:%U] udp:[src:%d dst:%d] flags:%U",
+		  format_ip6_address, &ue->ue_hdrs.ip6.ue_ip6.src_address,
+		  format_ip6_address, &ue->ue_hdrs.ip6.ue_ip6.dst_address,
 		  clib_net_to_host_u16 (ue->ue_hdrs.ip6.ue_udp.src_port),
-		  clib_net_to_host_u16 (ue->ue_hdrs.ip6.ue_udp.dst_port));
+		  clib_net_to_host_u16 (ue->ue_hdrs.ip6.ue_udp.dst_port),
+		  format_udp_encap_fixup_flags, ue->ue_flags);
     }
   vlib_get_combined_counter (&(udp_encap_counters), uei, &to);
   s = format (s, " to:[%Ld:%Ld]]", to.packets, to.bytes);
@@ -553,10 +565,12 @@
 /* *INDENT-OFF* */
 VLIB_CLI_COMMAND (udp_encap_add_command, static) = {
   .path = "udp encap",
-  .short_help = "udp encap [add|del] <id ID> <src-ip> <dst-ip> [<src-port>] <dst-port>  [src-port-is-entropy] [table-id <table>]",
+  .short_help = "udp encap [add|del] <id ID> <src-ip> <dst-ip> [<src-port>] "
+		"<dst-port> [src-port-is-entropy] [table-id <table>]",
   .function = udp_encap_cli,
   .is_mp_safe = 1,
 };
+
 VLIB_CLI_COMMAND (udp_encap_show_command, static) = {
   .path = "show udp encap",
   .short_help = "show udp encap [ID]",
diff --git a/src/vnet/udp/udp_encap.h b/src/vnet/udp/udp_encap.h
index 648e3b5..c8b42ff 100644
--- a/src/vnet/udp/udp_encap.h
+++ b/src/vnet/udp/udp_encap.h
@@ -115,6 +115,7 @@
 extern void udp_encap_lock (index_t uei);
 extern void udp_encap_unlock (index_t uei);
 extern u8 *format_udp_encap (u8 * s, va_list * args);
+extern u8 *format_udp_encap_fixup_flags (u8 *s, va_list *args);
 extern void udp_encap_contribute_forwarding (index_t uei,
 					     dpo_proto_t proto,
 					     dpo_id_t * dpo);
diff --git a/src/vnet/udp/udp_encap_node.c b/src/vnet/udp/udp_encap_node.c
index 1ebe795..639ac23 100644
--- a/src/vnet/udp/udp_encap_node.c
+++ b/src/vnet/udp/udp_encap_node.c
@@ -20,12 +20,16 @@
 {
   udp_header_t udp;
   ip4_header_t ip;
+  u32 flow_hash;
+  udp_encap_fixup_flags_t flags;
 } udp4_encap_trace_t;
 
 typedef struct udp6_encap_trace_t_
 {
   udp_header_t udp;
   ip6_header_t ip;
+  u32 flow_hash;
+  udp_encap_fixup_flags_t flags;
 } udp6_encap_trace_t;
 
 extern vlib_combined_counter_main_t udp_encap_counters;
@@ -35,13 +39,16 @@
 {
   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  u32 indent = format_get_indent (s);
   udp4_encap_trace_t *t;
 
   t = va_arg (*args, udp4_encap_trace_t *);
 
-  s = format (s, "%U\n  %U",
-	      format_ip4_header, &t->ip, sizeof (t->ip),
-	      format_udp_header, &t->udp, sizeof (t->udp));
+  s = format (s, "flags: %U, flow hash: 0x%08x\n%U%U\n%U%U",
+	      format_udp_encap_fixup_flags, t->flags, t->flow_hash,
+	      format_white_space, indent, format_ip4_header, &t->ip,
+	      sizeof (t->ip), format_white_space, indent, format_udp_header,
+	      &t->udp, sizeof (t->udp));
   return (s);
 }
 
@@ -50,13 +57,16 @@
 {
   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
+  u32 indent = format_get_indent (s);
   udp6_encap_trace_t *t;
 
   t = va_arg (*args, udp6_encap_trace_t *);
 
-  s = format (s, "%U\n  %U",
-	      format_ip6_header, &t->ip, sizeof (t->ip),
-	      format_udp_header, &t->udp, sizeof (t->udp));
+  s = format (s, "flags: %U, flow hash: 0x%08x\n%U%U\n%U%U",
+	      format_udp_encap_fixup_flags, t->flags, t->flow_hash,
+	      format_white_space, indent, format_ip6_header, &t->ip,
+	      sizeof (t->ip), format_white_space, indent, format_udp_header,
+	      &t->udp, sizeof (t->udp));
   return (s);
 }
 
@@ -127,13 +137,16 @@
 		sizeof (udp_header_t) + sizeof (ip6_header_t);
 	      ip_udp_encap_two (vm, b0, b1, (u8 *) &ue0->ue_hdrs,
 				(u8 *) &ue1->ue_hdrs, n_bytes, encap_family,
-				payload_family);
+				payload_family, ue0->ue_flags, ue1->ue_flags);
+
 	      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
 		{
 		  udp6_encap_trace_t *tr =
 		    vlib_add_trace (vm, node, b0, sizeof (*tr));
 		  tr->udp = ue0->ue_hdrs.ip6.ue_udp;
 		  tr->ip = ue0->ue_hdrs.ip6.ue_ip6;
+		  tr->flags = ue0->ue_flags;
+		  tr->flow_hash = vnet_buffer (b0)->ip.flow_hash;
 		}
 	      if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED))
 		{
@@ -141,6 +154,8 @@
 		    vlib_add_trace (vm, node, b1, sizeof (*tr));
 		  tr->udp = ue1->ue_hdrs.ip6.ue_udp;
 		  tr->ip = ue1->ue_hdrs.ip6.ue_ip6;
+		  tr->flags = ue1->ue_flags;
+		  tr->flow_hash = vnet_buffer (b1)->ip.flow_hash;
 		}
 	    }
 	  else
@@ -150,7 +165,7 @@
 
 	      ip_udp_encap_two (vm, b0, b1, (u8 *) &ue0->ue_hdrs,
 				(u8 *) &ue1->ue_hdrs, n_bytes, encap_family,
-				payload_family);
+				payload_family, ue0->ue_flags, ue1->ue_flags);
 
 	      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
 		{
@@ -158,6 +173,8 @@
 		    vlib_add_trace (vm, node, b0, sizeof (*tr));
 		  tr->udp = ue0->ue_hdrs.ip4.ue_udp;
 		  tr->ip = ue0->ue_hdrs.ip4.ue_ip4;
+		  tr->flags = ue0->ue_flags;
+		  tr->flow_hash = vnet_buffer (b0)->ip.flow_hash;
 		}
 	      if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED))
 		{
@@ -165,6 +182,8 @@
 		    vlib_add_trace (vm, node, b1, sizeof (*tr));
 		  tr->udp = ue1->ue_hdrs.ip4.ue_udp;
 		  tr->ip = ue1->ue_hdrs.ip4.ue_ip4;
+		  tr->flags = ue1->ue_flags;
+		  tr->flow_hash = vnet_buffer (b1)->ip.flow_hash;
 		}
 	    }
 
@@ -208,7 +227,7 @@
 	      const u8 n_bytes =
 		sizeof (udp_header_t) + sizeof (ip6_header_t);
 	      ip_udp_encap_one (vm, b0, (u8 *) &ue0->ue_hdrs.ip6, n_bytes,
-				encap_family, payload_family);
+				encap_family, payload_family, ue0->ue_flags);
 
 	      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
 		{
@@ -216,6 +235,8 @@
 		    vlib_add_trace (vm, node, b0, sizeof (*tr));
 		  tr->udp = ue0->ue_hdrs.ip6.ue_udp;
 		  tr->ip = ue0->ue_hdrs.ip6.ue_ip6;
+		  tr->flags = ue0->ue_flags;
+		  tr->flow_hash = vnet_buffer (b0)->ip.flow_hash;
 		}
 	    }
 	  else
@@ -224,7 +245,7 @@
 		sizeof (udp_header_t) + sizeof (ip4_header_t);
 
 	      ip_udp_encap_one (vm, b0, (u8 *) &ue0->ue_hdrs.ip4, n_bytes,
-				encap_family, payload_family);
+				encap_family, payload_family, ue0->ue_flags);
 
 	      if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED))
 		{
@@ -232,6 +253,8 @@
 		    vlib_add_trace (vm, node, b0, sizeof (*tr));
 		  tr->udp = ue0->ue_hdrs.ip4.ue_udp;
 		  tr->ip = ue0->ue_hdrs.ip4.ue_ip4;
+		  tr->flags = ue0->ue_flags;
+		  tr->flow_hash = vnet_buffer (b0)->ip.flow_hash;
 		}
 	    }
 
diff --git a/src/vnet/udp/udp_inlines.h b/src/vnet/udp/udp_inlines.h
index 025809e..f0dd44f 100644
--- a/src/vnet/udp/udp_inlines.h
+++ b/src/vnet/udp/udp_inlines.h
@@ -21,6 +21,9 @@
 #include <vnet/ip/ip6.h>
 #include <vnet/udp/udp_packet.h>
 #include <vnet/interface_output.h>
+#include <vnet/ip/ip4_inlines.h>
+#include <vnet/ip/ip6_inlines.h>
+#include <vnet/udp/udp_encap.h>
 
 always_inline void *
 vlib_buffer_push_udp (vlib_buffer_t * b, u16 sp, u16 dp, u8 offload_csum)
@@ -42,8 +45,39 @@
   return uh;
 }
 
+/*
+ * Encode udp source port entropy value per
+ * https://datatracker.ietf.org/doc/html/rfc7510#section-3
+ */
+always_inline u16
+ip_udp_sport_entropy (vlib_buffer_t *b0)
+{
+  u16 port = clib_host_to_net_u16 (0x03 << 14);
+  port |= vnet_buffer (b0)->ip.flow_hash & 0xffff;
+  return port;
+}
+
+always_inline u32
+ip_udp_compute_flow_hash (vlib_buffer_t *b0, u8 is_ip4)
+{
+  ip4_header_t *ip4;
+  ip6_header_t *ip6;
+
+  if (is_ip4)
+    {
+      ip4 = (ip4_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset);
+      return ip4_compute_flow_hash (ip4, IP_FLOW_HASH_DEFAULT);
+    }
+  else
+    {
+      ip6 = (ip6_header_t *) (b0->data + vnet_buffer (b0)->l3_hdr_offset);
+      return ip6_compute_flow_hash (ip6, IP_FLOW_HASH_DEFAULT);
+    }
+}
+
 always_inline void
-ip_udp_fixup_one (vlib_main_t * vm, vlib_buffer_t * b0, u8 is_ip4)
+ip_udp_fixup_one (vlib_main_t *vm, vlib_buffer_t *b0, u8 is_ip4,
+		  u8 sport_entropy)
 {
   u16 new_l0;
   udp_header_t *udp0;
@@ -71,6 +105,9 @@
       new_l0 = clib_host_to_net_u16 (vlib_buffer_length_in_chain (vm, b0)
 				     - sizeof (*ip0));
       udp0->length = new_l0;
+
+      if (sport_entropy)
+	udp0->src_port = ip_udp_sport_entropy (b0);
     }
   else
     {
@@ -87,6 +124,9 @@
       udp0 = (udp_header_t *) (ip0 + 1);
       udp0->length = new_l0;
 
+      if (sport_entropy)
+	udp0->src_port = ip_udp_sport_entropy (b0);
+
       udp0->checksum =
 	ip6_tcp_udp_icmp_compute_checksum (vm, b0, ip0, &bogus0);
       ASSERT (bogus0 == 0);
@@ -99,13 +139,20 @@
 always_inline void
 ip_udp_encap_one (vlib_main_t *vm, vlib_buffer_t *b0, u8 *ec0, word ec_len,
 		  ip_address_family_t encap_family,
-		  ip_address_family_t payload_family)
+		  ip_address_family_t payload_family,
+		  udp_encap_fixup_flags_t flags)
 {
+  u8 sport_entropy = (flags & UDP_ENCAP_FIXUP_UDP_SRC_PORT_ENTROPY) != 0;
 
   if (payload_family < N_AF)
     {
       vnet_calc_checksums_inline (vm, b0, payload_family == AF_IP4,
 				  payload_family == AF_IP6);
+
+      /* Сalculate flow hash to be used for entropy */
+      if (sport_entropy && 0 == vnet_buffer (b0)->ip.flow_hash)
+	vnet_buffer (b0)->ip.flow_hash =
+	  ip_udp_compute_flow_hash (b0, payload_family == AF_IP4);
     }
 
   vlib_buffer_advance (b0, -ec_len);
@@ -118,7 +165,7 @@
 
       /* Apply the encap string. */
       clib_memcpy_fast (ip0, ec0, ec_len);
-      ip_udp_fixup_one (vm, b0, 1);
+      ip_udp_fixup_one (vm, b0, 1, sport_entropy);
     }
   else
     {
@@ -128,7 +175,7 @@
 
       /* Apply the encap string. */
       clib_memcpy_fast (ip0, ec0, ec_len);
-      ip_udp_fixup_one (vm, b0, 0);
+      ip_udp_fixup_one (vm, b0, 0, sport_entropy);
     }
 }
 
@@ -136,16 +183,28 @@
 ip_udp_encap_two (vlib_main_t *vm, vlib_buffer_t *b0, vlib_buffer_t *b1,
 		  u8 *ec0, u8 *ec1, word ec_len,
 		  ip_address_family_t encap_family,
-		  ip_address_family_t payload_family)
+		  ip_address_family_t payload_family,
+		  udp_encap_fixup_flags_t flags0,
+		  udp_encap_fixup_flags_t flags1)
 {
   u16 new_l0, new_l1;
   udp_header_t *udp0, *udp1;
   int payload_ip4 = (payload_family == AF_IP4);
+  int sport_entropy0 = (flags0 & UDP_ENCAP_FIXUP_UDP_SRC_PORT_ENTROPY) != 0;
+  int sport_entropy1 = (flags1 & UDP_ENCAP_FIXUP_UDP_SRC_PORT_ENTROPY) != 0;
 
   if (payload_family < N_AF)
     {
       vnet_calc_checksums_inline (vm, b0, payload_ip4, !payload_ip4);
       vnet_calc_checksums_inline (vm, b1, payload_ip4, !payload_ip4);
+
+      /* Сalculate flow hash to be used for entropy */
+      if (sport_entropy0 && 0 == vnet_buffer (b0)->ip.flow_hash)
+	vnet_buffer (b0)->ip.flow_hash =
+	  ip_udp_compute_flow_hash (b0, payload_ip4);
+      if (sport_entropy1 && 0 == vnet_buffer (b1)->ip.flow_hash)
+	vnet_buffer (b1)->ip.flow_hash =
+	  ip_udp_compute_flow_hash (b1, payload_ip4);
     }
 
   vlib_buffer_advance (b0, -ec_len);
@@ -195,6 +254,11 @@
 			      sizeof (*ip1));
       udp0->length = new_l0;
       udp1->length = new_l1;
+
+      if (sport_entropy0)
+	udp0->src_port = ip_udp_sport_entropy (b0);
+      if (sport_entropy1)
+	udp1->src_port = ip_udp_sport_entropy (b1);
     }
   else
     {
@@ -222,6 +286,11 @@
       udp0->length = new_l0;
       udp1->length = new_l1;
 
+      if (sport_entropy0)
+	udp0->src_port = ip_udp_sport_entropy (b0);
+      if (sport_entropy1)
+	udp1->src_port = ip_udp_sport_entropy (b1);
+
       udp0->checksum =
 	ip6_tcp_udp_icmp_compute_checksum (vm, b0, ip0, &bogus0);
       udp1->checksum =
diff --git a/src/vnet/vxlan-gpe/encap.c b/src/vnet/vxlan-gpe/encap.c
index 35a5529..824f10f 100644
--- a/src/vnet/vxlan-gpe/encap.c
+++ b/src/vnet/vxlan-gpe/encap.c
@@ -96,7 +96,7 @@
   ASSERT (sizeof (ip6_vxlan_gpe_header_t) == 56);
 
   ip_udp_encap_one (ngm->vlib_main, b0, t0->rewrite, t0->rewrite_size, af,
-		    N_AF);
+		    N_AF, UDP_ENCAP_FIXUP_NONE);
   next0[0] = t0->encap_next_node;
 }
 
@@ -123,9 +123,9 @@
   ASSERT (sizeof (ip6_vxlan_gpe_header_t) == 56);
 
   ip_udp_encap_one (ngm->vlib_main, b0, t0->rewrite, t0->rewrite_size, af,
-		    N_AF);
+		    N_AF, UDP_ENCAP_FIXUP_NONE);
   ip_udp_encap_one (ngm->vlib_main, b1, t1->rewrite, t1->rewrite_size, af,
-		    N_AF);
+		    N_AF, UDP_ENCAP_FIXUP_NONE);
   next0[0] = next1[0] = t0->encap_next_node;
 }