From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTP id 2B3045A9CDD for ; Fri, 3 May 2024 15:43:57 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1714743835; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=YmqtAzGX7yCjx6cimuwqMRhBBNh99Zn6usiMYS0TMY8=; b=dXtRtkYwAI3RzOWwS1zgwMP4vze71sLdXCOFv+ajxw6U5YKt5cQgh00Md7XcouB7nS8E+m ILngb3rMQQDSWsoYZi6VCPAyQfsnTomrbZjFh0/2ZDLRVzpdXnzIDXjsr6pe60qmlPcxgX M3CzWHByi5w4zFKvLiov22Zx6efXaI8= Received: from mail-lj1-f199.google.com (mail-lj1-f199.google.com [209.85.208.199]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-607-o4Sc1lejMq6Iqodoa2QtEA-1; Fri, 03 May 2024 09:43:54 -0400 X-MC-Unique: o4Sc1lejMq6Iqodoa2QtEA-1 Received: by mail-lj1-f199.google.com with SMTP id 38308e7fff4ca-2d87d146022so74023641fa.3 for ; Fri, 03 May 2024 06:43:53 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1714743831; x=1715348631; h=content-transfer-encoding:mime-version:organization:references :in-reply-to:message-id:subject:cc:to:from:date:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=YmqtAzGX7yCjx6cimuwqMRhBBNh99Zn6usiMYS0TMY8=; b=QrnhInhJ+wXNJBP7XTkwyD1TT+Kv6zBbupyfYGCaXQgVa2AZHbKCX9X9rM0UZ03ooL tRWotuyBgfNWvJoa/8QTKxX8GlRGqWwe1yBqaQZ5uW4nL5CenOPQscz86b9ZxrJAHZC1 y3+wEpcWwOoWaGXSU3nUCqFx8z9g8iyyp0HhyJG5ROUMF8qAnFlr1pmfA1v4b8xyf3Yv bUzLXZ0JmLjgfdpxnvwHpELeutcILguNVgPmBxD/3Odx8XaSDbQH6dTZiWRYgDDQlN68 MtAbXp9OmLxmuNpSJ2Czxp5/VrvRs/eIyFlBkT4f6iIjBgnw2WP9cw3RjZ7YXzpvkJXs 531w== X-Gm-Message-State: AOJu0YwNeAUKoLqWGUVzWtHuFe4ALVEShERIQ5QSVi4MtNKC9xpFJA3j h77uO8PxuBU7B3anEE8IySu6f0N8E2D7l06f4wDnG3PswR7t7vJ+utHJaGRV3IQZLLiAohAlead oEYxkTa+LfLO3bzMKGPU0Gg2YZTOWV+wYtn/ZN9fMQFGuQd7a1IbVRwYyrKYM5dVjFTPUgGQ6x/ g5FV1y+fQPaKOYslwcva8Gu6Rqj1CIPCuzfak= X-Received: by 2002:ac2:596f:0:b0:518:dae6:d0ec with SMTP id h15-20020ac2596f000000b00518dae6d0ecmr1957088lfp.4.1714743831009; Fri, 03 May 2024 06:43:51 -0700 (PDT) X-Google-Smtp-Source: AGHT+IFZTkEjwKyIHtVf9xdfepFO4rfNbfqqfs6aaI7Ajb1we5ZypP6pw+AQYsxnv4Tr9gn4yTAPTg== X-Received: by 2002:ac2:596f:0:b0:518:dae6:d0ec with SMTP id h15-20020ac2596f000000b00518dae6d0ecmr1957057lfp.4.1714743830254; Fri, 03 May 2024 06:43:50 -0700 (PDT) Received: from maya.cloud.tilaa.com (maya.cloud.tilaa.com. [164.138.29.33]) by smtp.gmail.com with ESMTPSA id i23-20020a17090671d700b00a52222f2b21sm1726179ejk.66.2024.05.03.06.43.49 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 03 May 2024 06:43:49 -0700 (PDT) Date: Fri, 3 May 2024 15:43:16 +0200 From: Stefano Brivio To: Jon Maloy Subject: Re: [PATCH v2 2/2] tcp: allow retransmit when peer receive window is zero Message-ID: <20240503154316.001864a0@elisabeth> In-Reply-To: <20240501202839.3491885-3-jmaloy@redhat.com> References: <20240501202839.3491885-1-jmaloy@redhat.com> <20240501202839.3491885-3-jmaloy@redhat.com> Organization: Red Hat X-Mailer: Claws Mail 4.2.0 (GTK 3.24.36; x86_64-pc-linux-gnu) MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Message-ID-Hash: H64DECOYWVDUQ73AZEPMPQOUEZHO5F3F X-Message-ID-Hash: H64DECOYWVDUQ73AZEPMPQOUEZHO5F3F X-MailFrom: sbrivio@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: passt-dev@passt.top, lvivier@redhat.com, dgibson@redhat.com X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: On Wed, 1 May 2024 16:28:39 -0400 Jon Maloy wrote: > A bug in kernel TCP may lead to a deadlock where a zero window is sent > from the peer, while he is unable to send out window updates even after s/he/it/ > reads have freed up enough buffer space to permit a larger window. > In this situation, new window advertisemnts from the peer can only be > triggered by packets arriving from this side. > > However, such packets are never sent, because the zero-window condition > currently prevents this side from sending out any packets whatsoever > to the peer. > > We notice that the above bug is triggered *only* after the peer has > dropped an arriving packet because of severe memory squeeze, and that we > hence always enter a retransmission situation when this occurs. This is clearly an issue, but: > This > also means that RFC 9293 is violated, since the zero-advertisement > shrinks the previously advertised window within which the dropped packet > originally was sent. ...I don't think the RFC is violated, it just says "SHOULD NOT". > RFC 9293 gives the solution to this situation. In chapter 3.6.1 we find > the following statement: > "A TCP receiver SHOULD NOT shrink the window, i.e., move the right > window edge to the left (SHLD-14). However, a sending TCP peer MUST > be robust against window shrinking, which may cause the > "usable window" (see Section 3.8.6.2.1) to become negative (MUST-34). > > If this happens, the sender SHOULD NOT send new data (SHLD-15), but My understanding of this "this" is that it refers to the case where the usable window becomes negative, which I'm not sure it's the case here. That is, if the zero window update you get includes an acknowledgement for all the data that was sent so far, the usable window is not negative, it's zero, and we shouldn't retransmit anything. If previously sent data is not acknowledged, then yes, we should retransmit it at this point. In both cases, we _must_ implement zero-window probing (3.8.6.1), which is not entirely covered by this patch, if I understand correctly. > SHOULD retransmit normally the old unacknowledged data between SND.UNA > and SND.UNA+SND.WND (SHLD-16). The sender MAY also retransmit old data > beyond SND.UNA+SND.WND (MAY-7)" > > It should be noted that although this solves the problem we have at > hand, it is not a genuine solution to the kernel bug. There may well > be TCP stacks around in other OS-es which don't do this, nor have > keep-alive probing as an alternatve way to solve the situation. > > Signed-off-by: Jon Maloy > > --- > v2: - Using previously advertised window during retransmission, instead > highest send sequencece number in the cycle. > --- > tcp.c | 29 ++++++++++++++++++++--------- > tcp_conn.h | 2 ++ > 2 files changed, 22 insertions(+), 9 deletions(-) > > diff --git a/tcp.c b/tcp.c > index 535f1a2..703c62f 100644 > --- a/tcp.c > +++ b/tcp.c > @@ -1749,9 +1749,15 @@ static void tcp_get_tap_ws(struct tcp_tap_conn *conn, > */ > static void tcp_tap_window_update(struct tcp_tap_conn *conn, unsigned wnd) > { > + uint32_t wnd_upper; > + > wnd = MIN(MAX_WINDOW, wnd << conn->ws_from_tap); > conn->wnd_from_tap = MIN(wnd >> conn->ws_from_tap, USHRT_MAX); > > + wnd_upper = conn->seq_ack_from_tap + wnd; > + if (wnd && SEQ_GT(wnd_upper, conn->seq_wup_from_tap)) > + conn->seq_wup_from_tap = wnd_upper; > + > /* FIXME: reflect the tap-side receiver's window back to the sock-side > * sender by adjusting SO_RCVBUF? */ > } > @@ -1784,6 +1790,7 @@ static void tcp_seq_init(const struct ctx *c, struct tcp_tap_conn *conn, > ns = (now->tv_sec * 1000000000 + now->tv_nsec) >> 5; > > conn->seq_to_tap = ((uint32_t)(hash >> 32) ^ (uint32_t)hash) + ns; > + conn->seq_wup_from_tap = conn->seq_to_tap; > } > > /** > @@ -2133,9 +2140,8 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn, > * > * #syscalls recvmsg > */ > -static int tcp_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn) > +static int tcp_data_from_sock(struct ctx *c, struct tcp_tap_conn *conn, uint32_t wnd_scaled) Function comment should be updated. While at it, I wonder if instead of overriding the window value, we shouldn't rather add a 'zwp' or 'force' flag here, because the regular call sites become a bit awkward otherwise: why would you pass the receive window to a a function that's named tcp_data_from_sock()? I see that there's another point where you now need to call it with a value greater than one (below, from tcp_data_from_tap()), but I wonder if the window to be used, here, could (always?) be inferred by the value of seq_wup_from_tap. > { > - uint32_t wnd_scaled = conn->wnd_from_tap << conn->ws_from_tap; > int fill_bufs, send_bufs = 0, last_len, iov_rem = 0; > int sendlen, len, plen, v4 = CONN_V4(conn); > int s = conn->sock, i, ret = 0; > @@ -2282,6 +2288,7 @@ static int tcp_data_from_tap(struct ctx *c, struct tcp_tap_conn *conn, > uint32_t max_ack_seq = conn->seq_ack_from_tap; > uint32_t seq_from_tap = conn->seq_from_tap; > struct msghdr mh = { .msg_iov = tcp_iov }; > + uint32_t wnd; > size_t len; > ssize_t n; > > @@ -2325,8 +2332,7 @@ static int tcp_data_from_tap(struct ctx *c, struct tcp_tap_conn *conn, > SEQ_GE(ack_seq, max_ack_seq)) { > /* Fast re-transmit */ > retr = !len && !th->fin && > - ack_seq == max_ack_seq && > - ntohs(th->window) == max_ack_seq_wnd; The reason for this condition is to tell apart mere window updates from other segments. That is, if you get: - an acknowledgement for sequence x and advertised window y0 - an acknowledgement for sequence x and advertised window y1 ...then it's not a request to retransmit anything, it's a window update. If you get: - an acknowledgement for sequence x and advertised window y0 - an acknowledgement for sequence x and advertised window y0 ...then it's a trigger for fast re-transmit. Yes, we should retransmit something in some cases (what you quoted above), but not on every kind of window update (usually the window grows). > + ack_seq == max_ack_seq; > > max_ack_seq_wnd = ntohs(th->window); > max_ack_seq = ack_seq; > @@ -2400,7 +2406,8 @@ static int tcp_data_from_tap(struct ctx *c, struct tcp_tap_conn *conn, > conn->seq_ack_from_tap = max_ack_seq; > conn->seq_to_tap = max_ack_seq; > set_peek_offset(conn, 0); > - tcp_data_from_sock(c, conn); > + wnd = conn->seq_wup_from_tap - max_ack_seq; > + tcp_data_from_sock(c, conn, wnd); > > /* Empty queue before any POLL event tries to send it again */ > tcp_l2_data_buf_flush(c); > @@ -2500,7 +2507,7 @@ static void tcp_conn_from_sock_finish(struct ctx *c, struct tcp_tap_conn *conn, > /* The client might have sent data already, which we didn't > * dequeue waiting for SYN,ACK from tap -- check now. > */ > - tcp_data_from_sock(c, conn); > + tcp_data_from_sock(c, conn, conn->wnd_from_tap << conn->ws_from_tap); > tcp_send_flag(c, conn, ACK); > } > > @@ -2593,7 +2600,7 @@ int tcp_tap_handler(struct ctx *c, uint8_t pif, int af, > > tcp_tap_window_update(conn, ntohs(th->window)); > > - tcp_data_from_sock(c, conn); > + tcp_data_from_sock(c, conn, conn->wnd_from_tap << conn->ws_from_tap); > > if (p->count - idx == 1) > return 1; > @@ -2776,6 +2783,7 @@ void tcp_timer_handler(struct ctx *c, union epoll_ref ref) > { > struct itimerspec check_armed = { { 0 }, { 0 } }; > struct tcp_tap_conn *conn = CONN(ref.flow); > + uint32_t wnd; > > if (c->no_tcp) > return; > @@ -2807,7 +2815,10 @@ void tcp_timer_handler(struct ctx *c, union epoll_ref ref) > conn->seq_to_tap = conn->seq_ack_from_tap; > set_peek_offset(conn, 0); > tcp_l2_data_buf_flush(c); > - tcp_data_from_sock(c, conn); > + > + /* RFC 9293, 3.8.6: Send 1 byte of new data if needed */ ...if available. But, if it's not available, we should send a zero-window probe without payload. And to check if it's needed, we should check that the advertised receive window is zero, right? I understand that the outcome of this: if (!conn->wnd_from_tap) /* RFC 9293, 3.8.6.1, zero-window probing */ tcp_data_from_sock(c, conn, 1); else tcp_data_from_sock(c, conn, wnd); is the same as: tcp_data_from_sock(c, conn, MAX(1, wnd)); but I find it a bit complicated to understand what this does otherwise. > + wnd = conn->seq_wup_from_tap - conn->seq_ack_from_tap; > + tcp_data_from_sock(c, conn, MAX(1, wnd)); > tcp_l2_data_buf_flush(c); > tcp_timer_ctl(c, conn); > } > @@ -2863,7 +2874,7 @@ void tcp_sock_handler(struct ctx *c, union epoll_ref ref, uint32_t events) > conn_event(c, conn, SOCK_FIN_RCVD); > > if (events & EPOLLIN) > - tcp_data_from_sock(c, conn); > + tcp_data_from_sock(c, conn, conn->wnd_from_tap << conn->ws_from_tap); > > if (events & EPOLLOUT) > tcp_update_seqack_wnd(c, conn, 0, NULL); > diff --git a/tcp_conn.h b/tcp_conn.h > index a5f5cfe..d6b627a 100644 > --- a/tcp_conn.h > +++ b/tcp_conn.h > @@ -30,6 +30,7 @@ > * @wnd_to_tap: Sending window advertised to tap, unscaled (as sent) > * @seq_to_tap: Next sequence for packets to tap > * @seq_ack_from_tap: Last ACK number received from tap > + * @seq_wup_from_tap: Right edge (+1) of last non-zero window from tap It's not +1 anymore, right? > * @seq_from_tap: Next sequence for packets from tap (not actually sent) > * @seq_ack_to_tap: Last ACK number sent to tap > * @seq_init_from_tap: Initial sequence number from tap > @@ -101,6 +102,7 @@ struct tcp_tap_conn { > > uint32_t seq_to_tap; > uint32_t seq_ack_from_tap; > + uint32_t seq_wup_from_tap; > uint32_t seq_from_tap; > uint32_t seq_ack_to_tap; > uint32_t seq_init_from_tap; -- Stefano