diff --git a/iov.c b/iov.c
index 2f7be15..3b12272 100644
--- a/iov.c
+++ b/iov.c
@@ -236,7 +236,6 @@ void *iov_peek_header_(struct iov_tail *tail, size_t len, size_t align)
  *	    overruns the IO vector, is not contiguous or doesn't have the
  *	    requested alignment.
  */
-/* cppcheck-suppress unusedFunction */
 void *iov_remove_header_(struct iov_tail *tail, size_t len, size_t align)
 {
 	char *p = iov_peek_header_(tail, len, align);
diff --git a/tcp.c b/tcp.c
index 5c40e18..2f900fc 100644
--- a/tcp.c
+++ b/tcp.c
@@ -909,21 +909,21 @@ static void tcp_fill_header(struct tcphdr *th,
  * @conn:		Connection pointer
  * @taph:		tap backend specific header
  * @iph:		Pointer to IPv4 header
- * @bp:			Pointer to TCP header followed by TCP payload
- * @dlen:		TCP payload length
+ * @th:			Pointer to TCP header
+ * @payload:		TCP payload
  * @check:		Checksum, if already known
  * @seq:		Sequence number for this segment
  * @no_tcp_csum:	Do not set TCP checksum
  */
 void tcp_fill_headers4(const struct tcp_tap_conn *conn,
 		       struct tap_hdr *taph, struct iphdr *iph,
-		       struct tcp_payload_t *bp, size_t dlen,
+		       struct tcphdr *th, struct iov_tail *payload,
 		       const uint16_t *check, uint32_t seq, bool no_tcp_csum)
 {
 	const struct flowside *tapside = TAPFLOW(conn);
 	const struct in_addr *src4 = inany_v4(&tapside->oaddr);
 	const struct in_addr *dst4 = inany_v4(&tapside->eaddr);
-	size_t l4len = dlen + sizeof(bp->th);
+	size_t l4len = iov_tail_size(payload) + sizeof(*th);
 	size_t l3len = l4len + sizeof(*iph);
 
 	ASSERT(src4 && dst4);
@@ -935,19 +935,12 @@ void tcp_fill_headers4(const struct tcp_tap_conn *conn,
 	iph->check = check ? *check :
 			     csum_ip4_header(l3len, IPPROTO_TCP, *src4, *dst4);
 
-	tcp_fill_header(&bp->th, conn, seq);
+	tcp_fill_header(th, conn, seq);
 
-	if (no_tcp_csum) {
-		bp->th.check = 0;
-	} else {
-		const struct iovec iov = {
-			.iov_base = bp->data,
-			.iov_len = dlen,
-		};
-		struct iov_tail payload = IOV_TAIL(&iov, 1, 0);
-
-		tcp_update_check_tcp4(iph, &bp->th, &payload);
-	}
+	if (no_tcp_csum)
+		th->check = 0;
+	else
+		tcp_update_check_tcp4(iph, th, payload);
 
 	tap_hdr_update(taph, l3len + sizeof(struct ethhdr));
 }
@@ -957,19 +950,19 @@ void tcp_fill_headers4(const struct tcp_tap_conn *conn,
  * @conn:		Connection pointer
  * @taph:		tap backend specific header
  * @ip6h:		Pointer to IPv6 header
- * @bp:			Pointer to TCP header followed by TCP payload
- * @dlen:		TCP payload length
+ * @th:			Pointer to TCP header
+ * @payload:		TCP payload
  * @check:		Checksum, if already known
  * @seq:		Sequence number for this segment
  * @no_tcp_csum:	Do not set TCP checksum
  */
 void tcp_fill_headers6(const struct tcp_tap_conn *conn,
 		       struct tap_hdr *taph, struct ipv6hdr *ip6h,
-		       struct tcp_payload_t *bp, size_t dlen,
+		       struct tcphdr *th, struct iov_tail *payload,
 		       uint32_t seq, bool no_tcp_csum)
 {
+	size_t l4len = iov_tail_size(payload) + sizeof(*th);
 	const struct flowside *tapside = TAPFLOW(conn);
-	size_t l4len = dlen + sizeof(bp->th);
 
 	ip6h->payload_len = htons(l4len);
 	ip6h->saddr = tapside->oaddr.a6;
@@ -983,19 +976,12 @@ void tcp_fill_headers6(const struct tcp_tap_conn *conn,
 	ip6h->flow_lbl[1] = (conn->sock >> 8) & 0xff;
 	ip6h->flow_lbl[2] = (conn->sock >> 0) & 0xff;
 
-	tcp_fill_header(&bp->th, conn, seq);
+	tcp_fill_header(th, conn, seq);
 
-	if (no_tcp_csum) {
-		bp->th.check = 0;
-	} else {
-		const struct iovec iov = {
-			.iov_base = bp->data,
-			.iov_len = dlen,
-		};
-		struct iov_tail payload = IOV_TAIL(&iov, 1, 0);
-
-		tcp_update_check_tcp6(ip6h, &bp->th, &payload);
-	}
+	if (no_tcp_csum)
+		th->check = 0;
+	else
+		tcp_update_check_tcp6(ip6h, th, payload);
 
 	tap_hdr_update(taph, l4len + sizeof(*ip6h) + sizeof(struct ethhdr));
 }
diff --git a/tcp_buf.c b/tcp_buf.c
index 0946cd5..830c23d 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -151,29 +151,27 @@ void tcp_payload_flush(const struct ctx *c)
  * tcp_buf_fill_headers() - Fill 802.3, IP, TCP headers in pre-cooked buffers
  * @conn:	Connection pointer
  * @iov:	Pointer to an array of iovec of TCP pre-cooked buffers
- * @dlen:	TCP payload length
  * @check:	Checksum, if already known
  * @seq:	Sequence number for this segment
  * @no_tcp_csum: Do not set TCP checksum
  */
 static void tcp_l2_buf_fill_headers(const struct tcp_tap_conn *conn,
-				    struct iovec *iov, size_t dlen,
-				    const uint16_t *check, uint32_t seq,
-				    bool no_tcp_csum)
+				    struct iovec *iov, const uint16_t *check,
+				    uint32_t seq, bool no_tcp_csum)
 {
+	struct iov_tail tail = IOV_TAIL(&iov[TCP_IOV_PAYLOAD], 1, 0);
+	struct tcphdr *th = IOV_REMOVE_HEADER(&tail, struct tcphdr);
 	const struct flowside *tapside = TAPFLOW(conn);
 	const struct in_addr *a4 = inany_v4(&tapside->oaddr);
 
 	if (a4) {
 		tcp_fill_headers4(conn, iov[TCP_IOV_TAP].iov_base,
-				  iov[TCP_IOV_IP].iov_base,
-				  iov[TCP_IOV_PAYLOAD].iov_base, dlen,
-				  check, seq, no_tcp_csum);
+				  iov[TCP_IOV_IP].iov_base, th,
+				  &tail,  check, seq, no_tcp_csum);
 	} else {
 		tcp_fill_headers6(conn, iov[TCP_IOV_TAP].iov_base,
-				  iov[TCP_IOV_IP].iov_base,
-				  iov[TCP_IOV_PAYLOAD].iov_base, dlen,
-				  seq, no_tcp_csum);
+				  iov[TCP_IOV_IP].iov_base, th,
+				  &tail, seq, no_tcp_csum);
 	}
 }
 
@@ -213,7 +211,7 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
 	tcp_payload_used++;
 	l4len = optlen + sizeof(struct tcphdr);
 	iov[TCP_IOV_PAYLOAD].iov_len = l4len;
-	tcp_l2_buf_fill_headers(conn, iov, optlen, NULL, seq, false);
+	tcp_l2_buf_fill_headers(conn, iov, NULL, seq, false);
 
 	if (flags & DUP_ACK) {
 		struct iovec *dup_iov = tcp_l2_iov[tcp_payload_used++];
@@ -270,7 +268,7 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
 	payload->th.th_flags = 0;
 	payload->th.ack = 1;
 	iov[TCP_IOV_PAYLOAD].iov_len = dlen + sizeof(struct tcphdr);
-	tcp_l2_buf_fill_headers(conn, iov, dlen, check, seq, false);
+	tcp_l2_buf_fill_headers(conn, iov, check, seq, false);
 	if (++tcp_payload_used > TCP_FRAMES_MEM - 1)
 		tcp_payload_flush(c);
 }
diff --git a/tcp_internal.h b/tcp_internal.h
index 744c5c0..9732b5b 100644
--- a/tcp_internal.h
+++ b/tcp_internal.h
@@ -168,11 +168,11 @@ void tcp_update_check_tcp6(const struct ipv6hdr *ip6h, struct tcphdr *th,
 			   struct iov_tail *payload);
 void tcp_fill_headers4(const struct tcp_tap_conn *conn,
 		       struct tap_hdr *taph, struct iphdr *iph,
-		       struct tcp_payload_t *bp, size_t dlen,
+		       struct tcphdr *th, struct iov_tail *payload,
 		       const uint16_t *check, uint32_t seq, bool no_tcp_csum);
 void tcp_fill_headers6(const struct tcp_tap_conn *conn,
 		       struct tap_hdr *taph, struct ipv6hdr *ip6h,
-		       struct tcp_payload_t *bp, size_t dlen,
+		       struct tcphdr *th, struct iov_tail *payload,
 		       uint32_t seq, bool no_tcp_csum);
 
 int tcp_update_seqack_wnd(const struct ctx *c, struct tcp_tap_conn *conn,
diff --git a/tcp_vu.c b/tcp_vu.c
index 134650e..470649e 100644
--- a/tcp_vu.c
+++ b/tcp_vu.c
@@ -104,10 +104,12 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
 	const struct flowside *tapside = TAPFLOW(conn);
 	size_t optlen, hdrlen;
 	struct vu_virtq_element flags_elem[2];
-	struct tcp_payload_t *payload;
 	struct ipv6hdr *ip6h = NULL;
 	struct iovec flags_iov[2];
+	struct tcp_syn_opts *opts;
 	struct iphdr *iph = NULL;
+	struct iov_tail payload;
+	struct tcphdr *th;
 	struct ethhdr *eh;
 	uint32_t seq;
 	int elem_cnt;
@@ -139,35 +141,35 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
 		iph = vu_ip(flags_elem[0].in_sg[0].iov_base);
 		*iph = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
 
-		payload = vu_payloadv4(flags_elem[0].in_sg[0].iov_base);
+		th = vu_payloadv4(flags_elem[0].in_sg[0].iov_base);
 	} else {
 		eh->h_proto = htons(ETH_P_IPV6);
 
 		ip6h = vu_ip(flags_elem[0].in_sg[0].iov_base);
 		*ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
-		payload = vu_payloadv6(flags_elem[0].in_sg[0].iov_base);
+		th = vu_payloadv6(flags_elem[0].in_sg[0].iov_base);
 	}
 
-	memset(&payload->th, 0, sizeof(payload->th));
-	payload->th.doff = offsetof(struct tcp_payload_t, data) / 4;
-	payload->th.ack = 1;
+	memset(th, 0, sizeof(*th));
+	th->doff = sizeof(*th) / 4;
+	th->ack = 1;
 
 	seq = conn->seq_to_tap;
-	ret = tcp_prepare_flags(c, conn, flags, &payload->th,
-				(struct tcp_syn_opts *)payload->data,
-				&optlen);
+	opts = (struct tcp_syn_opts *)(th + 1);
+	ret = tcp_prepare_flags(c, conn, flags, th, opts, &optlen);
 	if (ret <= 0) {
 		vu_queue_rewind(vq, 1);
 		return ret;
 	}
 
 	flags_elem[0].in_sg[0].iov_len = hdrlen + optlen;
+	payload = IOV_TAIL(flags_elem[0].in_sg, 1, hdrlen);
 
 	if (CONN_V4(conn)) {
-		tcp_fill_headers4(conn, NULL, iph, payload, optlen, NULL, seq,
-				  true);
+		tcp_fill_headers4(conn, NULL, iph, th, &payload,
+				  NULL, seq, true);
 	} else {
-		tcp_fill_headers6(conn, NULL, ip6h, payload, optlen, seq, true);
+		tcp_fill_headers6(conn, NULL, ip6h, th, &payload, seq, true);
 	}
 
 	if (*c->pcap) {
@@ -317,23 +319,28 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c,
  * tcp_vu_prepare() - Prepare the frame header
  * @c:		Execution context
  * @conn:	Connection pointer
- * @first:	Pointer to the array of IO vectors
- * @dlen:	Packet data length
+ * @iov:	Pointer to the array of IO vectors
+ * @iov_cnt:	Number of entries in @iov
  * @check:	Checksum, if already known
  */
-static void tcp_vu_prepare(const struct ctx *c,
-			   struct tcp_tap_conn *conn, char *base,
-			   size_t dlen, const uint16_t **check)
+static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn,
+			   struct iovec *iov, size_t iov_cnt,
+			   const uint16_t **check)
 {
 	const struct flowside *toside = TAPFLOW(conn);
-	struct tcp_payload_t *payload;
+	bool v6 = !(inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr));
+	size_t hdrlen = tcp_vu_hdrlen(v6);
+	struct iov_tail payload = IOV_TAIL(iov, iov_cnt, hdrlen);
+	char *base = iov[0].iov_base;
 	struct ipv6hdr *ip6h = NULL;
 	struct iphdr *iph = NULL;
+	struct tcphdr *th;
 	struct ethhdr *eh;
 
 	/* we guess the first iovec provided by the guest can embed
 	 * all the headers needed by L2 frame
 	 */
+	ASSERT(iov[0].iov_len >= hdrlen);
 
 	eh = vu_eth(base);
 
@@ -342,31 +349,31 @@ static void tcp_vu_prepare(const struct ctx *c,
 
 	/* initialize header */
 
-	if (inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr)) {
+	if (!v6) {
 		eh->h_proto = htons(ETH_P_IP);
 
 		iph = vu_ip(base);
 		*iph = (struct iphdr)L2_BUF_IP4_INIT(IPPROTO_TCP);
-		payload = vu_payloadv4(base);
+		th = vu_payloadv4(base);
 	} else {
 		eh->h_proto = htons(ETH_P_IPV6);
 
 		ip6h = vu_ip(base);
 		*ip6h = (struct ipv6hdr)L2_BUF_IP6_INIT(IPPROTO_TCP);
 
-		payload = vu_payloadv6(base);
+		th = vu_payloadv6(base);
 	}
 
-	memset(&payload->th, 0, sizeof(payload->th));
-	payload->th.doff = offsetof(struct tcp_payload_t, data) / 4;
-	payload->th.ack = 1;
+	memset(th, 0, sizeof(*th));
+	th->doff = sizeof(*th) / 4;
+	th->ack = 1;
 
-	if (inany_v4(&toside->eaddr) && inany_v4(&toside->oaddr)) {
-		tcp_fill_headers4(conn, NULL, iph, payload, dlen,
+	if (!v6) {
+		tcp_fill_headers4(conn, NULL, iph, th, &payload,
 				  *check, conn->seq_to_tap, true);
 		*check = &iph->check;
 	} else {
-		tcp_fill_headers6(conn, NULL, ip6h, payload, dlen,
+		tcp_fill_headers6(conn, NULL, ip6h, th, &payload,
 				  conn->seq_to_tap, true);
 	}
 }
@@ -478,7 +485,7 @@ int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn)
 		if (i + 1 == head_cnt)
 			check = NULL;
 
-		tcp_vu_prepare(c, conn, iov->iov_base, dlen, &check);
+		tcp_vu_prepare(c, conn, iov, buf_cnt, &check);
 
 		if (*c->pcap) {
 			tcp_vu_update_check(tapside, iov, buf_cnt);