* [PATCH net-next v3 1/6] netconsole: add target_state enum
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 2/6] netconsole: convert 'enabled' flag to enum for clearer state management Andre Carvalho
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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 bb6e03a92956..7a7eba041e23 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.
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net-next v3 2/6] netconsole: convert 'enabled' flag to enum for clearer state management
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 1/6] netconsole: add target_state enum Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 3/6] netconsole: add STATE_DEACTIVATED to track targets disabled by low level Andre Carvalho
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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 7a7eba041e23..2d15f7ab7235 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;
@@ -1330,7 +1332,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);
@@ -1459,7 +1461,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;
}
@@ -1726,7 +1728,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);
}
@@ -1746,7 +1749,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
@@ -1902,7 +1906,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);
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net-next v3 3/6] netconsole: add STATE_DEACTIVATED to track targets disabled by low level
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 1/6] netconsole: add target_state enum Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 2/6] netconsole: convert 'enabled' flag to enum for clearer state management Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 4/6] netpoll: add wrapper around __netpoll_setup with dev reference Andre Carvalho
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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 2d15f7ab7235..5a374e6d178d 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) {
@@ -1461,7 +1470,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;
}
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net-next v3 4/6] netpoll: add wrapper around __netpoll_setup with dev reference
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
` (2 preceding siblings ...)
2025-11-09 11:05 ` [PATCH net-next v3 3/6] netconsole: add STATE_DEACTIVATED to track targets disabled by low level Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 5/6] netconsole: resume previously deactivated target Andre Carvalho
2025-11-09 11:05 ` [PATCH net-next v3 6/6] selftests: netconsole: validate target resume Andre Carvalho
5 siblings, 0 replies; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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 f22eec466040..345e74112674 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 c85f740065fc..4d25ba422d81 100644
--- a/net/core/netpoll.c
+++ b/net/core/netpoll.c
@@ -605,6 +605,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
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net-next v3 5/6] netconsole: resume previously deactivated target
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
` (3 preceding siblings ...)
2025-11-09 11:05 ` [PATCH net-next v3 4/6] netpoll: add wrapper around __netpoll_setup with dev reference Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-11 10:12 ` Breno Leitao
2025-11-09 11:05 ` [PATCH net-next v3 6/6] selftests: netconsole: validate target resume Andre Carvalho
5 siblings, 1 reply; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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.
Depending on how the target was setup (by mac or interface name), the
corresponding field is compared with the device being brought up.
Targets that are candidates for resuming are removed from the target list
and added to a temporarily list, as __netpoll_setup_hold might allocate.
__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 | 62 +++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 56 insertions(+), 6 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
index 5a374e6d178d..50d6df101c20 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.
@@ -1445,17 +1447,50 @@ 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;
+
+ 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);
+ }
+}
+
+/* Check if the target was bound by mac address. */
+static bool bound_by_mac(struct netconsole_target *nt)
+{
+ return is_valid_ether_addr(nt->np.dev_mac);
+}
+
+/* Checks if a target matches a device. */
+static bool target_match(struct netconsole_target *nt, struct net_device *ndev)
+{
+ if (bound_by_mac(nt))
+ return !memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN);
+ return !strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ);
+}
+
/* Handle network interface device notifications */
static int netconsole_netdev_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
- unsigned long flags;
- struct netconsole_target *nt, *tmp;
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct netconsole_target *nt, *tmp;
+ LIST_HEAD(resume_list);
bool stopped = false;
+ unsigned long flags;
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);
@@ -1475,11 +1510,26 @@ static int netconsole_netdev_event(struct notifier_block *this,
stopped = true;
}
}
+ if (nt->state == STATE_DEACTIVATED && event == NETDEV_UP &&
+ target_match(nt, dev))
+ list_move(&nt->list, &resume_list);
netconsole_target_put(nt);
}
spin_unlock_irqrestore(&target_list_lock, flags);
mutex_unlock(&target_cleanup_list_lock);
+ list_for_each_entry_safe(nt, tmp, &resume_list, list) {
+ maybe_resume_target(nt, dev);
+
+ /* At this point the target is either enabled or disabled and
+ * was cleaned up before getting deactivated. Either way, add it
+ * back to target list.
+ */
+ spin_lock_irqsave(&target_list_lock, flags);
+ list_move(&nt->list, &target_list);
+ spin_unlock_irqrestore(&target_list_lock, flags);
+ }
+
if (stopped) {
const char *msg = "had an event";
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH net-next v3 5/6] netconsole: resume previously deactivated target
2025-11-09 11:05 ` [PATCH net-next v3 5/6] netconsole: resume previously deactivated target Andre Carvalho
@ 2025-11-11 10:12 ` Breno Leitao
0 siblings, 0 replies; 9+ messages in thread
From: Breno Leitao @ 2025-11-11 10:12 UTC (permalink / raw)
To: Andre Carvalho
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
On Sun, Nov 09, 2025 at 11:05:55AM +0000, 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.
>
> Depending on how the target was setup (by mac or interface name), the
> corresponding field is compared with the device being brought up.
>
> Targets that are candidates for resuming are removed from the target list
> and added to a temporarily list, as __netpoll_setup_hold might allocate.
> __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 | 62 +++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 56 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
> index 5a374e6d178d..50d6df101c20 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.
* disabled. Internally, although both STATE_DISABLED and
* STATE_DEACTIVATED correspond to inactive targets, the latter is
* due to automatic interface state changes and will try
* recover automatically, if the interface comes back
* online.
> * 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.
> @@ -1445,17 +1447,50 @@ 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;
> +
> + 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);
> + }
> +}
I am not sure that helper is useful, I would simplify the last patch
with this one and write something like:
/* Attempts to resume logging to a deactivated target. */
static void maybe_resume_target(struct netconsole_target *nt,
struct net_device *ndev)
{
int ret;
ret = __netpoll_setup_hold(&nt->np, ndev);
if (ret) {
/* netpoll fails setup once, do not try again. */
nt->state = STATE_DISABLED;
return;
}
netdev_hold(ndev, &np->dev_tracker, GFP_KERNEL);
nt->state = STATE_ENABLED;
pr_info("network logging resumed on interface %s\n",
nt->np.dev_name);
}
> +
> +/* Check if the target was bound by mac address. */
> +static bool bound_by_mac(struct netconsole_target *nt)
> +{
> + return is_valid_ether_addr(nt->np.dev_mac);
> +}
Awesome. I liked this helper. It might be useful it some other places, and
eventually transformed into a specific type in the target (in case we need to
in the future)
Can we use it egress_dev also? If so, please separate this in a separate patch.
> +/* Checks if a target matches a device. */
> +static bool target_match(struct netconsole_target *nt, struct net_device *ndev)
> +{
> + if (bound_by_mac(nt))
> + return !memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN);
> + return !strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ);
> +}
> +
> /* Handle network interface device notifications */
> static int netconsole_netdev_event(struct notifier_block *this,
> unsigned long event, void *ptr)
> {
> - unsigned long flags;
> - struct netconsole_target *nt, *tmp;
> struct net_device *dev = netdev_notifier_info_to_dev(ptr);
> + struct netconsole_target *nt, *tmp;
> + LIST_HEAD(resume_list);
> bool stopped = false;
> + unsigned long flags;
>
> 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);
> @@ -1475,11 +1510,26 @@ static int netconsole_netdev_event(struct notifier_block *this,
> stopped = true;
> }
> }
> + if (nt->state == STATE_DEACTIVATED && event == NETDEV_UP &&
> + target_match(nt, dev))
> + list_move(&nt->list, &resume_list);
I think it would be better to move the nt->state == STATE_DEACTIVATED to target_match and use
the case above. As the following:
if (nt->np.dev == dev) {
switch (event) {
case NETDEV_CHANGENAME:
....
case NETDEV_UP:
if (target_match(nt, dev))
list_move(&nt->list, &resume_list);
> netconsole_target_put(nt);
> }
> spin_unlock_irqrestore(&target_list_lock, flags);
> mutex_unlock(&target_cleanup_list_lock);
>
Write a comment saying that maybe_resume_target() might be called with IRQ
enabled.
> + list_for_each_entry_safe(nt, tmp, &resume_list, list) {
> + maybe_resume_target(nt, dev);
> +
> + /* At this point the target is either enabled or disabled and
> + * was cleaned up before getting deactivated. Either way, add it
> + * back to target list.
> + */
> + spin_lock_irqsave(&target_list_lock, flags);
> + list_move(&nt->list, &target_list);
> + spin_unlock_irqrestore(&target_list_lock, flags);
> + }
> +
> if (stopped) {
> const char *msg = "had an event";
>
Also, extract the code below in a static function. Similar to
netconsole_process_cleanups_core(), but passing resume_list argument.
Let's try to keep netconsole_netdev_event() simple to read and reason about.
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next v3 6/6] selftests: netconsole: validate target resume
2025-11-09 11:05 [PATCH net-next v3 0/6] netconsole: support automatic target recovery Andre Carvalho
` (4 preceding siblings ...)
2025-11-09 11:05 ` [PATCH net-next v3 5/6] netconsole: resume previously deactivated target Andre Carvalho
@ 2025-11-09 11:05 ` Andre Carvalho
2025-11-11 10:27 ` Breno Leitao
5 siblings, 1 reply; 9+ messages in thread
From: Andre Carvalho @ 2025-11-09 11:05 UTC (permalink / raw)
To: Breno Leitao, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Shuah Khan, Simon Horman
Cc: netdev, linux-kernel, linux-kselftest, Andre Carvalho
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
we are able to resume targets that were bound by mac and interface name.
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 68e0bb603a9d..fbd81bec66cd 100644
--- a/tools/testing/selftests/drivers/net/Makefile
+++ b/tools/testing/selftests/drivers/net/Makefile
@@ -17,6 +17,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 8e1085e89647..88b4bdfa84cf 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 000000000000..404df7abef1b
--- /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}"
--
2.51.2
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH net-next v3 6/6] selftests: netconsole: validate target resume
2025-11-09 11:05 ` [PATCH net-next v3 6/6] selftests: netconsole: validate target resume Andre Carvalho
@ 2025-11-11 10:27 ` Breno Leitao
0 siblings, 0 replies; 9+ messages in thread
From: Breno Leitao @ 2025-11-11 10:27 UTC (permalink / raw)
To: Andre Carvalho
Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Shuah Khan, Simon Horman, netdev, linux-kernel,
linux-kselftest
On Sun, Nov 09, 2025 at 11:05:56AM +0000, Andre Carvalho wrote:
> 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
> we are able to resume targets that were bound by mac and interface name.
>
> 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 68e0bb603a9d..fbd81bec66cd 100644
> --- a/tools/testing/selftests/drivers/net/Makefile
> +++ b/tools/testing/selftests/drivers/net/Makefile
> @@ -17,6 +17,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 8e1085e89647..88b4bdfa84cf 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"
local TARGET_PATH="${NETCONS_CONFIGFS}"/"${TARGET}"
> +
> + if [ "${STATE}" == "enabled" ]
> + then
> + ENABLED=1
Shouldn't they be local variables in here ?
> + else
> + ENABLED=0
> + fi
> +
> + if [ ! -f "$FILE" ]; then
if [ ! -f "${TARGET_PATH}" ]; then
> + echo "FAIL: Target does not exist." >&2
> + exit "${ksft_fail}"
> + fi
> +
> + slowwait 2 sh -c "test -n \"\$(grep \"${ENABLED}\" \"${FILE}\")\"" || {
slowwait 2 sh -c "test -n \"\$(grep \"${ENABLED}\" \"${TARGET_PATH}/enabled\")\"" || {
> + echo "FAIL: ${TARGET} is not ${STATE}." >&2
> + }
> +}
> +
> # 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 000000000000..404df7abef1b
> --- /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.
Comment above is a bit harder to understand.
> +#
> +# 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
can we keep these trap lines outside of the loop?
> +
> + # 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
Why do we need to remove netconsole module in here?
Thanks for this patch. This is solving a real issue we have right now.
--breno
^ permalink raw reply [flat|nested] 9+ messages in thread