vcl: add QUIC support

Type: feature

* Adds the concept of a "connectable listener" : a session that
 can be both connected and accepted on.
* vppcom_session_is_connectable_listener (fd) that tells if the fd
is a connectable listener
* vppcom_session_listener (fd) that gives you the listener's fd
that accepted the session (if any)
* vppcom_session_n_accepted (fd) that gives the number
of sessions a listener accepted.

Change-Id: Id89d67d8339fb15a7cf7e00a9c5448175eca04fc
Signed-off-by: Nathan Skrzypczak <nathan.skrzypczak@gmail.com>
diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h
index 544b288..d46208c 100644
--- a/src/vcl/vcl_private.h
+++ b/src/vcl/vcl_private.h
@@ -167,6 +167,10 @@
   /* Socket configuration state */
   u8 is_vep;
   u8 is_vep_session;
+  /* VCL session index of the listening session (if any) */
+  u32 listener_index;
+  /* Accepted sessions on this listener */
+  int n_accepted_sessions;
   u8 has_rx_evt;
   u32 attr;
   u64 transport_opts;
@@ -352,6 +356,7 @@
   pool_get (wrk->sessions, s);
   memset (s, 0, sizeof (*s));
   s->session_index = s - wrk->sessions;
+  s->listener_index = VCL_INVALID_SESSION_INDEX;
   return s;
 }
 
@@ -447,6 +452,26 @@
   hash_unset (wrk->session_index_by_vpp_handles, listener_handle);
 }
 
+static inline int
+vcl_session_is_connectable_listener (vcl_worker_t * wrk,
+				     vcl_session_t * session)
+{
+  /* Tell if we session_handle is a QUIC session.
+   * We can be in the following cases :
+   * Listen session <- QUIC session <- Stream session
+   * QUIC session <- Stream session
+   */
+  vcl_session_t *ls;
+  if (session->session_type != VPPCOM_PROTO_QUIC)
+    return 0;
+  if (session->listener_index == VCL_INVALID_SESSION_INDEX)
+    return !(session->session_state & STATE_LISTEN);
+  ls = vcl_session_get_w_handle (wrk, session->listener_index);
+  if (!ls)
+    return VPPCOM_EBADFD;
+  return ls->session_state & STATE_LISTEN;
+}
+
 static inline vcl_session_t *
 vcl_session_table_lookup_listener (vcl_worker_t * wrk, u64 handle)
 {
@@ -467,7 +492,8 @@
       return 0;
     }
 
-  ASSERT (session->session_state & (STATE_LISTEN | STATE_LISTEN_NO_MQ));
+  ASSERT ((session->session_state & (STATE_LISTEN | STATE_LISTEN_NO_MQ)) ||
+	  vcl_session_is_connectable_listener (wrk, session));
   return session;
 }
 
diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c
index dcbbfc4..3205a81 100644
--- a/src/vcl/vppcom.c
+++ b/src/vcl/vppcom.c
@@ -312,7 +312,9 @@
   session->transport.lcl_port = listen_session->transport.lcl_port;
   session->transport.lcl_ip = listen_session->transport.lcl_ip;
   session->session_type = listen_session->session_type;
-  session->is_dgram = session->session_type == VPPCOM_PROTO_UDP;
+  session->is_dgram = vcl_proto_is_dgram (session->session_type);
+  session->listener_index = listen_session->session_index;
+  listen_session->n_accepted_sessions++;
 
   VDBG (1, "session %u [0x%llx]: client accept request from %s address %U"
 	" port %d queue %p!", session->session_index, mp->handle,
@@ -860,7 +862,7 @@
 {
   vcl_worker_t *wrk = vcl_worker_get_current ();
   svm_msg_q_t *vpp_evt_q;
-  vcl_session_t *session;
+  vcl_session_t *session, *listen_session;
   vcl_session_state_t state;
   u64 vpp_handle;
 
@@ -895,6 +897,12 @@
       vppcom_send_disconnect_session (vpp_handle);
     }
 
+  if (session->listener_index != VCL_INVALID_SESSION_INDEX)
+    {
+      listen_session = vcl_session_get (wrk, session->listener_index);
+      listen_session->n_accepted_sessions--;
+    }
+
   return VPPCOM_OK;
 }
 
@@ -1029,7 +1037,7 @@
   session->session_type = proto;
   session->session_state = STATE_START;
   session->vpp_handle = ~0;
-  session->is_dgram = proto == VPPCOM_PROTO_UDP;
+  session->is_dgram = vcl_proto_is_dgram (proto);
 
   if (is_nonblocking)
     VCL_SESS_ATTR_SET (session->attr, VCL_SESS_ATTR_NONBLOCK);
@@ -1100,7 +1108,8 @@
 		  vppcom_retval_str (rv));
 	  return rv;
 	}
-      else if (state & STATE_OPEN)
+      else if ((state & STATE_OPEN)
+	       || (vcl_session_is_connectable_listener (wrk, session)))
 	{
 	  rv = vppcom_session_disconnect (sh);
 	  if (PREDICT_FALSE (rv < 0))
@@ -1206,8 +1215,7 @@
       return VPPCOM_OK;
     }
 
-  VDBG (0, "session %u [0x%llx]: sending vpp listen request...",
-	listen_sh, listen_vpp_handle);
+  VDBG (0, "session %u: sending vpp listen request...", listen_sh);
 
   /*
    * Send listen request to vpp and wait for reply
@@ -1288,10 +1296,12 @@
       return VPPCOM_EBADFD;
     }
 
-  if (ls->session_state != STATE_LISTEN)
+  if ((ls->session_state != STATE_LISTEN)
+      && (!vcl_session_is_connectable_listener (wrk, ls)))
     {
-      VDBG (0, "ERROR: session [0x%llx]: not in listen state! state 0x%x"
-	    " (%s)", ls->vpp_handle, ls->session_index, ls->session_state,
+      VDBG (0,
+	    "ERROR: session [0x%llx]: not in listen state! state 0x%x"
+	    " (%s)", ls->vpp_handle, ls->session_state,
 	    vppcom_session_state_str (ls->session_state));
       return VPPCOM_EBADFD;
     }
@@ -1299,6 +1309,38 @@
 }
 
 int
+vppcom_unformat_proto (uint8_t * proto, char *proto_str)
+{
+  if (!strcmp (proto_str, "TCP"))
+    *proto = VPPCOM_PROTO_TCP;
+  else if (!strcmp (proto_str, "tcp"))
+    *proto = VPPCOM_PROTO_TCP;
+  else if (!strcmp (proto_str, "UDP"))
+    *proto = VPPCOM_PROTO_UDP;
+  else if (!strcmp (proto_str, "udp"))
+    *proto = VPPCOM_PROTO_UDP;
+  else if (!strcmp (proto_str, "UDPC"))
+    *proto = VPPCOM_PROTO_UDPC;
+  else if (!strcmp (proto_str, "udpc"))
+    *proto = VPPCOM_PROTO_UDPC;
+  else if (!strcmp (proto_str, "SCTP"))
+    *proto = VPPCOM_PROTO_SCTP;
+  else if (!strcmp (proto_str, "sctp"))
+    *proto = VPPCOM_PROTO_SCTP;
+  else if (!strcmp (proto_str, "TLS"))
+    *proto = VPPCOM_PROTO_TLS;
+  else if (!strcmp (proto_str, "tls"))
+    *proto = VPPCOM_PROTO_TLS;
+  else if (!strcmp (proto_str, "QUIC"))
+    *proto = VPPCOM_PROTO_QUIC;
+  else if (!strcmp (proto_str, "quic"))
+    *proto = VPPCOM_PROTO_QUIC;
+  else
+    return 1;
+  return 0;
+}
+
+int
 vppcom_session_accept (uint32_t listen_session_handle, vppcom_endpt_t * ep,
 		       uint32_t flags)
 {
@@ -1432,8 +1474,7 @@
       VDBG (0, "session handle %u [0x%llx]: session already "
 	    "connected to %s %U port %d proto %s, state 0x%x (%s)",
 	    session_handle, session->vpp_handle,
-	    session->transport.is_ip4 ? "IPv4" : "IPv6",
-	    format_ip46_address,
+	    session->transport.is_ip4 ? "IPv4" : "IPv6", format_ip46_address,
 	    &session->transport.rmt_ip, session->transport.is_ip4 ?
 	    IP46_TYPE_IP4 : IP46_TYPE_IP6,
 	    clib_net_to_host_u16 (session->transport.rmt_port),
@@ -1450,9 +1491,10 @@
     clib_memcpy_fast (&session->transport.rmt_ip.ip6, server_ep->ip,
 		      sizeof (ip6_address_t));
   session->transport.rmt_port = server_ep->port;
+  session->transport_opts = VCL_INVALID_SESSION_HANDLE;
 
-  VDBG (0, "session handle %u [0x%llx]: connecting to server %s %U "
-	"port %d proto %s", session_handle, session->vpp_handle,
+  VDBG (0, "session handle %u: connecting to server %s %U "
+	"port %d proto %s", session_handle,
 	session->transport.is_ip4 ? "IPv4" : "IPv6",
 	format_ip46_address,
 	&session->transport.rmt_ip, session->transport.is_ip4 ?
@@ -1474,6 +1516,69 @@
   return rv;
 }
 
+int
+vppcom_session_stream_connect (uint32_t session_handle,
+			       uint32_t parent_session_handle)
+{
+  vcl_worker_t *wrk = vcl_worker_get_current ();
+  vcl_session_t *session, *parent_session;
+  u32 session_index, parent_session_index;
+  int rv;
+
+  session = vcl_session_get_w_handle (wrk, session_handle);
+  if (!session)
+    return VPPCOM_EBADFD;
+  parent_session = vcl_session_get_w_handle (wrk, parent_session_handle);
+  if (!parent_session)
+    return VPPCOM_EBADFD;
+
+  session_index = session->session_index;
+  parent_session_index = parent_session->session_index;
+  if (PREDICT_FALSE (session->is_vep))
+    {
+      VDBG (0, "ERROR: cannot connect epoll session %u!",
+	    session->session_index);
+      return VPPCOM_EBADFD;
+    }
+
+  if (PREDICT_FALSE (session->session_state & CLIENT_STATE_OPEN))
+    {
+      VDBG (0, "session handle %u [0x%llx]: session already "
+	    "connected to session %u [0x%llx] proto %s, state 0x%x (%s)",
+	    session_handle, session->vpp_handle,
+	    parent_session_handle, parent_session->vpp_handle,
+	    vppcom_proto_str (session->session_type), session->session_state,
+	    vppcom_session_state_str (session->session_state));
+      return VPPCOM_OK;
+    }
+
+  /* Connect to quic session specifics */
+  session->transport.is_ip4 = parent_session->transport.is_ip4;
+  session->transport.rmt_ip.ip4.as_u32 = (uint32_t) 1;
+  session->transport.rmt_port = 0;
+  session->transport_opts = parent_session->vpp_handle;
+
+  VDBG (0, "session handle %u: connecting to session %u [0x%llx]",
+	session_handle, parent_session_handle, parent_session->vpp_handle);
+
+  /*
+   * Send connect request and wait for reply from vpp
+   */
+  vppcom_send_connect_sock (session);
+  rv = vppcom_wait_for_session_state_change (session_index, STATE_CONNECT,
+					     vcm->cfg.session_timeout);
+
+  session->listener_index = parent_session_index;
+  parent_session = vcl_session_get_w_handle (wrk, parent_session_handle);
+  parent_session->n_accepted_sessions++;
+
+  session = vcl_session_get (wrk, session_index);
+  VDBG (0, "session %u [0x%llx]: connect %s!", session->session_index,
+	session->vpp_handle, rv ? "failed" : "succeeded");
+
+  return rv;
+}
+
 static u8
 vcl_is_rx_evt_for_session (session_event_t * e, u32 sid, u8 is_ct)
 {
@@ -3379,6 +3484,43 @@
   return wrk->mqs_epfd;
 }
 
+int
+vppcom_session_is_connectable_listener (uint32_t session_handle)
+{
+  vcl_session_t *session;
+  vcl_worker_t *wrk = vcl_worker_get_current ();
+  session = vcl_session_get_w_handle (wrk, session_handle);
+  if (!session)
+    return VPPCOM_EBADFD;
+  return vcl_session_is_connectable_listener (wrk, session);
+}
+
+int
+vppcom_session_listener (uint32_t session_handle)
+{
+  vcl_worker_t *wrk = vcl_worker_get_current ();
+  vcl_session_t *listen_session, *session;
+  session = vcl_session_get_w_handle (wrk, session_handle);
+  if (!session)
+    return VPPCOM_EBADFD;
+  if (session->listener_index == VCL_INVALID_SESSION_INDEX)
+    return VPPCOM_EBADFD;
+  listen_session = vcl_session_get_w_handle (wrk, session->listener_index);
+  if (!listen_session)
+    return VPPCOM_EBADFD;
+  return vcl_session_handle (listen_session);
+}
+
+int
+vppcom_session_n_accepted (uint32_t session_handle)
+{
+  vcl_worker_t *wrk = vcl_worker_get_current ();
+  vcl_session_t *session = vcl_session_get_w_handle (wrk, session_handle);
+  if (!session)
+    return VPPCOM_EBADFD;
+  return session->n_accepted_sessions;
+}
+
 /*
  * fd.io coding-style-patch-verification: ON
  *
diff --git a/src/vcl/vppcom.h b/src/vcl/vppcom.h
index 6dfdd26..b05eae7 100644
--- a/src/vcl/vppcom.h
+++ b/src/vcl/vppcom.h
@@ -51,7 +51,8 @@
   VPPCOM_PROTO_SCTP,
   VPPCOM_PROTO_NONE,
   VPPCOM_PROTO_TLS,
-  VPPCOM_PROTO_UDPC
+  VPPCOM_PROTO_UDPC,
+  VPPCOM_PROTO_QUIC,
 } vppcom_proto_t;
 
 static inline char *
@@ -76,6 +77,9 @@
     case VPPCOM_PROTO_UDPC:
       proto_str = "UDPC";
       break;
+    case VPPCOM_PROTO_QUIC:
+      proto_str = "QUIC";
+      break;
     default:
       proto_str = "UNKNOWN";
       break;
@@ -83,6 +87,12 @@
   return proto_str;
 }
 
+static inline int
+vcl_proto_is_dgram (uint8_t proto)
+{
+  return proto == VPPCOM_PROTO_UDP || proto == VPPCOM_PROTO_UDPC;
+}
+
 typedef enum
 {
   VPPCOM_IS_IP6 = 0,
@@ -95,6 +105,7 @@
   uint8_t is_ip4;
   uint8_t *ip;
   uint16_t port;
+  uint64_t transport_opts;
 } vppcom_endpt_t;
 
 typedef uint32_t vcl_session_handle_t;
@@ -254,6 +265,8 @@
 
 extern int vppcom_session_connect (uint32_t session_handle,
 				   vppcom_endpt_t * server_ep);
+extern int vppcom_session_stream_connect (uint32_t session_handle,
+					  uint32_t parent_session_handle);
 extern int vppcom_session_read (uint32_t session_handle, void *buf, size_t n);
 extern int vppcom_session_write (uint32_t session_handle, void *buf,
 				 size_t n);
@@ -294,6 +307,10 @@
 				       uint32_t key_len);
 extern int vppcom_data_segment_copy (void *buf, vppcom_data_segments_t ds,
 				     uint32_t max_bytes);
+extern int vppcom_unformat_proto (uint8_t * proto, char *proto_str);
+extern int vppcom_session_is_connectable_listener (uint32_t session_handle);
+extern int vppcom_session_listener (uint32_t session_handle);
+extern int vppcom_session_n_accepted (uint32_t session_handle);
 
 /**
  * Request from application to register a new worker