session tcp vcl: api to update connection attributes

Type: feature

Signed-off-by: Florin Coras <fcoras@cisco.com>
Change-Id: Ifdd6024daf044751895bb8d2deabad41d3a80c92
diff --git a/src/vcl/vcl_private.h b/src/vcl/vcl_private.h
index 1ac9691..34e05f2 100644
--- a/src/vcl/vcl_private.h
+++ b/src/vcl/vcl_private.h
@@ -165,7 +165,6 @@
 
   u32 sndbuf_size;		// VPP-TBD: Hack until support setsockopt(SO_SNDBUF)
   u32 rcvbuf_size;		// VPP-TBD: Hack until support setsockopt(SO_RCVBUF)
-  u32 user_mss;			// VPP-TBD: Hack until support setsockopt(TCP_MAXSEG)
 
 #if VCL_ELOG
   elog_track_t elog_track;
@@ -292,6 +291,10 @@
   volatile vcl_bapi_app_state_t bapi_app_state;
   volatile uword bapi_return;
 
+  u8 session_attr_op;
+  int session_attr_op_rv;
+  transport_endpt_attr_t session_attr_rv;
+
   /** vcl needs next epoll_create to go to libc_epoll */
   u8 vcl_needs_real_epoll;
   volatile int rpc_done;
diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c
index 3e4ba2f..ce67889 100644
--- a/src/vcl/vppcom.c
+++ b/src/vcl/vppcom.c
@@ -360,6 +360,42 @@
   return ret;
 }
 
+int
+vcl_session_transport_attr (vcl_worker_t *wrk, vcl_session_t *s, u8 is_get,
+			    transport_endpt_attr_t *attr)
+{
+  app_session_evt_t _app_evt, *app_evt = &_app_evt;
+  session_transport_attr_msg_t *mp;
+  svm_msg_q_t *mq;
+  f64 timeout;
+
+  ASSERT (!wrk->session_attr_op);
+  wrk->session_attr_op = 1;
+  wrk->session_attr_op_rv = -1;
+
+  mq = s->vpp_evt_q;
+  app_alloc_ctrl_evt_to_vpp (mq, app_evt, SESSION_CTRL_EVT_TRANSPORT_ATTR);
+  mp = (session_transport_attr_msg_t *) app_evt->evt->data;
+  memset (mp, 0, sizeof (*mp));
+  mp->client_index = wrk->api_client_handle;
+  mp->handle = s->vpp_handle;
+  mp->is_get = is_get;
+  mp->attr = *attr;
+  app_send_ctrl_evt_to_vpp (mq, app_evt);
+
+  timeout = clib_time_now (&wrk->clib_time) + 1;
+
+  while (wrk->session_attr_op && clib_time_now (&wrk->clib_time) < timeout)
+    vcl_flush_mq_events ();
+
+  if (!wrk->session_attr_op_rv && is_get)
+    *attr = wrk->session_attr_rv;
+
+  wrk->session_attr_op = 0;
+
+  return wrk->session_attr_op_rv;
+}
+
 static u32
 vcl_session_accepted_handler (vcl_worker_t * wrk, session_accepted_msg_t * mp,
 			      u32 ls_index)
@@ -936,6 +972,21 @@
   (vcm->wrk_rpc_fn) (((session_app_wrk_rpc_msg_t *) data)->data);
 }
 
+static void
+vcl_session_transport_attr_reply_handler (vcl_worker_t *wrk, void *data)
+{
+  session_transport_attr_reply_msg_t *mp;
+
+  if (!wrk->session_attr_op)
+    return;
+
+  mp = (session_transport_attr_reply_msg_t *) data;
+
+  wrk->session_attr_op_rv = mp->retval;
+  wrk->session_attr_op = 0;
+  wrk->session_attr_rv = mp->attr;
+}
+
 static int
 vcl_handle_mq_event (vcl_worker_t * wrk, session_event_t * e)
 {
@@ -1031,6 +1082,9 @@
     case SESSION_CTRL_EVT_APP_WRK_RPC:
       vcl_worker_rpc_handler (wrk, e->data);
       break;
+    case SESSION_CTRL_EVT_TRANSPORT_ATTR_REPLY:
+      vcl_session_transport_attr_reply_handler (wrk, e->data);
+      break;
     default:
       clib_warning ("unhandled %u", e->event_type);
     }
@@ -3061,6 +3115,7 @@
   vcl_worker_t *wrk = vcl_worker_get_current ();
   u32 *flags = buffer, tmp_flags = 0;
   vppcom_endpt_t *ep = buffer;
+  transport_endpt_attr_t tea;
   vcl_session_t *session;
   int rv = VPPCOM_OK;
 
@@ -3559,30 +3614,41 @@
       break;
 
     case VPPCOM_ATTR_GET_TCP_USER_MSS:
-      if (buffer && buflen && (*buflen >= sizeof (u32)))
+      if (!(buffer && buflen && (*buflen >= sizeof (u32))))
 	{
-	  /* VPP-TBD */
-	  *(u32 *) buffer = session->user_mss;
-	  *buflen = sizeof (int);
-
-	  VDBG (2, "VPPCOM_ATTR_GET_TCP_USER_MSS: %d, buflen %d, #VPP-TBD#",
-		*(int *) buffer, *buflen);
+	  rv = VPPCOM_EINVAL;
+	  break;
 	}
-      else
-	rv = VPPCOM_EINVAL;
+
+      tea.type = TRANSPORT_ENDPT_ATTR_MSS;
+      tea.mss = *(u32 *) buffer;
+      if (vcl_session_transport_attr (wrk, session, 1 /* is_get */, &tea))
+	rv = VPPCOM_ENOPROTOOPT;
+
+      if (!rv)
+	{
+	  *(u32 *) buffer = tea.mss;
+	  *buflen = sizeof (int);
+	}
+
+      VDBG (2, "VPPCOM_ATTR_GET_TCP_USER_MSS: %d, buflen %d", *(int *) buffer,
+	    *buflen);
       break;
 
     case VPPCOM_ATTR_SET_TCP_USER_MSS:
-      if (buffer && buflen && (*buflen == sizeof (u32)))
+      if (!(buffer && buflen && (*buflen == sizeof (u32))))
 	{
-	  /* VPP-TBD */
-	  session->user_mss = *(u32 *) buffer;
-
-	  VDBG (2, "VPPCOM_ATTR_SET_TCP_USER_MSS: %u, buflen %d, #VPP-TBD#",
-		session->user_mss, *buflen);
+	  rv = VPPCOM_EINVAL;
+	  break;
 	}
-      else
-	rv = VPPCOM_EINVAL;
+
+      tea.type = TRANSPORT_ENDPT_ATTR_MSS;
+      tea.mss = *(u32 *) buffer;
+      if (vcl_session_transport_attr (wrk, session, 0 /* is_get */, &tea))
+	rv = VPPCOM_ENOPROTOOPT;
+
+      VDBG (2, "VPPCOM_ATTR_SET_TCP_USER_MSS: %u, buflen %d", tea.mss,
+	    *buflen);
       break;
 
     case VPPCOM_ATTR_SET_SHUT:
diff --git a/src/vcl/vppcom.h b/src/vcl/vppcom.h
index c808829..d956b5f 100644
--- a/src/vcl/vppcom.h
+++ b/src/vcl/vppcom.h
@@ -96,7 +96,8 @@
   VPPCOM_ENOTCONN = -ENOTCONN,
   VPPCOM_ECONNREFUSED = -ECONNREFUSED,
   VPPCOM_ETIMEDOUT = -ETIMEDOUT,
-  VPPCOM_EEXIST = -EEXIST
+  VPPCOM_EEXIST = -EEXIST,
+  VPPCOM_ENOPROTOOPT = -ENOPROTOOPT
 } vppcom_error_t;
 
 typedef enum
diff --git a/src/vnet/session/application_interface.h b/src/vnet/session/application_interface.h
index 2683e35..3f333af 100644
--- a/src/vnet/session/application_interface.h
+++ b/src/vnet/session/application_interface.h
@@ -535,6 +535,22 @@
   u8 data[64];		/**< rpc data */
 } __clib_packed session_app_wrk_rpc_msg_t;
 
+typedef struct session_transport_attr_msg_
+{
+  u32 client_index;
+  session_handle_t handle;
+  transport_endpt_attr_t attr;
+  u8 is_get;
+} __clib_packed session_transport_attr_msg_t;
+
+typedef struct session_transport_attr_reply_msg_
+{
+  i32 retval;
+  session_handle_t handle;
+  transport_endpt_attr_t attr;
+  u8 is_get;
+} __clib_packed session_transport_attr_reply_msg_t;
+
 typedef struct app_session_event_
 {
   svm_msg_q_msg_t msg;
diff --git a/src/vnet/session/session.c b/src/vnet/session/session.c
index 7513aa3..fd52f2b 100644
--- a/src/vnet/session/session.c
+++ b/src/vnet/session/session.c
@@ -1661,6 +1661,18 @@
 					    s->connection_index, tep, is_lcl);
 }
 
+int
+session_transport_attribute (session_t *s, u8 is_get,
+			     transport_endpt_attr_t *attr)
+{
+  if (s->session_state < SESSION_STATE_READY)
+    return -1;
+
+  return transport_connection_attribute (session_get_transport_proto (s),
+					 s->connection_index, s->thread_index,
+					 is_get, attr);
+}
+
 transport_connection_t *
 listen_session_get_transport (session_t * s)
 {
diff --git a/src/vnet/session/session.h b/src/vnet/session/session.h
index a05ecb4..44cbaeb 100644
--- a/src/vnet/session/session.h
+++ b/src/vnet/session/session.h
@@ -477,6 +477,8 @@
 transport_connection_t *session_get_transport (session_t * s);
 void session_get_endpoint (session_t * s, transport_endpoint_t * tep,
 			   u8 is_lcl);
+int session_transport_attribute (session_t *s, u8 is_get,
+				 transport_endpt_attr_t *attr);
 
 u8 *format_session (u8 * s, va_list * args);
 uword unformat_session (unformat_input_t * input, va_list * args);
diff --git a/src/vnet/session/session_node.c b/src/vnet/session/session_node.c
index d40411c..22ab3e8 100644
--- a/src/vnet/session/session_node.c
+++ b/src/vnet/session/session_node.c
@@ -520,6 +520,51 @@
   svm_msg_q_add_and_unlock (app_wrk->event_queue, msg);
 }
 
+static void
+session_mq_transport_attr_handler (void *data)
+{
+  session_transport_attr_msg_t *mp = (session_transport_attr_msg_t *) data;
+  session_transport_attr_reply_msg_t *rmp;
+  svm_msg_q_msg_t _msg, *msg = &_msg;
+  app_worker_t *app_wrk;
+  session_event_t *evt;
+  application_t *app;
+  session_t *s;
+  int rv;
+
+  app = application_lookup (mp->client_index);
+  if (!app)
+    return;
+
+  if (!(s = session_get_from_handle_if_valid (mp->handle)))
+    {
+      clib_warning ("invalid handle %llu", mp->handle);
+      return;
+    }
+  app_wrk = app_worker_get (s->app_wrk_index);
+  if (app_wrk->app_index != app->app_index)
+    {
+      clib_warning ("app %u does not own session %llu", app->app_index,
+		    mp->handle);
+      return;
+    }
+
+  rv = session_transport_attribute (s, mp->is_get, &mp->attr);
+
+  svm_msg_q_lock_and_alloc_msg_w_ring (
+    app_wrk->event_queue, SESSION_MQ_CTRL_EVT_RING, SVM_Q_WAIT, msg);
+  evt = svm_msg_q_msg_data (app_wrk->event_queue, msg);
+  clib_memset (evt, 0, sizeof (*evt));
+  evt->event_type = SESSION_CTRL_EVT_TRANSPORT_ATTR_REPLY;
+  rmp = (session_transport_attr_reply_msg_t *) evt->data;
+  rmp->handle = mp->handle;
+  rmp->retval = rv;
+  rmp->is_get = mp->is_get;
+  if (!rv && mp->is_get)
+    rmp->attr = mp->attr;
+  svm_msg_q_add_and_unlock (app_wrk->event_queue, msg);
+}
+
 vlib_node_registration_t session_queue_node;
 
 typedef struct
@@ -1277,6 +1322,9 @@
     case SESSION_CTRL_EVT_APP_WRK_RPC:
       session_mq_app_wrk_rpc_handler (session_evt_ctrl_data (wrk, elt));
       break;
+    case SESSION_CTRL_EVT_TRANSPORT_ATTR:
+      session_mq_transport_attr_handler (session_evt_ctrl_data (wrk, elt));
+      break;
     default:
       clib_warning ("unhandled event type %d", e->event_type);
     }
diff --git a/src/vnet/session/session_types.h b/src/vnet/session/session_types.h
index 32cb953..3a026a1 100644
--- a/src/vnet/session/session_types.h
+++ b/src/vnet/session/session_types.h
@@ -360,33 +360,36 @@
   SESSION_CTRL_EVT_MIGRATED,
   SESSION_CTRL_EVT_CLEANUP,
   SESSION_CTRL_EVT_APP_WRK_RPC,
+  SESSION_CTRL_EVT_TRANSPORT_ATTR,
+  SESSION_CTRL_EVT_TRANSPORT_ATTR_REPLY,
 } session_evt_type_t;
 
-#define foreach_session_ctrl_evt				\
-  _(LISTEN, listen)						\
-  _(LISTEN_URI, listen_uri)					\
-  _(BOUND, bound)						\
-  _(UNLISTEN, unlisten)						\
-  _(UNLISTEN_REPLY, unlisten_reply)				\
-  _(ACCEPTED, accepted)						\
-  _(ACCEPTED_REPLY, accepted_reply)				\
-  _(CONNECT, connect)						\
-  _(CONNECT_URI, connect_uri)					\
-  _(CONNECTED, connected)					\
-  _(DISCONNECT, disconnect)					\
-  _(DISCONNECTED, disconnected)					\
-  _(DISCONNECTED_REPLY, disconnected_reply)			\
-  _(RESET_REPLY, reset_reply)					\
-  _(REQ_WORKER_UPDATE, req_worker_update)			\
-  _(WORKER_UPDATE, worker_update)				\
-  _(WORKER_UPDATE_REPLY, worker_update_reply)			\
-  _(APP_DETACH, app_detach)					\
-  _(APP_ADD_SEGMENT, app_add_segment)				\
-  _(APP_DEL_SEGMENT, app_del_segment)				\
-  _(MIGRATED, migrated)						\
-  _(CLEANUP, cleanup)						\
-  _(APP_WRK_RPC, app_wrk_rpc)					\
-
+#define foreach_session_ctrl_evt                                              \
+  _ (LISTEN, listen)                                                          \
+  _ (LISTEN_URI, listen_uri)                                                  \
+  _ (BOUND, bound)                                                            \
+  _ (UNLISTEN, unlisten)                                                      \
+  _ (UNLISTEN_REPLY, unlisten_reply)                                          \
+  _ (ACCEPTED, accepted)                                                      \
+  _ (ACCEPTED_REPLY, accepted_reply)                                          \
+  _ (CONNECT, connect)                                                        \
+  _ (CONNECT_URI, connect_uri)                                                \
+  _ (CONNECTED, connected)                                                    \
+  _ (DISCONNECT, disconnect)                                                  \
+  _ (DISCONNECTED, disconnected)                                              \
+  _ (DISCONNECTED_REPLY, disconnected_reply)                                  \
+  _ (RESET_REPLY, reset_reply)                                                \
+  _ (REQ_WORKER_UPDATE, req_worker_update)                                    \
+  _ (WORKER_UPDATE, worker_update)                                            \
+  _ (WORKER_UPDATE_REPLY, worker_update_reply)                                \
+  _ (APP_DETACH, app_detach)                                                  \
+  _ (APP_ADD_SEGMENT, app_add_segment)                                        \
+  _ (APP_DEL_SEGMENT, app_del_segment)                                        \
+  _ (MIGRATED, migrated)                                                      \
+  _ (CLEANUP, cleanup)                                                        \
+  _ (APP_WRK_RPC, app_wrk_rpc)                                                \
+  _ (TRANSPORT_ATTR, transport_attr)                                          \
+  _ (TRANSPORT_ATTR_REPLY, transport_attr_reply)                              \
 /* Deprecated and will be removed. Use types above */
 #define FIFO_EVENT_APP_RX SESSION_IO_EVT_RX
 #define FIFO_EVENT_APP_TX SESSION_IO_EVT_TX
diff --git a/src/vnet/session/transport.c b/src/vnet/session/transport.c
index 4f6ac8b..a420dcd 100644
--- a/src/vnet/session/transport.c
+++ b/src/vnet/session/transport.c
@@ -399,6 +399,17 @@
     }
 }
 
+int
+transport_connection_attribute (transport_proto_t tp, u32 conn_index,
+				u8 thread_index, u8 is_get,
+				transport_endpt_attr_t *attr)
+{
+  if (!tp_vfts[tp].attribute)
+    return -1;
+
+  return tp_vfts[tp].attribute (conn_index, thread_index, is_get, attr);
+}
+
 #define PORT_MASK ((1 << 16)- 1)
 
 void
diff --git a/src/vnet/session/transport.h b/src/vnet/session/transport.h
index efd2507..67583d2 100644
--- a/src/vnet/session/transport.h
+++ b/src/vnet/session/transport.h
@@ -107,13 +107,15 @@
   u8 *(*format_half_open) (u8 * s, va_list * args);
 
   /*
-   *  Properties retrieval
+   *  Properties retrieval/setting
    */
   void (*get_transport_endpoint) (u32 conn_index, u32 thread_index,
 				  transport_endpoint_t *tep, u8 is_lcl);
   void (*get_transport_listener_endpoint) (u32 conn_index,
 					   transport_endpoint_t *tep,
 					   u8 is_lcl);
+  int (*attribute) (u32 conn_index, u32 thread_index, u8 is_get,
+		    transport_endpt_attr_t *attr);
 
   /*
    * Properties
@@ -145,6 +147,9 @@
 			     u8 is_lcl);
 void transport_get_listener_endpoint (transport_proto_t tp, u32 conn_index,
 				      transport_endpoint_t * tep, u8 is_lcl);
+int transport_connection_attribute (transport_proto_t tp, u32 conn_index,
+				    u8 thread_index, u8 is_get,
+				    transport_endpt_attr_t *attr);
 
 static inline transport_connection_t *
 transport_get_connection (transport_proto_t tp, u32 conn_index,
diff --git a/src/vnet/session/transport_types.h b/src/vnet/session/transport_types.h
index 1e7d567..f0fc285 100644
--- a/src/vnet/session/transport_types.h
+++ b/src/vnet/session/transport_types.h
@@ -215,6 +215,42 @@
 #undef _
 } transport_endpoint_cfg_t;
 
+#define foreach_transport_endpt_cfg_flags                                     \
+  _ (CSUM_OFFLOAD)                                                            \
+  _ (GSO)                                                                     \
+  _ (RATE_SAMPLING)
+
+typedef enum transport_endpt_attr_flag_
+{
+#define _(name) TRANSPORT_ENDPT_ATTR_F_##name,
+  foreach_transport_endpt_cfg_flags
+#undef _
+} __clib_packed transport_endpt_attr_flag_t;
+
+#define foreach_transport_attr_fields                                         \
+  _ (u64, next_output_node, NEXT_OUTPUT_NODE)                                 \
+  _ (u16, mss, MSS)                                                           \
+  _ (u8, flags, FLAGS)                                                        \
+  _ (u8, cc_algo, CC_ALGO)
+
+typedef enum transport_endpt_attr_type_
+{
+#define _(type, name, str) TRANSPORT_ENDPT_ATTR_##str,
+  foreach_transport_attr_fields
+#undef _
+} __clib_packed transport_endpt_attr_type_t;
+
+typedef struct transport_endpt_attr_
+{
+  transport_endpt_attr_type_t type;
+  union
+  {
+#define _(type, name, str) type name;
+    foreach_transport_attr_fields
+#undef _
+  };
+} transport_endpt_attr_t;
+
 typedef clib_bihash_24_8_t transport_endpoint_table_t;
 
 #define ENDPOINT_INVALID_INDEX ((u32)~0)
diff --git a/src/vnet/tcp/tcp.c b/src/vnet/tcp/tcp.c
index 2d384a6..7f1e63e 100644
--- a/src/vnet/tcp/tcp.c
+++ b/src/vnet/tcp/tcp.c
@@ -873,6 +873,101 @@
   return &tc->connection;
 }
 
+static int
+tcp_set_attribute (tcp_connection_t *tc, transport_endpt_attr_t *attr)
+{
+  int rv = 0;
+
+  switch (attr->type)
+    {
+    case TRANSPORT_ENDPT_ATTR_NEXT_OUTPUT_NODE:
+      tc->next_node_index = attr->next_output_node & 0xffffffff;
+      tc->next_node_opaque = attr->next_output_node >> 32;
+      break;
+    case TRANSPORT_ENDPT_ATTR_MSS:
+      tc->mss = attr->mss;
+      tc->snd_mss = clib_min (tc->snd_mss, tc->mss);
+      break;
+    case TRANSPORT_ENDPT_ATTR_FLAGS:
+      if (attr->flags & TRANSPORT_ENDPT_ATTR_F_CSUM_OFFLOAD)
+	tc->cfg_flags |= TCP_CFG_F_NO_CSUM_OFFLOAD;
+      else
+	tc->cfg_flags &= ~TCP_CFG_F_NO_CSUM_OFFLOAD;
+      if (attr->flags & TRANSPORT_ENDPT_ATTR_F_GSO)
+	{
+	  if (!(tc->cfg_flags & TCP_CFG_F_TSO))
+	    tcp_check_gso (tc);
+	  tc->cfg_flags &= ~TCP_CFG_F_NO_TSO;
+	}
+      else
+	{
+	  tc->cfg_flags |= TCP_CFG_F_NO_TSO;
+	  tc->cfg_flags &= ~TCP_CFG_F_TSO;
+	}
+      break;
+    case TRANSPORT_ENDPT_ATTR_CC_ALGO:
+      if (tc->cc_algo == tcp_cc_algo_get (attr->cc_algo))
+	break;
+      tcp_cc_cleanup (tc);
+      tc->cc_algo = tcp_cc_algo_get (attr->cc_algo);
+      tcp_cc_init (tc);
+      break;
+    default:
+      rv = -1;
+      break;
+    }
+
+  return rv;
+}
+
+static int
+tcp_get_attribute (tcp_connection_t *tc, transport_endpt_attr_t *attr)
+{
+  int rv = 0;
+  u64 non;
+
+  switch (attr->type)
+    {
+    case TRANSPORT_ENDPT_ATTR_NEXT_OUTPUT_NODE:
+      non = (u64) tc->next_node_opaque << 32 | tc->next_node_index;
+      attr->next_output_node = non;
+      break;
+    case TRANSPORT_ENDPT_ATTR_MSS:
+      attr->mss = tc->snd_mss;
+      break;
+    case TRANSPORT_ENDPT_ATTR_FLAGS:
+      attr->flags = 0;
+      if (!(tc->cfg_flags & TCP_CFG_F_NO_CSUM_OFFLOAD))
+	attr->flags |= TRANSPORT_ENDPT_ATTR_F_CSUM_OFFLOAD;
+      if (tc->cfg_flags & TCP_CFG_F_TSO)
+	attr->flags |= TRANSPORT_ENDPT_ATTR_F_GSO;
+      break;
+    case TRANSPORT_ENDPT_ATTR_CC_ALGO:
+      attr->cc_algo = tc->cc_algo - tcp_main.cc_algos;
+      break;
+    default:
+      rv = -1;
+      break;
+    }
+
+  return rv;
+}
+
+static int
+tcp_session_attribute (u32 conn_index, u32 thread_index, u8 is_get,
+		       transport_endpt_attr_t *attr)
+{
+  tcp_connection_t *tc = tcp_connection_get (conn_index, thread_index);
+
+  if (PREDICT_FALSE (!tc))
+    return -1;
+
+  if (is_get)
+    return tcp_get_attribute (tc, attr);
+  else
+    return tcp_set_attribute (tc, attr);
+}
+
 static u16
 tcp_session_cal_goal_size (tcp_connection_t * tc)
 {
@@ -1172,6 +1267,7 @@
   .get_connection = tcp_session_get_transport,
   .get_listener = tcp_session_get_listener,
   .get_half_open = tcp_half_open_session_get_transport,
+  .attribute = tcp_session_attribute,
   .connect = tcp_session_open,
   .close = tcp_session_close,
   .cleanup = tcp_session_cleanup,
diff --git a/src/vnet/tcp/tcp.h b/src/vnet/tcp/tcp.h
index 418bc47..23b6131 100644
--- a/src/vnet/tcp/tcp.h
+++ b/src/vnet/tcp/tcp.h
@@ -336,6 +336,7 @@
 void tcp_connection_tx_pacer_reset (tcp_connection_t * tc, u32 window,
 				    u32 start_bucket);
 void tcp_program_cleanup (tcp_worker_ctx_t * wrk, tcp_connection_t * tc);
+void tcp_check_gso (tcp_connection_t *tc);
 
 void tcp_punt_unknown (vlib_main_t * vm, u8 is_ip4, u8 is_add);
 int tcp_configure_v4_source_address_range (vlib_main_t * vm,
diff --git a/src/vnet/tcp/tcp_input.c b/src/vnet/tcp/tcp_input.c
index b64c236..398bf1b 100644
--- a/src/vnet/tcp/tcp_input.c
+++ b/src/vnet/tcp/tcp_input.c
@@ -3050,6 +3050,12 @@
 /* *INDENT-ON* */
 
 #ifndef CLIB_MARCH_VARIANT
+void
+tcp_check_gso (tcp_connection_t *tc)
+{
+  tcp_check_tx_offload (tc, tc->c_is_ip4);
+}
+
 static void
 tcp_dispatch_table_init (tcp_main_t * tm)
 {