This patchset introduces target resume capability to netconsole allowing it to recover targets when underlying low-level interface comes back online.
The patchset starts by refactoring netconsole state representation in order to allow representing deactivated targets (targets that are disabled due to interfaces going down). It then modifies netconsole to handle NETDEV_UP events for such targets and setups netpoll.
The patchset includes a selftest that validates netconsole target state transitions and that target is functional after resumed.
Signed-off-by: Andre Carvalho asantostc@gmail.com --- Changes in v2: - Attempt to resume target in the same thread, instead of using workqueue . - Add wrapper around __netpoll_setup (patch 4). - Renamed resume_target to maybe_resume_target and moved conditionals to inside its implementation, keeping code more clear. - Verify that device addr matches target mac address when target was setup using mac. - Update selftest to cover targets bound by mac and interface name. - Fix typo in selftest comment and sort tests alphabetically in Makefile. - Link to v1: https://lore.kernel.org/r/20250909-netcons-retrigger-v1-0-3aea904926cf@gmail...
--- Andre Carvalho (4): netconsole: convert 'enabled' flag to enum for clearer state management netpoll: add wrapper around __netpoll_setup with dev reference netconsole: resume previously deactivated target selftests: netconsole: validate target reactivation
Breno Leitao (2): netconsole: add target_state enum netconsole: add STATE_DEACTIVATED to track targets disabled by low level
drivers/net/netconsole.c | 102 +++++++++++++++------ include/linux/netpoll.h | 1 + net/core/netpoll.c | 20 ++++ tools/testing/selftests/drivers/net/Makefile | 1 + .../selftests/drivers/net/lib/sh/lib_netcons.sh | 30 +++++- .../selftests/drivers/net/netcons_resume.sh | 92 +++++++++++++++++++ 6 files changed, 216 insertions(+), 30 deletions(-) --- base-commit: 312e6f7676e63bbb9b81e5c68e580a9f776cc6f0 change-id: 20250816-netcons-retrigger-a4f547bfc867
Best regards,
From: Breno Leitao leitao@debian.org
Introduces a enum to track netconsole target state which is going to replace the enabled boolean.
Signed-off-by: Breno Leitao leitao@debian.org Signed-off-by: Andre Carvalho asantostc@gmail.com --- drivers/net/netconsole.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 194570443493b1417570d3fe3250281cffe01316..b5e6a9fdc3155196d1fd7e81def584360ecbc3d2 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -117,6 +117,11 @@ enum sysdata_feature { SYSDATA_MSGID = BIT(3), };
+enum target_state { + STATE_DISABLED, + STATE_ENABLED, +}; + /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list.
This patch refactors the netconsole driver's target enabled state from a simple boolean to an explicit enum (`target_state`).
This allow the states to be expanded to a new state in the upcoming change.
Co-developed-by: Breno Leitao leitao@debian.org Signed-off-by: Breno Leitao leitao@debian.org Signed-off-by: Andre Carvalho asantostc@gmail.com --- drivers/net/netconsole.c | 52 ++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 24 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index b5e6a9fdc3155196d1fd7e81def584360ecbc3d2..688ed670b37b56ab4a03d43fb3de94ca0e6a8360 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -132,12 +132,12 @@ enum target_state { * @sysdata_fields: Sysdata features enabled. * @msgcounter: Message sent counter. * @stats: Packet send stats for the target. Used for debugging. - * @enabled: On / off knob to enable / disable target. + * @state: State of the target. * Visible from userspace (read-write). * We maintain a strict 1:1 correspondence between this and * whether the corresponding netpoll is active or inactive. * Also, other parameters of a target may be modified at - * runtime only when it is disabled (enabled == 0). + * runtime only when it is disabled (state == STATE_DISABLED). * @extended: Denotes whether console is extended or not. * @release: Denotes whether kernel release version should be prepended * to the message. Depends on extended console. @@ -165,7 +165,7 @@ struct netconsole_target { u32 msgcounter; #endif struct netconsole_target_stats stats; - bool enabled; + enum target_state state; bool extended; bool release; struct netpoll np; @@ -257,6 +257,7 @@ static struct netconsole_target *alloc_and_init(void) nt->np.local_port = 6665; nt->np.remote_port = 6666; eth_broadcast_addr(nt->np.remote_mac); + nt->state = STATE_DISABLED;
return nt; } @@ -275,7 +276,7 @@ static void netconsole_process_cleanups_core(void) mutex_lock(&target_cleanup_list_lock); list_for_each_entry_safe(nt, tmp, &target_cleanup_list, list) { /* all entries in the cleanup_list needs to be disabled */ - WARN_ON_ONCE(nt->enabled); + WARN_ON_ONCE(nt->state == STATE_ENABLED); do_netpoll_cleanup(&nt->np); /* moved the cleaned target to target_list. Need to hold both * locks @@ -398,7 +399,7 @@ static void trim_newline(char *s, size_t maxlen)
static ssize_t enabled_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->enabled); + return sysfs_emit(buf, "%d\n", to_target(item)->state == STATE_ENABLED); }
static ssize_t extended_show(struct config_item *item, char *buf) @@ -565,8 +566,8 @@ static ssize_t enabled_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); + bool enabled, current_enabled; unsigned long flags; - bool enabled; ssize_t ret;
mutex_lock(&dynamic_netconsole_mutex); @@ -575,9 +576,10 @@ static ssize_t enabled_store(struct config_item *item, goto out_unlock;
ret = -EINVAL; - if (enabled == nt->enabled) { + current_enabled = nt->state == STATE_ENABLED; + if (enabled == current_enabled) { pr_info("network logging has already %s\n", - nt->enabled ? "started" : "stopped"); + current_enabled ? "started" : "stopped"); goto out_unlock; }
@@ -610,16 +612,16 @@ static ssize_t enabled_store(struct config_item *item, if (ret) goto out_unlock;
- nt->enabled = true; + nt->state = STATE_ENABLED; pr_info("network logging started\n"); } else { /* false */ /* We need to disable the netconsole before cleaning it up * otherwise we might end up in write_msg() with - * nt->np.dev == NULL and nt->enabled == true + * nt->np.dev == NULL and nt->state == STATE_ENABLED */ mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); - nt->enabled = false; + nt->state = STATE_DISABLED; /* Remove the target from the list, while holding * target_list_lock */ @@ -648,7 +650,7 @@ static ssize_t release_store(struct config_item *item, const char *buf, ssize_t ret;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); ret = -EINVAL; @@ -675,7 +677,7 @@ static ssize_t extended_store(struct config_item *item, const char *buf, ssize_t ret;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); ret = -EINVAL; @@ -699,7 +701,7 @@ static ssize_t dev_name_store(struct config_item *item, const char *buf, struct netconsole_target *nt = to_target(item);
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); mutex_unlock(&dynamic_netconsole_mutex); @@ -720,7 +722,7 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, ssize_t ret = -EINVAL;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); goto out_unlock; @@ -742,7 +744,7 @@ static ssize_t remote_port_store(struct config_item *item, ssize_t ret = -EINVAL;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); goto out_unlock; @@ -765,7 +767,7 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf, int ipv6;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); goto out_unlock; @@ -790,7 +792,7 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf, int ipv6;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); goto out_unlock; @@ -839,7 +841,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, ssize_t ret = -EINVAL;
mutex_lock(&dynamic_netconsole_mutex); - if (nt->enabled) { + if (nt->state == STATE_ENABLED) { pr_err("target (%s) is enabled, disable to update parameters\n", config_item_name(&nt->group.cg_item)); goto out_unlock; @@ -1315,7 +1317,7 @@ static void drop_netconsole_target(struct config_group *group, * The target may have never been enabled, or was manually disabled * before being removed so netpoll may have already been cleaned up. */ - if (nt->enabled) + if (nt->state == STATE_ENABLED) netpoll_cleanup(&nt->np);
config_item_put(&nt->group.cg_item); @@ -1444,7 +1446,7 @@ static int netconsole_netdev_event(struct notifier_block *this, case NETDEV_RELEASE: case NETDEV_JOIN: case NETDEV_UNREGISTER: - nt->enabled = false; + nt->state = STATE_DISABLED; list_move(&nt->list, &target_cleanup_list); stopped = true; } @@ -1711,7 +1713,8 @@ static void write_ext_msg(struct console *con, const char *msg,
spin_lock_irqsave(&target_list_lock, flags); list_for_each_entry(nt, &target_list, list) - if (nt->extended && nt->enabled && netif_running(nt->np.dev)) + if (nt->extended && nt->state == STATE_ENABLED && + netif_running(nt->np.dev)) send_ext_msg_udp(nt, msg, len); spin_unlock_irqrestore(&target_list_lock, flags); } @@ -1731,7 +1734,8 @@ static void write_msg(struct console *con, const char *msg, unsigned int len)
spin_lock_irqsave(&target_list_lock, flags); list_for_each_entry(nt, &target_list, list) { - if (!nt->extended && nt->enabled && netif_running(nt->np.dev)) { + if (!nt->extended && nt->state == STATE_ENABLED && + netif_running(nt->np.dev)) { /* * We nest this inside the for-each-target loop above * so that we're able to get as much logging out to @@ -1887,7 +1891,7 @@ static struct netconsole_target *alloc_param_target(char *target_config, */ goto fail; } else { - nt->enabled = true; + nt->state = STATE_ENABLED; } populate_configfs_item(nt, cmdline_count);
From: Breno Leitao leitao@debian.org
When the low level interface brings a netconsole target down, record this using a new STATE_DEACTIVATED state. This allows netconsole to distinguish between targets explicitly disabled by users and those deactivated due to interface state changes.
It also enables automatic recovery and re-enabling of targets if the underlying low-level interfaces come back online.
From a code perspective, anything that is not STATE_ENABLED is disabled. Mark the device that is down due to NETDEV_UNREGISTER as STATE_DEACTIVATED, this, should be the same as STATE_DISABLED from a code perspective.
Signed-off-by: Breno Leitao leitao@debian.org Signed-off-by: Andre Carvalho asantostc@gmail.com --- drivers/net/netconsole.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 688ed670b37b56ab4a03d43fb3de94ca0e6a8360..59d770bb4baa5f9616b10c0dfb39ed45a4eb7710 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -120,6 +120,7 @@ enum sysdata_feature { enum target_state { STATE_DISABLED, STATE_ENABLED, + STATE_DEACTIVATED, };
/** @@ -575,6 +576,14 @@ static ssize_t enabled_store(struct config_item *item, if (ret) goto out_unlock;
+ /* When the user explicitly enables or disables a target that is + * currently deactivated, reset its state to disabled. The DEACTIVATED + * state only tracks interface-driven deactivation and should _not_ + * persist when the user manually changes the target's enabled state. + */ + if (nt->state == STATE_DEACTIVATED) + nt->state = STATE_DISABLED; + ret = -EINVAL; current_enabled = nt->state == STATE_ENABLED; if (enabled == current_enabled) { @@ -1446,7 +1455,7 @@ static int netconsole_netdev_event(struct notifier_block *this, case NETDEV_RELEASE: case NETDEV_JOIN: case NETDEV_UNREGISTER: - nt->state = STATE_DISABLED; + nt->state = STATE_DEACTIVATED; list_move(&nt->list, &target_cleanup_list); stopped = true; }
Introduce __netpoll_setup_hold() which wraps __netpoll_setup() and on success holds a reference to the device. This helper requires caller to already hold RNTL and should be paired with netpoll_cleanup to ensure proper handling of the reference.
This helper is going to be used by netconsole to setup netpoll in response to a NETDEV_UP event. Since netconsole always perform cleanup using netpoll_cleanup, this will ensure that reference counting is correct and handled entirely inside netpoll.
Signed-off-by: Andre Carvalho asantostc@gmail.com --- include/linux/netpoll.h | 1 + net/core/netpoll.c | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+)
diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index f22eec4660405eaa654eb7746cbfdc89113fe312..345e741126748c0ee8d55dba594d782bced4eeed 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -69,6 +69,7 @@ static inline void netpoll_poll_enable(struct net_device *dev) { return; }
int netpoll_send_udp(struct netpoll *np, const char *msg, int len); int __netpoll_setup(struct netpoll *np, struct net_device *ndev); +int __netpoll_setup_hold(struct netpoll *np, struct net_device *ndev); int netpoll_setup(struct netpoll *np); void __netpoll_free(struct netpoll *np); void netpoll_cleanup(struct netpoll *np); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 60a05d3b7c2491096f79ea6cf82eeef222c3eac2..bf563c4259f6cb19c31613ff277eb5a0e2165e43 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -608,6 +608,26 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) } EXPORT_SYMBOL_GPL(__netpoll_setup);
+/* + * Wrapper around __netpoll_setup that holds a reference to the device. + * The caller must pair this with netpoll_cleanup() to release the reference. + */ +int __netpoll_setup_hold(struct netpoll *np, struct net_device *ndev) +{ + int err; + + ASSERT_RTNL(); + + err = __netpoll_setup(np, ndev); + if (err) + return err; + + netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL); + + return 0; +} +EXPORT_SYMBOL_GPL(__netpoll_setup_hold); + /* * Returns a pointer to a string representation of the identifier used * to select the egress interface for the given netpoll instance. buf
Attempt to resume a previously deactivated target when the associated interface comes back (NETDEV_UP event is received) by calling __netpoll_setup_hold on the device.
For targets that were initally setup by mac address, their address is also compared with the interface address (while still verifying that the interface name matches).
__netpoll_setup_hold assumes RTNL is held (which is guaranteed to be the case when handling the event) and holds a reference to the device in case of success. This reference will be removed upon target (or netconsole) removal by netpoll_cleanup.
Target transitions to STATE_DISABLED in case of failures resuming it to avoid retrying the same target indefinitely.
Signed-off-by: Andre Carvalho asantostc@gmail.com --- drivers/net/netconsole.c | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 59d770bb4baa5f9616b10c0dfb39ed45a4eb7710..96485e979e61e0ed6c850ae3b29f46d529923f2d 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -135,10 +135,12 @@ enum target_state { * @stats: Packet send stats for the target. Used for debugging. * @state: State of the target. * Visible from userspace (read-write). - * We maintain a strict 1:1 correspondence between this and - * whether the corresponding netpoll is active or inactive. + * From a userspace perspective, the target is either enabled or + * disabled. Internally, although both STATE_DISABLED and + * STATE_DEACTIVATED correspond to inactive netpoll the latter is + * due to interface state changes and may recover automatically. * Also, other parameters of a target may be modified at - * runtime only when it is disabled (state == STATE_DISABLED). + * runtime only when it is disabled (state != STATE_ENABLED). * @extended: Denotes whether console is extended or not. * @release: Denotes whether kernel release version should be prepended * to the message. Depends on extended console. @@ -1430,6 +1432,31 @@ static int prepare_extradata(struct netconsole_target *nt) } #endif /* CONFIG_NETCONSOLE_DYNAMIC */
+/* Attempts to resume logging to a deactivated target. */ +static void maybe_resume_target(struct netconsole_target *nt, + struct net_device *ndev) +{ + int ret; + + if (strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ)) + return; + + /* for targets specified by mac, also verify it matches the addr */ + if (!is_broadcast_ether_addr(nt->np.dev_mac) && + memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN)) + return; + + ret = __netpoll_setup_hold(&nt->np, ndev); + if (ret) { + /* netpoll fails setup once, do not try again. */ + nt->state = STATE_DISABLED; + } else { + nt->state = STATE_ENABLED; + pr_info("network logging resumed on interface %s\n", + nt->np.dev_name); + } +} + /* Handle network interface device notifications */ static int netconsole_netdev_event(struct notifier_block *this, unsigned long event, void *ptr) @@ -1440,7 +1467,8 @@ static int netconsole_netdev_event(struct notifier_block *this, bool stopped = false;
if (!(event == NETDEV_CHANGENAME || event == NETDEV_UNREGISTER || - event == NETDEV_RELEASE || event == NETDEV_JOIN)) + event == NETDEV_RELEASE || event == NETDEV_JOIN || + event == NETDEV_UP)) goto done;
mutex_lock(&target_cleanup_list_lock); @@ -1460,6 +1488,8 @@ static int netconsole_netdev_event(struct notifier_block *this, stopped = true; } } + if (nt->state == STATE_DEACTIVATED && event == NETDEV_UP) + maybe_resume_target(nt, dev); netconsole_target_put(nt); } spin_unlock_irqrestore(&target_list_lock, flags);
On Sun, 21 Sep 2025 22:55:45 +0100 Andre Carvalho wrote:
if (nt->state == STATE_DEACTIVATED && event == NETDEV_UP)
maybe_resume_target(nt, dev);
This uses GFP_KERNEL
netconsole_target_put(nt);
} spin_unlock_irqrestore(&target_list_lock, flags);
And we're under a spin lock.
This gets hit in the selftest you're adding so please triple check the kernel config that you're testing with.
Hi Jakub,
On Mon, Sep 22, 2025 at 04:50:57PM -0700, Jakub Kicinski wrote:
This gets hit in the selftest you're adding so please triple check the kernel config that you're testing with.
Sorry for the silly mistake. I'll make sure to adjust my local testing to include running all selftests with kernel/configs/debug.config to catch these before sending the patches.
Hello Andre,
On Sun, Sep 21, 2025 at 10:55:45PM +0100, Andre Carvalho wrote:
Attempt to resume a previously deactivated target when the associated interface comes back (NETDEV_UP event is received) by calling __netpoll_setup_hold on the device.
For targets that were initally setup by mac address, their address is also compared with the interface address (while still verifying that the interface name matches).
For targets that are set by the mac address, they don't necessarily get np.dev_name populated, do they?
I am double checking netpoll_setup(), and if is_valid_ether_addr(np->dev_mac), I don't see np.dev_name being populated.
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 59d770bb4baa5f9616b10c0dfb39ed45a4eb7710..96485e979e61e0ed6c850ae3b29f46d529923f2d 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c +/* Attempts to resume logging to a deactivated target. */ +static void maybe_resume_target(struct netconsole_target *nt,
struct net_device *ndev)
+{
- int ret;
- if (strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ))
return;
But here, you expect that np.dev_name is populate, which I suppose it will fail if the target is binding by np->dev_mac.
Should we also compare that the mac doesn't match before returning?
--breno
Hi Breno,
On Tue, Sep 23, 2025 at 05:22:25AM -0700, Breno Leitao wrote:
For targets that are set by the mac address, they don't necessarily get np.dev_name populated, do they?
I am double checking netpoll_setup(), and if is_valid_ether_addr(np->dev_mac), I don't see np.dev_name being populated.
I was not expecting it to be the case either, bu my understanding is that np.dev_name does get populated by __netpoll_setup, which is called unconditionally at the end of netpoll_setup. __netpoll_setup eventually does:
np->dev = ndev; strscpy(np->dev_name, ndev->name, IFNAMSIZ);
I've confirmed that for targets bound by mac, np->dev_name is empty before these lines but then it is correctly populated here. For targets create by name, np->dev_name is already correctly set prior to this. Please, let me know if I'm missing something.
Should we also compare that the mac doesn't match before returning?
Even though the above seem to work on my tests, I was not 100% sure we wanted to also check the dev_name when we initially bound by mac. I've also considered the approach below, which I think achieves what you are suggesting:
if (!is_broadcast_ether_addr(nt->np.dev_mac)) { if(memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN)) return; } else if (strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ)) { return; }
Let me know if you prefer this approach, it would allow resuming targets in case even if their dev_name changes.
--breno
Hello Andre,
On Tue, Sep 23, 2025 at 08:30:39PM +0100, Andre Carvalho wrote:
On Tue, Sep 23, 2025 at 05:22:25AM -0700, Breno Leitao wrote:
For targets that are set by the mac address, they don't necessarily get np.dev_name populated, do they?
I am double checking netpoll_setup(), and if is_valid_ether_addr(np->dev_mac), I don't see np.dev_name being populated.
I was not expecting it to be the case either, bu my understanding is that np.dev_name does get populated by __netpoll_setup, which is called unconditionally at the end of netpoll_setup. __netpoll_setup eventually does:
np->dev = ndev; strscpy(np->dev_name, ndev->name, IFNAMSIZ);
I've confirmed that for targets bound by mac, np->dev_name is empty before these lines but then it is correctly populated here. For targets create by name, np->dev_name is already correctly set prior to this. Please, let me know if I'm missing something.
Thanks for confirming it. I think this might cause some semantics confusion for the user, given it is asking it to bind to mac, and later, netconsole is binding by dev_name.
Let's say the following case:
1) netconsole is configured to bind to mac X which happens to be on eth0. 2) there is a PCI downstream failure which causes a re-enumeration 3) netconsole will get DEACTIVATED during phase 2 4) After the re-enumeration, eth0 becomes some other and interface (not the one with mac X). 5) Now you are going to bind do eth0 which is not the one with mac X.
Should we also compare that the mac doesn't match before returning?
Even though the above seem to work on my tests, I was not 100% sure we wanted to also check the dev_name when we initially bound by mac. I've also considered the approach below, which I think achieves what you are suggesting:
if (!is_broadcast_ether_addr(nt->np.dev_mac)) { if(memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN)) return; } else if (strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ)) { return; }
Let me know if you prefer this approach, it would allow resuming targets in case even if their dev_name changes.
I would prefer this approach than the current one, this would avoid the problem above.
The other option is to always populate the mac during netpoll setup and then always resume based on mac. This seems a more precise resume.
In this case, if the device goes to DEACTIVATED, then np.dev_mac will be populated, and you only compare it to check if you want to resume it.
Hi Breno,
On Wed, Sep 24, 2025 at 01:26:16AM -0700, Breno Leitao wrote:
The other option is to always populate the mac during netpoll setup and then always resume based on mac. This seems a more precise resume.
In this case, if the device goes to DEACTIVATED, then np.dev_mac will be populated, and you only compare it to check if you want to resume it.
This sounds good to me. I've done some initial testing patching __netpoll_setup to always set np->dev_mac, changing maybe_resume_target to simply compare the mac as you suggested and seems like this approach works.
I'll do more testing and analysis to see if I see any issue with the approach. If all goes well, I can include these changes in v3. I'll report back in case I find any problems.
On Wed, Sep 24, 2025 at 11:26:58PM +0100, Andre Carvalho wrote:
Hi Breno,
On Wed, Sep 24, 2025 at 01:26:16AM -0700, Breno Leitao wrote:
The other option is to always populate the mac during netpoll setup and then always resume based on mac. This seems a more precise resume.
In this case, if the device goes to DEACTIVATED, then np.dev_mac will be populated, and you only compare it to check if you want to resume it.
This sounds good to me. I've done some initial testing patching __netpoll_setup to always set np->dev_mac, changing maybe_resume_target to simply compare the mac as you suggested and seems like this approach works.
Thanks. You probably want to clean the dev_mac once the is disabled for such case. in other words, if user configured a target to be dev_name bound, dev_mac might be NULL once the interface got disbled.
So, if user disable the interface, it should unbound from the mac. In case the user re-enable it later, it needs to bind by dev_name instead of dev_mac.
Introduce a new netconsole selftest to validate that netconsole is able to resume a deactivated target when the low level interface comes back.
The test setups the network using netdevsim, creates a netconsole target and then remove/add netdevsim in order to bring the same interfaces back. Afterwards, the test validates that the target works as expected.
Targets are created via cmdline parameters to the module to ensure that resuming works for targets bound by interface name and mac address.
Signed-off-by: Andre Carvalho asantostc@gmail.com --- tools/testing/selftests/drivers/net/Makefile | 1 + .../selftests/drivers/net/lib/sh/lib_netcons.sh | 30 ++++++- .../selftests/drivers/net/netcons_resume.sh | 92 ++++++++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/drivers/net/Makefile b/tools/testing/selftests/drivers/net/Makefile index 984ece05f7f92e836592107ba4c692da6d8ce1b3..a40b50c66d530b3fcbeaf93ca46f79380b3a1949 100644 --- a/tools/testing/selftests/drivers/net/Makefile +++ b/tools/testing/selftests/drivers/net/Makefile @@ -16,6 +16,7 @@ TEST_PROGS := \ netcons_cmdline.sh \ netcons_fragmented_msg.sh \ netcons_overflow.sh \ + netcons_resume.sh \ netcons_sysdata.sh \ netpoll_basic.py \ ping.py \ diff --git a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh index 8e1085e896472d5c87ec8b236240878a5b2d00d2..88b4bdfa84cf4ab67ff0e04c3ed88e5ae9df49d2 100644 --- a/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh +++ b/tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh @@ -186,12 +186,13 @@ function do_cleanup() { }
function cleanup() { + local TARGETPATH=${1:-${NETCONS_PATH}} # delete netconsole dynamic reconfiguration - echo 0 > "${NETCONS_PATH}"/enabled + echo 0 > "${TARGETPATH}"/enabled # Remove all the keys that got created during the selftest - find "${NETCONS_PATH}/userdata/" -mindepth 1 -type d -delete + find "${TARGETPATH}/userdata/" -mindepth 1 -type d -delete # Remove the configfs entry - rmdir "${NETCONS_PATH}" + rmdir "${TARGETPATH}"
do_cleanup } @@ -350,6 +351,29 @@ function check_netconsole_module() { fi }
+function wait_target_state() { + local TARGET=${1} + local STATE=${2} + local FILE="${NETCONS_CONFIGFS}"/"${TARGET}"/"enabled" + + if [ "${STATE}" == "enabled" ] + then + ENABLED=1 + else + ENABLED=0 + fi + + if [ ! -f "$FILE" ]; then + echo "FAIL: Target does not exist." >&2 + exit "${ksft_fail}" + fi + + slowwait 2 sh -c "test -n "$(grep "${ENABLED}" "${FILE}")"" || { + echo "FAIL: ${TARGET} is not ${STATE}." >&2 + exit "${ksft_fail}" + } +} + # A wrapper to translate protocol version to udp version function wait_for_port() { local NAMESPACE=${1} diff --git a/tools/testing/selftests/drivers/net/netcons_resume.sh b/tools/testing/selftests/drivers/net/netcons_resume.sh new file mode 100755 index 0000000000000000000000000000000000000000..404df7abef1bcdbd29a128c304ac9b39f19fc82d --- /dev/null +++ b/tools/testing/selftests/drivers/net/netcons_resume.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0 + +# This test validates that netconsole is able to resume a target that was +# deactivated when its interface was removed when the interface is brought +# back up. +# +# The test configures a netconsole target and then removes netdevsim module to +# cause the interface to disappear. Targets are configured via cmdline to ensure +# targets bound by interface name and mac address can be resumed. +# The test verifies that the target moved to disabled state before adding +# netdevsim and the interface back. +# +# Finally, the test verifies that the target is re-enabled automatically and +# the message is received on the destination interface. +# +# Author: Andre Carvalho asantostc@gmail.com + +set -euo pipefail + +SCRIPTDIR=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")") + +source "${SCRIPTDIR}"/lib/sh/lib_netcons.sh + +modprobe netdevsim 2> /dev/null || true +rmmod netconsole 2> /dev/null || true + +check_netconsole_module + +# Run the test twice, with different cmdline parameters +for BINDMODE in "ifname" "mac" +do + echo "Running with bind mode: ${BINDMODE}" >&2 + # Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5) + echo "6 5" > /proc/sys/kernel/printk + + # Create one namespace and two interfaces + set_network + trap do_cleanup EXIT + + # Create the command line for netconsole, with the configuration from + # the function above + CMDLINE=$(create_cmdline_str "${BINDMODE}") + + # The content of kmsg will be save to the following file + OUTPUT_FILE="/tmp/${TARGET}-${BINDMODE}" + + # Load the module, with the cmdline set + modprobe netconsole "${CMDLINE}" + # Expose cmdline target in configfs + mkdir ${NETCONS_CONFIGFS}"/cmdline0" + trap 'cleanup "${NETCONS_CONFIGFS}"/cmdline0' EXIT + + # Target should be enabled + wait_target_state "cmdline0" "enabled" + + # Remove low level module + rmmod netdevsim + # Target should be disabled + wait_target_state "cmdline0" "disabled" + + # Add back low level module + modprobe netdevsim + # Recreate namespace and two interfaces + set_network + # Target should be enabled again + wait_target_state "cmdline0" "enabled" + + # Listen for netconsole port inside the namespace and destination + # interface + listen_port_and_save_to "${OUTPUT_FILE}" & + # Wait for socat to start and listen to the port. + wait_local_port_listen "${NAMESPACE}" "${PORT}" udp + # Send the message + echo "${MSG}: ${TARGET}" > /dev/kmsg + # Wait until socat saves the file to disk + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" + # Make sure the message was received in the dst part + # and exit + validate_msg "${OUTPUT_FILE}" + + # kill socat in case it is still running + pkill_socat + # Cleanup & unload the module + cleanup "${NETCONS_CONFIGFS}/cmdline0" + rmmod netconsole + trap - EXIT + + echo "${BINDMODE} : Test passed" >&2 +done + +exit "${ksft_pass}"
linux-kselftest-mirror@lists.linaro.org