[PATCH v3 0/2] tcp: unify IPv4 and IPv6 tap queues
This should save us some memory and code. --- v2: - Setting pointers to pre-set IP and MAC headers on the fly instead of copying them. - Merged patch #2 and #3 from v1 v3: - Changes based on feedback from team Jon Maloy (2): tcp: set ip and eth headers in l2 tap queues on the fly tcp: unify l2 TCPv4 and TCPv6 queues and structures tcp.c | 6 +- tcp_buf.c | 257 ++++++++++++++++++------------------------------------ tcp_buf.h | 3 +- 3 files changed, 88 insertions(+), 178 deletions(-) -- 2.45.2
l2 tap queue entries are currently initialized at system start, and
reused with preset headers through its whole life time. The only
fields we need to update per message are things like payload size
and checksums.
If we want to reuse these entries between ipv4 and ipv6 messages we
will need to set the pointer to the right header on the fly per
message, since the header type may differ between entries in the same
queue.
The same needs to be done for the ethernet header.
We do these changes here.
Signed-off-by: Jon Maloy
On Thu, Sep 19, 2024 at 02:44:08PM -0400, Jon Maloy wrote:
l2 tap queue entries are currently initialized at system start, and reused with preset headers through its whole life time. The only fields we need to update per message are things like payload size and checksums.
If we want to reuse these entries between ipv4 and ipv6 messages we will need to set the pointer to the right header on the fly per message, since the header type may differ between entries in the same queue.
The same needs to be done for the ethernet header.
We do these changes here.
Signed-off-by: Jon Maloy
Reviewed-by: David Gibson
--- v2: Setting pointers to pre-initialized IP and MAC headers instead of copying them in on the fly. v3: Adapted to D. Gibson's recent commit eliminitaing overlapping memcpy() --- tcp_buf.c | 54 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 24 deletions(-)
diff --git a/tcp_buf.c b/tcp_buf.c index ffbff5e..c56fb9c 100644 --- a/tcp_buf.c +++ b/tcp_buf.c @@ -159,8 +159,7 @@ void tcp_sock4_iov_init(const struct ctx *c) iov = tcp4_l2_iov[i];
iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp4_payload_tap_hdr[i]); - iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp4_eth_src); - iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[i]); + iov[TCP_IOV_ETH].iov_len = sizeof(struct ethhdr); iov[TCP_IOV_PAYLOAD].iov_base = &tcp4_payload[i]; }
@@ -202,8 +201,7 @@ void tcp_sock6_iov_init(const struct ctx *c) iov = tcp6_l2_iov[i];
iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp6_payload_tap_hdr[i]); - iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp6_eth_src); - iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[i]); + iov[TCP_IOV_ETH].iov_len = sizeof(struct ethhdr); iov[TCP_IOV_PAYLOAD].iov_base = &tcp6_payload[i]; }
@@ -302,11 +300,17 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) uint32_t seq; int ret;
- if (CONN_V4(conn)) - iov = tcp4_l2_flags_iov[tcp4_flags_used++]; - else - iov = tcp6_l2_flags_iov[tcp6_flags_used++]; - + if (CONN_V4(conn)) { + iov = tcp4_l2_flags_iov[tcp4_flags_used]; + iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_flags_ip[tcp4_flags_used]); + iov[TCP_IOV_ETH].iov_base = &tcp4_eth_src; + tcp4_flags_used++; + } else { + iov = tcp6_l2_flags_iov[tcp6_flags_used]; + iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_flags_ip[tcp6_flags_used]); + iov[TCP_IOV_ETH].iov_base = &tcp6_eth_src; + tcp6_flags_used++; + } payload = iov[TCP_IOV_PAYLOAD].iov_base;
seq = conn->seq_to_tap; @@ -325,21 +329,19 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
if (flags & DUP_ACK) { struct iovec *dup_iov; - int i;
if (CONN_V4(conn)) dup_iov = tcp4_l2_flags_iov[tcp4_flags_used++]; else - dup_iov = tcp6_l2_flags_iov[tcp6_flags_used++]; - - for (i = 0; i < TCP_NUM_IOVS; i++) { - /* All frames share the same ethernet header buffer */ - if (i != TCP_IOV_ETH) { - memcpy(dup_iov[i].iov_base, iov[i].iov_base, - iov[i].iov_len); - } - } - dup_iov[TCP_IOV_PAYLOAD].iov_len = iov[TCP_IOV_PAYLOAD].iov_len; + dup_iov = tcp4_l2_flags_iov[tcp6_flags_used++]; + + memcpy(dup_iov[TCP_IOV_TAP].iov_base, iov[TCP_IOV_TAP].iov_base, + iov[TCP_IOV_TAP].iov_len); + dup_iov[TCP_IOV_ETH].iov_base = iov[TCP_IOV_ETH].iov_base; + dup_iov[TCP_IOV_IP] = IOV_OF_LVALUE(iov[TCP_IOV_IP]); + memcpy(dup_iov[TCP_IOV_PAYLOAD].iov_base, + iov[TCP_IOV_PAYLOAD].iov_base, l4len); + dup_iov[TCP_IOV_PAYLOAD].iov_len = l4len; }
if (CONN_V4(conn)) { @@ -379,8 +381,10 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn, }
tcp4_frame_conns[tcp4_payload_used] = conn; - - iov = tcp4_l2_iov[tcp4_payload_used++]; + iov = tcp4_l2_iov[tcp4_payload_used]; + iov[TCP_IOV_IP] = + IOV_OF_LVALUE(tcp4_payload_ip[tcp4_payload_used++]); + iov[TCP_IOV_ETH].iov_base = &tcp4_eth_src; l4len = tcp_l2_buf_fill_headers(conn, iov, dlen, check, seq, false); iov[TCP_IOV_PAYLOAD].iov_len = l4len; @@ -388,8 +392,10 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn, tcp_payload_flush(c); } else if (CONN_V6(conn)) { tcp6_frame_conns[tcp6_payload_used] = conn; - - iov = tcp6_l2_iov[tcp6_payload_used++]; + iov = tcp6_l2_iov[tcp6_payload_used]; + iov[TCP_IOV_IP] = + IOV_OF_LVALUE(tcp6_payload_ip[tcp6_payload_used++]); + iov[TCP_IOV_ETH].iov_base = &tcp6_eth_src; l4len = tcp_l2_buf_fill_headers(conn, iov, dlen, NULL, seq, false); iov[TCP_IOV_PAYLOAD].iov_len = l4len;
-- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson
Following the preparations in the previous commit, we can now remove
the payload and flag queues dedicated for TCPv6 and TCPv4 and move all
traffic into common queues handling both protocol types.
Apart from reducing code and memory footprint, this change reduces
a potential risk for TCPv4 traffic starving out TCPv6 traffic.
Since we always flush out the TCPv4 frame queue before the TCPv6 queue,
the latter will never be handled if the former fails to send all its
frames.
Tests with iperf3 shows no measurable change in performance after this
change.
Signed-off-by: Jon Maloy
On Thu, Sep 19, 2024 at 02:44:09PM -0400, Jon Maloy wrote:
Following the preparations in the previous commit, we can now remove the payload and flag queues dedicated for TCPv6 and TCPv4 and move all traffic into common queues handling both protocol types.
Apart from reducing code and memory footprint, this change reduces a potential risk for TCPv4 traffic starving out TCPv6 traffic. Since we always flush out the TCPv4 frame queue before the TCPv6 queue, the latter will never be handled if the former fails to send all its frames.
Tests with iperf3 shows no measurable change in performance after this change.
Signed-off-by: Jon Maloy
Reviewed-by: David Gibson
On Thu, 19 Sep 2024 14:44:07 -0400
Jon Maloy
This should save us some memory and code.
--- v2: - Setting pointers to pre-set IP and MAC headers on the fly instead of copying them. - Merged patch #2 and #3 from v1 v3: - Changes based on feedback from team
Sorry for the delay, now tests go a bit further but I have a similar problem as I had on v2: guest to host, IPv6, with 65520 bytes MTU only (perf/passt_tcp case), the iperf3 server fails to receive results, test suite output: === perf/passt_tcp
passt: throughput and latency Throughput in Gbps, latency in µs, 4 threads at 3.6 GHz MTU: | 256B | 576B | 1280B | 1500B | 9000B | 65520B | |--------|--------|--------|--------|--------|--------| TCP throughput over IPv6: guest to host | - | - | 5.8 | 6.5 | 18.1 |
and matching iperf3 server output: - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate [ 6] 0.00-1.01 sec 568 MBytes 4.74 Gbits/sec receiver [ 9] 0.00-1.01 sec 621 MBytes 5.18 Gbits/sec receiver [ 11] 0.00-1.01 sec 569 MBytes 4.75 Gbits/sec receiver [ 13] 0.00-1.01 sec 675 MBytes 5.63 Gbits/sec receiver [SUM] 0.00-1.01 sec 2.38 GBytes 20.3 Gbits/sec receiver iperf3: error - unable to receive results: Bad file descriptor after that, connectivity from the guest seems to be pretty much gone. I didn't debug further. -- Stefano
On 2024-09-24 16:30, Stefano Brivio wrote:
On Thu, 19 Sep 2024 14:44:07 -0400 Jon Maloy
wrote: This should save us some memory and code.
--- v2: - Setting pointers to pre-set IP and MAC headers on the fly instead of copying them. - Merged patch #2 and #3 from v1 v3: - Changes based on feedback from team Sorry for the delay, now tests go a bit further but I have a similar problem as I had on v2: guest to host, IPv6, with 65520 bytes MTU only (perf/passt_tcp case), the iperf3 server fails to receive results, test suite output:
=== perf/passt_tcp
passt: throughput and latency Throughput in Gbps, latency in µs, 4 threads at 3.6 GHz MTU: | 256B | 576B | 1280B | 1500B | 9000B | 65520B | |--------|--------|--------|--------|--------|--------| TCP throughput over IPv6: guest to host | - | - | 5.8 | 6.5 | 18.1 |
and matching iperf3 server output:
- - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate [ 6] 0.00-1.01 sec 568 MBytes 4.74 Gbits/sec receiver [ 9] 0.00-1.01 sec 621 MBytes 5.18 Gbits/sec receiver [ 11] 0.00-1.01 sec 569 MBytes 4.75 Gbits/sec receiver [ 13] 0.00-1.01 sec 675 MBytes 5.63 Gbits/sec receiver [SUM] 0.00-1.01 sec 2.38 GBytes 20.3 Gbits/sec receiver iperf3: error - unable to receive results: Bad file descriptor
after that, connectivity from the guest seems to be pretty much gone. I didn't debug further.
Thanks, I'll look into this. That aside, I ran a performance test with my latest patch, and it turns out that performance pasta->host was significantly degraded after the patch was applied. When increasing the frame array to 256 it was better, but still not back to the original performance. I have a strong suspicion this is related to the kernel bug I still haven't re-posted, so I will give that one a try. ///jon
participants (3)
-
David Gibson
-
Jon Maloy
-
Stefano Brivio