mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com>
To: "ceph-devel@vger.kernel.org" <ceph-devel@vger.kernel.org>,
	"ionut.nechita@windriver.com" <ionut.nechita@windriver.com>
Cc: "idryomov@gmail.com" <idryomov@gmail.com>,
	Xiubo Li <xiubli@redhat.com>,
	"linux-kernel@vger.kernel.org" <linux-kernel@vger.kernel.org>,
	"ionut_n2001@yahoo.com" <ionut_n2001@yahoo.com>
Subject: Re:  [PATCH v1 11/13] libceph: reset source address on persistent EADDRNOTAVAIL
Date: Thu, 12 Mar 2026 21:39:06 +0000	[thread overview]
Message-ID: <1aa4e598d3c0f45d408c66153addd3502ae432c0.camel@ibm.com> (raw)
In-Reply-To: <20260312081619.40854-12-ionut.nechita@windriver.com>

On Thu, 2026-03-12 at 10:16 +0200, Ionut Nechita (Wind River) wrote:
> From: Ionut Nechita <ionut.nechita@windriver.com>
> 
> In containerized environments (e.g., Rook-Ceph with
> Calico CNI), the kernel CephFS client's source address
> (msgr->inst.addr) is learned from the first successful monitor
> connection via process_hello(). If the initial connection was made
> through a transient CNI pod address (e.g., a Calico-assigned
> dead:beef::... address from a CSI plugin pod), that address is
> stored permanently in inst.addr.
> 
> When the pod is later rescheduled or the CNI reconfigures networking,
> the original pod address is removed and Calico installs a blackhole
> route for the old address range. All subsequent kernel socket
> connections fail with EADDRNOTAVAIL at ip6_dst_lookup_flow() before
> even sending a TCP SYN, because the IPv6 source address selection
> finds the blackhole route for the old address range.
> 
> This creates a permanent deadlock:
> - All connections (mon, mds, osd) fail with EADDRNOTAVAIL
> - The client cannot reach any monitor to re-learn its address
> - inst.addr is never blank again (set once, never cleared)
> - The only recovery is force-unmounting and remounting
> 
> Fix this by tracking consecutive EADDRNOTAVAIL failures across all
> connections using an atomic counter in struct ceph_messenger. After
> ADDRNOTAVAIL_RESET_THRESHOLD (30) consecutive failures (~3 seconds
> at 100ms retry interval), reset inst.addr.in_addr to zero (blank)
> while preserving the nonce and type. This allows process_hello()
> (msgr2) or process_banner() (msgr1) to re-learn the source address
> from the next successful monitor connection, which will use the
> current stable host address instead of the defunct pod address.
> 
> The counter is reset to zero when:
> - A TCP connection succeeds (in ceph_tcp_connect)
> - The address is successfully re-learned (in process_hello/
>   process_banner)
> 
> Observed in production (kernel 6.12.0-1-rt-amd64, Ceph Reef
> 18.2.2->18.2.5 upgrade, IPv6-only cluster):
>   - Client instance: client.55136 [dead:beef::a2bf:c94c:345d:bc66]:0
>   - Address dead:beef::a2bf:c94c:345d:bc66 was a Calico pod address
>   - After pod reschedule: blackhole dead:beef::a2bf:c94c:345d:bc40/122
>   - All connections stuck in EADDRNOTAVAIL loop for 16+ hours
>   - After force-unmount + remount: new client got stable host address
>     [aefd::2b93:d245:fd09:127e]:0 and worked immediately
> 
> Signed-off-by: Ionut Nechita <ionut.nechita@windriver.com>
> ---
>  include/linux/ceph/messenger.h | 20 +++++++++++++
>  net/ceph/messenger.c           | 51 ++++++++++++++++++++++++++++++++++
>  net/ceph/messenger_v1.c        |  7 +++++
>  net/ceph/messenger_v2.c        | 12 ++++++++
>  4 files changed, 90 insertions(+)
> 
> diff --git a/include/linux/ceph/messenger.h b/include/linux/ceph/messenger.h
> index 730a754353aed..d8f7946d85a68 100644
> --- a/include/linux/ceph/messenger.h
> +++ b/include/linux/ceph/messenger.h
> @@ -113,6 +113,17 @@ struct ceph_messenger {
>  	 */
>  	u32 global_seq;
>  	spinlock_t global_seq_lock;
> +
> +	/*
> +	 * Track consecutive EADDRNOTAVAIL failures across all
> +	 * connections. When this exceeds a threshold, the client's
> +	 * inst.addr is reset to blank so that process_hello() will
> +	 * re-learn the source address from the next successful
> +	 * monitor connection. This handles the case where the
> +	 * original source address was a transient CNI pod address
> +	 * that no longer exists.
> +	 */
> +	atomic_t addr_notavail_count;

Would atomic_t be enough? Do we need to consider atomic64_t? Any thoughts?

>  };
>  
>  enum ceph_msg_data_type {
> @@ -328,6 +339,15 @@ struct ceph_msg {
>   */
>  #define ADDRNOTAVAIL_DELAY	(HZ / 10)

You already introduce likewise constant. Why do you need introduce another one?
And why HZ/10 namely?

>  
> +/*
> + * Number of consecutive EADDRNOTAVAIL failures (across all connections)
> + * before resetting the messenger's source address. At ~100ms per retry,
> + * 30 failures means ~3 seconds of persistent EADDRNOTAVAIL before we
> + * conclude the source address is permanently gone (e.g., a CNI pod
> + * address that was removed) and needs to be re-learned.
> + */
> +#define ADDRNOTAVAIL_RESET_THRESHOLD	30

I am not completely sure that this math sounds reasonably well. :)

> +
>  struct ceph_connection_v1_info {
>  	struct kvec out_kvec[8],         /* sending header/footer data */
>  		*out_kvec_cur;
> diff --git a/net/ceph/messenger.c b/net/ceph/messenger.c
> index c40c7c332e7f4..8165e6a8fe092 100644
> --- a/net/ceph/messenger.c
> +++ b/net/ceph/messenger.c
> @@ -497,6 +497,10 @@ int ceph_tcp_connect(struct ceph_connection *con)
>  	else
>  		con->v1.addr_notavail = false;
>  
> +	/* Reset the persistent EADDRNOTAVAIL counter on success */
> +	if (atomic_read(&con->msgr->addr_notavail_count) > 0)

Why do you check the addr_notavail_count value? If it is success, then who cares
what this value has before.

> +		atomic_set(&con->msgr->addr_notavail_count, 0);
> +
>  	return 0;
>  }
>  
> @@ -1663,6 +1667,52 @@ static void con_fault(struct ceph_connection *con)
>  		}
>  	}
>  
> +	/*
> +	 * Track persistent EADDRNOTAVAIL across all connections.
> +	 * If the source address stored in msgr->inst.addr is no longer
> +	 * valid (e.g., it was a transient CNI pod address that has been
> +	 * removed), all connections will fail with EADDRNOTAVAIL at
> +	 * ip6_dst_lookup_flow() before even sending a SYN.
> +	 *
> +	 * After ADDRNOTAVAIL_RESET_THRESHOLD consecutive failures,
> +	 * reset inst.addr to blank so that process_hello() will
> +	 * re-learn the source address from the next successful
> +	 * monitor connection. The nonce is preserved.
> +	 */
> +	if (addr_issue) {

Where addr_issue has been declared and initialized?

> +		int count = atomic_inc_return(&con->msgr->addr_notavail_count);
> +
> +		if (count == ADDRNOTAVAIL_RESET_THRESHOLD) {

Why not count >= ADDRNOTAVAIL_RESET_THRESHOLD?

> +			struct ceph_entity_addr *my_addr =
> +				&con->msgr->inst.addr;

It looks like you need to introduce a static inline function if you need to play
likewise tricks.

> +
> +			pr_warn("libceph: %d consecutive EADDRNOTAVAIL errors, resetting source address %s (will re-learn from monitor)\n",
> +				count, ceph_pr_addr(my_addr));
> +
> +			/*
> +			 * Zero out the address portion of in_addr but
> +			 * preserve ss_family, nonce, and type so the
> +			 * client identity is maintained and debug output
> +			 * remains readable. process_hello() checks
> +			 * ceph_addr_is_blank() and will fill in the new
> +			 * address from the monitor's addr_for_me response.
> +			 *
> +			 * We preserve ss_family so that ceph_pr_addr()
> +			 * shows e.g. "[::]:0" instead of
> +			 * "(unknown sockaddr family 0)".
> +			 */
> +			{

It is another reason of necessity of static inline function.

> +				sa_family_t family =
> +					get_unaligned(&my_addr->in_addr.ss_family);
> +				memset(&my_addr->in_addr, 0,
> +				       sizeof(my_addr->in_addr));
> +				put_unaligned(family,
> +					      &my_addr->in_addr.ss_family);

Do we have any existing function for this?

> +			}
> +			ceph_encode_my_addr(con->msgr);
> +		}
> +	}
> +
>  	WARN_ON(con->state == CEPH_CON_S_STANDBY ||
>  		con->state == CEPH_CON_S_CLOSED);
>  
> @@ -1740,6 +1790,7 @@ void ceph_messenger_init(struct ceph_messenger *msgr,
>  	ceph_encode_my_addr(msgr);
>  
>  	atomic_set(&msgr->stopping, 0);
> +	atomic_set(&msgr->addr_notavail_count, 0);
>  	write_pnet(&msgr->net, get_net(current->nsproxy->net_ns));
>  
>  	dout("%s %p\n", __func__, msgr);
> diff --git a/net/ceph/messenger_v1.c b/net/ceph/messenger_v1.c
> index 0cb61c76b9b87..4f3868f296c06 100644
> --- a/net/ceph/messenger_v1.c
> +++ b/net/ceph/messenger_v1.c
> @@ -736,6 +736,13 @@ static int process_banner(struct ceph_connection *con)
>  		ceph_encode_my_addr(con->msgr);
>  		dout("process_banner learned my addr is %s\n",
>  		     ceph_pr_addr(my_addr));
> +
> +		if (atomic_read(&con->msgr->addr_notavail_count) > 0) {
> +			pr_info("libceph: re-learned source address %s from peer %s\n",
> +				ceph_pr_addr(my_addr),
> +				ceph_pr_addr(&con->peer_addr));

Do we need to inform about this? I assume we could have bunch of messages in
system log.

> +			atomic_set(&con->msgr->addr_notavail_count, 0);
> +		}
>  	}
>  
>  	return 0;
> diff --git a/net/ceph/messenger_v2.c b/net/ceph/messenger_v2.c
> index bd608ffa06279..12ad9f571dcca 100644
> --- a/net/ceph/messenger_v2.c
> +++ b/net/ceph/messenger_v2.c
> @@ -2260,6 +2260,18 @@ static int process_hello(struct ceph_connection *con, void *p, void *end)
>  		dout("%s con %p set my addr %s, as seen by peer %s\n",
>  		     __func__, con, ceph_pr_addr(my_addr),
>  		     ceph_pr_addr(&con->peer_addr));
> +
> +		/*
> +		 * If we re-learned the address after a reset due to
> +		 * persistent EADDRNOTAVAIL, log it and clear the
> +		 * failure counter.
> +		 */
> +		if (atomic_read(&con->msgr->addr_notavail_count) > 0) {
> +			pr_info("libceph: re-learned source address %s from monitor %s\n",
> +				ceph_pr_addr(my_addr),
> +				ceph_pr_addr(&con->peer_addr));

The same question here.

Thanks,
Slava.

> +			atomic_set(&con->msgr->addr_notavail_count, 0);
> +		}
>  	} else {
>  		dout("%s con %p my addr already set %s\n",
>  		     __func__, con, ceph_pr_addr(my_addr));

  reply	other threads:[~2026-03-12 21:39 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-12  8:16 [PATCH v1 00/13] ceph/libceph: fix hung tasks and connection recovery during network disruptions Ionut Nechita (Wind River)
2026-03-12  8:16 ` [PATCH v1 01/13] libceph: handle EADDRNOTAVAIL more gracefully Ionut Nechita (Wind River)
2026-03-12 18:51   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 02/13] ceph: add timeout protection to ceph_mdsc_sync() path Ionut Nechita (Wind River)
2026-03-12 19:19   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 03/13] ceph: add timeout protection to ceph_osdc_sync() path Ionut Nechita (Wind River)
2026-03-12 19:26   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 04/13] ceph: fix race condition in cleanup_session_requests() Ionut Nechita (Wind River)
2026-03-12 19:32   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 05/13] ceph: add timeout protection to ceph_lock_wait_for_completion() Ionut Nechita (Wind River)
2026-03-12 19:38   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 06/13] ceph: set default timeout for MDS requests Ionut Nechita (Wind River)
2026-03-12 19:41   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 07/13] ceph: add timeout to caps wait in __ceph_get_caps() Ionut Nechita (Wind River)
2026-03-12 19:52   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 08/13] ceph: make ceph_start_io_write() killable Ionut Nechita (Wind River)
2026-03-12 20:02   ` Viacheslav Dubeyko
2026-03-12 20:45     ` Ionut Nechita (Wind River)
2026-03-13 18:28       ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 09/13] ceph: make remaining I/O lock functions killable Ionut Nechita (Wind River)
2026-03-12 20:05   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 10/13] ceph: force mdsmap refresh on persistent MDS connection failures Ionut Nechita (Wind River)
2026-03-12 21:23   ` Viacheslav Dubeyko
2026-03-12  8:16 ` [PATCH v1 11/13] libceph: reset source address on persistent EADDRNOTAVAIL Ionut Nechita (Wind River)
2026-03-12 21:39   ` Viacheslav Dubeyko [this message]
2026-03-12  8:16 ` [PATCH v1 12/13] libceph: force monitor reconnect " Ionut Nechita (Wind River)
2026-03-12  8:16 ` [PATCH v1 13/13] libceph: force host network namespace for kernel CephFS mounts Ionut Nechita (Wind River)
2026-03-16 15:28   ` Ilya Dryomov
2026-03-16 21:20     ` Ionut Nechita (Wind River)
2026-04-02 17:06       ` Ionut Nechita (Wind River)
2026-04-03 15:05         ` Ionut Nechita (Wind River)

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1aa4e598d3c0f45d408c66153addd3502ae432c0.camel@ibm.com \
    --to=slava.dubeyko@ibm.com \
    --cc=ceph-devel@vger.kernel.org \
    --cc=idryomov@gmail.com \
    --cc=ionut.nechita@windriver.com \
    --cc=ionut_n2001@yahoo.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=xiubli@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®