// SPDX-License-Identifier: GPL-2.0-or-later/* * linux/drivers/net/netconsole.c * * Copyright (C) 2001 Ingo Molnar <mingo@redhat.com> * * This file contains the implementation of an IRQ-safe, crash-safe * kernel console implementation that outputs kernel messages to the * network. * * Modification history: * * 2001-09-17 started by Ingo Molnar. * 2003-08-11 2.6 port by Matt Mackall * simplified options * generic card hooks * works non-modular * 2003-09-07 rewritten with netpoll api */ /**************************************************************** * ****************************************************************/ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/mm.h>#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/console.h>#include <linux/moduleparam.h>#include <linux/kernel.h>#include <linux/string.h>#include <linux/ip.h>#include <linux/ipv6.h>#include <linux/udp.h>#include <linux/netpoll.h>#include <linux/inet.h>#include <linux/unaligned.h>#include <net/ip6_checksum.h>#include <linux/configfs.h>#include <linux/etherdevice.h>#include <linux/hex.h>#include <linux/u64_stats_sync.h>#include <linux/utsname.h>#include <linux/rtnetlink.h>#include <linux/workqueue.h> MODULE_AUTHOR("Matt Mackall <mpm@selenic.com>");MODULE_DESCRIPTION("Console driver for network interfaces");MODULE_LICENSE("GPL");MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_PARAM_LENGTH 256#define MAX_EXTRADATA_ENTRY_LEN 256#define MAX_EXTRADATA_VALUE_LEN 200/* The number 3 comes from userdata entry format characters (' ', '=', '\n') */#define MAX_EXTRADATA_NAME_LEN (MAX_EXTRADATA_ENTRY_LEN - \ MAX_EXTRADATA_VALUE_LEN - 3)#define MAX_USERDATA_ITEMS 256#define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH];module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0);MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]"); static bool oops_only;module_param(oops_only, bool, 0600);MODULE_PARM_DESC(oops_only, "Only log oops messages"); #define NETCONSOLE_PARAM_TARGET_PREFIX "cmdline" #ifndef MODULEstatic int __init option_setup(char *opt){ strscpy(config, opt, MAX_PARAM_LENGTH); return 1;}__setup("netconsole=", option_setup);#endif /* MODULE */ /* Linked list of all configured targets */static LIST_HEAD(target_list);/* target_cleanup_list is used to track targets that need to be cleaned outside * of target_list_lock. It should be cleaned in the same function it is * populated. */static LIST_HEAD(target_cleanup_list); /* This needs to be a spinlock because write_msg() cannot sleep */static DEFINE_SPINLOCK(target_list_lock);/* This needs to be a mutex because netpoll_cleanup might sleep */static DEFINE_MUTEX(target_cleanup_list_lock); static struct workqueue_struct *netconsole_wq; /* * Console driver for netconsoles. Register only consoles that have * an associated target of the same type. */static struct console netconsole_ext, netconsole; struct netconsole_target_stats { u64_stats_t xmit_drop_count; u64_stats_t enomem_count; struct u64_stats_sync syncp;}; enum console_type { CONS_BASIC = BIT(0), CONS_EXTENDED = BIT(1),}; /* Features enabled in sysdata. Contrary to userdata, this data is populated by * the kernel. The fields are designed as bitwise flags, allowing multiple * features to be set in sysdata_fields. */enum sysdata_feature { /* Populate the CPU that sends the message */ SYSDATA_CPU_NR = BIT(0), /* Populate the task name (as in current->comm) in sysdata */ SYSDATA_TASKNAME = BIT(1), /* Kernel release/version as part of sysdata */ SYSDATA_RELEASE = BIT(2), /* Include a per-target message ID as part of sysdata */ SYSDATA_MSGID = BIT(3), /* Sentinel: highest bit position */ MAX_SYSDATA_ITEMS = 4,}; enum target_state { STATE_DISABLED, STATE_ENABLED, STATE_DEACTIVATED,}; /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. * @userdata_group: Links to the userdata configfs hierarchy * @userdata: Cached, formatted string of append * @userdata_length: String length of userdata. * @sysdata: Cached, formatted string of append * @sysdata_fields: Sysdata features enabled. * @msgcounter: Message sent counter. * @stats: Packet send stats for the target. Used for debugging. * @state: State of the target. * Visible from userspace (read-write). * From a userspace perspective, the target is either enabled or * 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_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. * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) * local_port (read-write) * remote_port (read-write) * local_ip (read-write) * remote_ip (read-write) * local_mac (read-only) * remote_mac (read-write) * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target */struct netconsole_target { struct list_head list;#ifdef CONFIG_NETCONSOLE_DYNAMIC struct config_group group; struct config_group userdata_group; char *userdata; size_t userdata_length; char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; /* bit-wise with sysdata_feature bits */ u32 sysdata_fields; /* protected by target_list_lock */ u32 msgcounter;#endif struct netconsole_target_stats stats; enum target_state state; bool extended; bool release; struct netpoll np; /* protected by target_list_lock; +1 gives scnprintf() room for its * NUL terminator so a full MAX_PRINT_CHUNK payload is not truncated */ char buf[MAX_PRINT_CHUNK + 1]; struct work_struct resume_wq;}; #ifdef CONFIG_NETCONSOLE_DYNAMIC static struct configfs_subsystem netconsole_subsys;static DEFINE_MUTEX(dynamic_netconsole_mutex); static int __init dynamic_netconsole_init(void){ config_group_init(&netconsole_subsys.su_group); mutex_init(&netconsole_subsys.su_mutex); return configfs_register_subsystem(&netconsole_subsys);} static void __exit dynamic_netconsole_exit(void){ configfs_unregister_subsystem(&netconsole_subsys);} /* * Targets that were created by parsing the boot/module option string * do not exist in the configfs hierarchy (and have NULL names) and will * never go away, so make these a no-op for them. */static void netconsole_target_get(struct netconsole_target *nt){ if (config_item_name(&nt->group.cg_item)) config_group_get(&nt->group);} static void netconsole_target_put(struct netconsole_target *nt){ if (config_item_name(&nt->group.cg_item)) config_group_put(&nt->group);} static void dynamic_netconsole_mutex_lock(void){ mutex_lock(&dynamic_netconsole_mutex);} static void dynamic_netconsole_mutex_unlock(void){ mutex_unlock(&dynamic_netconsole_mutex);} #else /* !CONFIG_NETCONSOLE_DYNAMIC */ static int __init dynamic_netconsole_init(void){ return 0;} static void __exit dynamic_netconsole_exit(void){} /* * No danger of targets going away from under us when dynamic * reconfigurability is off. */static void netconsole_target_get(struct netconsole_target *nt){} static void netconsole_target_put(struct netconsole_target *nt){} static void populate_configfs_item(struct netconsole_target *nt, int cmdline_count){} static void dynamic_netconsole_mutex_lock(void){} static void dynamic_netconsole_mutex_unlock(void){} #endif /* CONFIG_NETCONSOLE_DYNAMIC */ /* 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);} static void netcons_release_dev(struct netconsole_target *nt){ do_netpoll_cleanup(&nt->np); if (bound_by_mac(nt)) memset(&nt->np.dev_name, 0, IFNAMSIZ);} /* Attempts to resume logging to a deactivated target. */static void resume_target(struct netconsole_target *nt){ if (netpoll_setup(&nt->np)) { /* netpoll fails setup once, do not try again. */ nt->state = STATE_DISABLED; return; } nt->state = STATE_ENABLED; pr_info("network logging resumed on interface %s\n", nt->np.dev_name);} /* Checks if a deactivated target matches a device. */static bool deactivated_target_match(struct netconsole_target *nt, struct net_device *ndev){ if (nt->state != STATE_DEACTIVATED) return false; 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);} /* Process work scheduled for target resume. */static void process_resume_target(struct work_struct *work){ struct netconsole_target *nt; unsigned long flags; nt = container_of(work, struct netconsole_target, resume_wq); dynamic_netconsole_mutex_lock(); spin_lock_irqsave(&target_list_lock, flags); /* Check if target is still deactivated as it may have been disabled * while resume was being scheduled. */ if (nt->state != STATE_DEACTIVATED) { spin_unlock_irqrestore(&target_list_lock, flags); goto out_unlock; } /* resume_target is IRQ unsafe, remove target from * target_list in order to resume it with IRQ enabled. */ list_del_init(&nt->list); spin_unlock_irqrestore(&target_list_lock, flags); resume_target(nt); /* netpoll_setup() took a net_device reference and dropped the RTNL * before returning, all while this target was off target_list and * thus invisible to netconsole_netdev_event(). If the device was * unregistered in that window the NETDEV_UNREGISTER notifier could not * tear this target down, which would leak the reference and hang * unregister_netdevice(). Re-check under the RTNL before re-publishing: * taking it across the check and the list_add() serialises against the * notifier (which also runs under the RTNL), so the device is either * still registered (the notifier will find the re-added target) or * already unregistering (we drop the reference here). */ rtnl_lock(); if (nt->state == STATE_ENABLED && nt->np.dev && nt->np.dev->reg_state != NETREG_REGISTERED) { netcons_release_dev(nt); nt->state = STATE_DISABLED; } /* 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_add(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); rtnl_unlock(); out_unlock: dynamic_netconsole_mutex_unlock();} /* Allocate and initialize with defaults. * Note that these targets get their config_item fields zeroed-out. */static struct netconsole_target *alloc_and_init(void){ struct netconsole_target *nt; nt = kzalloc_obj(*nt); if (!nt) return nt; if (IS_ENABLED(CONFIG_NETCONSOLE_EXTENDED_LOG)) nt->extended = true; if (IS_ENABLED(CONFIG_NETCONSOLE_PREPEND_RELEASE)) nt->release = true; nt->np.name = "netconsole"; strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->np.local_port = 6665; nt->np.remote_port = 6666; eth_broadcast_addr(nt->np.remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); return nt;} /* Clean up every target in the cleanup_list and move the clean targets back to * the main target_list. */static void netconsole_process_cleanups_core(void){ struct netconsole_target *nt, *tmp; unsigned long flags; /* The cleanup needs RTNL locked */ ASSERT_RTNL(); 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->state == STATE_ENABLED); netcons_release_dev(nt); /* moved the cleaned target to target_list. Need to hold both * locks */ spin_lock_irqsave(&target_list_lock, flags); list_move(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); } WARN_ON_ONCE(!list_empty(&target_cleanup_list)); mutex_unlock(&target_cleanup_list_lock);} static void netconsole_print_banner(struct netpoll *np){ np_info(np, "local port %d\n", np->local_port); if (np->ipv6) np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); else np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); np_info(np, "interface name '%s'\n", np->dev_name); np_info(np, "local ethernet address '%pM'\n", np->dev_mac); np_info(np, "remote port %d\n", np->remote_port); if (np->ipv6) np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); else np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); np_info(np, "remote ethernet address %pM\n", np->remote_mac);} /* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is * populated, 1 if IPv6 is populated, and -1 upon failure. */static int netpoll_parse_ip_addr(const char *str, union inet_addr *addr){ const char *end = NULL; int len; len = strlen(str); if (!len) return -1; if (str[len - 1] == '\n') len -= 1; if (in4_pton(str, len, (void *)addr, -1, &end) > 0 && (!end || *end == 0 || *end == '\n')) return 0; if (IS_ENABLED(CONFIG_IPV6) && in6_pton(str, len, (void *)addr, -1, &end) > 0 && (!end || *end == 0 || *end == '\n')) return 1; return -1;} #ifdef CONFIG_NETCONSOLE_DYNAMIC /* * Our subsystem hierarchy is: * * /sys/kernel/config/netconsole/ * | * <target>/ * | enabled * | release * | dev_name * | local_port * | remote_port * | local_ip * | remote_ip * | local_mac * | remote_mac * | transmit_errors * | userdata/ * | <key>/ * | value * | ... * | * <target>/... */Lines 1–500 of 2499. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * linux/drivers/net/netconsole.c * * Copyright (C) 2001 Ingo Molnar <mingo@redhat.com> * * This file contains the implementation of an IRQ-safe, crash-safe * kernel console implementation that outputs kernel messages to the * network. * * Modification history: * * 2001-09-17 started by Ingo Molnar. * 2003-08-11 2.6 port by Matt Mackall * simplified options * generic card hooks * works non-modular * 2003-09-07 rewritten with netpoll api */ /**************************************************************** * ****************************************************************/ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/mm.h> #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/console.h> #include <linux/moduleparam.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/udp.h> #include <linux/netpoll.h> #include <linux/inet.h> #include <linux/unaligned.h> #include <net/ip6_checksum.h> #include <linux/configfs.h> #include <linux/etherdevice.h> #include <linux/hex.h> #include <linux/u64_stats_sync.h> #include <linux/utsname.h> #include <linux/rtnetlink.h> #include <linux/workqueue.h> MODULE_AUTHOR("Matt Mackall <mpm@selenic.com>"); MODULE_DESCRIPTION("Console driver for network interfaces"); MODULE_LICENSE("GPL"); MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_PARAM_LENGTH 256 #define MAX_EXTRADATA_ENTRY_LEN 256 #define MAX_EXTRADATA_VALUE_LEN 200 /* The number 3 comes from userdata entry format characters (' ', '=', '\n') */ #define MAX_EXTRADATA_NAME_LEN (MAX_EXTRADATA_ENTRY_LEN - \ MAX_EXTRADATA_VALUE_LEN - 3) #define MAX_USERDATA_ITEMS 256 #define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]"); static bool oops_only; module_param(oops_only, bool, 0600); MODULE_PARM_DESC(oops_only, "Only log oops messages"); #define NETCONSOLE_PARAM_TARGET_PREFIX "cmdline" #ifndef MODULE static int __init option_setup(char *opt) { strscpy(config, opt, MAX_PARAM_LENGTH); return 1; } __setup("netconsole=", option_setup); #endif /* MODULE */ /* Linked list of all configured targets */ static LIST_HEAD(target_list); /* target_cleanup_list is used to track targets that need to be cleaned outside * of target_list_lock. It should be cleaned in the same function it is * populated. */ static LIST_HEAD(target_cleanup_list); /* This needs to be a spinlock because write_msg() cannot sleep */ static DEFINE_SPINLOCK(target_list_lock); /* This needs to be a mutex because netpoll_cleanup might sleep */ static DEFINE_MUTEX(target_cleanup_list_lock); static struct workqueue_struct *netconsole_wq; /* * Console driver for netconsoles. Register only consoles that have * an associated target of the same type. */ static struct console netconsole_ext, netconsole; struct netconsole_target_stats { u64_stats_t xmit_drop_count; u64_stats_t enomem_count; struct u64_stats_sync syncp; }; enum console_type { CONS_BASIC = BIT(0), CONS_EXTENDED = BIT(1), }; /* Features enabled in sysdata. Contrary to userdata, this data is populated by * the kernel. The fields are designed as bitwise flags, allowing multiple * features to be set in sysdata_fields. */ enum sysdata_feature { /* Populate the CPU that sends the message */ SYSDATA_CPU_NR = BIT(0), /* Populate the task name (as in current->comm) in sysdata */ SYSDATA_TASKNAME = BIT(1), /* Kernel release/version as part of sysdata */ SYSDATA_RELEASE = BIT(2), /* Include a per-target message ID as part of sysdata */ SYSDATA_MSGID = BIT(3), /* Sentinel: highest bit position */ MAX_SYSDATA_ITEMS = 4, }; enum target_state { STATE_DISABLED, STATE_ENABLED, STATE_DEACTIVATED, }; /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. * @userdata_group: Links to the userdata configfs hierarchy * @userdata: Cached, formatted string of append * @userdata_length: String length of userdata. * @sysdata: Cached, formatted string of append * @sysdata_fields: Sysdata features enabled. * @msgcounter: Message sent counter. * @stats: Packet send stats for the target. Used for debugging. * @state: State of the target. * Visible from userspace (read-write). * From a userspace perspective, the target is either enabled or * 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_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. * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) * local_port (read-write) * remote_port (read-write) * local_ip (read-write) * remote_ip (read-write) * local_mac (read-only) * remote_mac (read-write) * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target */ struct netconsole_target { struct list_head list; #ifdef CONFIG_NETCONSOLE_DYNAMIC struct config_group group; struct config_group userdata_group; char *userdata; size_t userdata_length; char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; /* bit-wise with sysdata_feature bits */ u32 sysdata_fields; /* protected by target_list_lock */ u32 msgcounter; #endif struct netconsole_target_stats stats; enum target_state state; bool extended; bool release; struct netpoll np; /* protected by target_list_lock; +1 gives scnprintf() room for its * NUL terminator so a full MAX_PRINT_CHUNK payload is not truncated */ char buf[MAX_PRINT_CHUNK + 1]; struct work_struct resume_wq; }; #ifdef CONFIG_NETCONSOLE_DYNAMIC static struct configfs_subsystem netconsole_subsys; static DEFINE_MUTEX(dynamic_netconsole_mutex); static int __init dynamic_netconsole_init(void) { config_group_init(&netconsole_subsys.su_group); mutex_init(&netconsole_subsys.su_mutex); return configfs_register_subsystem(&netconsole_subsys); } static void __exit dynamic_netconsole_exit(void) { configfs_unregister_subsystem(&netconsole_subsys); } /* * Targets that were created by parsing the boot/module option string * do not exist in the configfs hierarchy (and have NULL names) and will * never go away, so make these a no-op for them. */ static void netconsole_target_get(struct netconsole_target *nt) { if (config_item_name(&nt->group.cg_item)) config_group_get(&nt->group); } static void netconsole_target_put(struct netconsole_target *nt) { if (config_item_name(&nt->group.cg_item)) config_group_put(&nt->group); } static void dynamic_netconsole_mutex_lock(void) { mutex_lock(&dynamic_netconsole_mutex); } static void dynamic_netconsole_mutex_unlock(void) { mutex_unlock(&dynamic_netconsole_mutex); } #else /* !CONFIG_NETCONSOLE_DYNAMIC */ static int __init dynamic_netconsole_init(void) { return 0; } static void __exit dynamic_netconsole_exit(void) { } /* * No danger of targets going away from under us when dynamic * reconfigurability is off. */ static void netconsole_target_get(struct netconsole_target *nt) { } static void netconsole_target_put(struct netconsole_target *nt) { } static void populate_configfs_item(struct netconsole_target *nt, int cmdline_count) { } static void dynamic_netconsole_mutex_lock(void) { } static void dynamic_netconsole_mutex_unlock(void) { } #endif /* CONFIG_NETCONSOLE_DYNAMIC */ /* 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); } static void netcons_release_dev(struct netconsole_target *nt) { do_netpoll_cleanup(&nt->np); if (bound_by_mac(nt)) memset(&nt->np.dev_name, 0, IFNAMSIZ); } /* Attempts to resume logging to a deactivated target. */ static void resume_target(struct netconsole_target *nt) { if (netpoll_setup(&nt->np)) { /* netpoll fails setup once, do not try again. */ nt->state = STATE_DISABLED; return; } nt->state = STATE_ENABLED; pr_info("network logging resumed on interface %s\n", nt->np.dev_name); } /* Checks if a deactivated target matches a device. */ static bool deactivated_target_match(struct netconsole_target *nt, struct net_device *ndev) { if (nt->state != STATE_DEACTIVATED) return false; 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); } /* Process work scheduled for target resume. */ static void process_resume_target(struct work_struct *work) { struct netconsole_target *nt; unsigned long flags; nt = container_of(work, struct netconsole_target, resume_wq); dynamic_netconsole_mutex_lock(); spin_lock_irqsave(&target_list_lock, flags); /* Check if target is still deactivated as it may have been disabled * while resume was being scheduled. */ if (nt->state != STATE_DEACTIVATED) { spin_unlock_irqrestore(&target_list_lock, flags); goto out_unlock; } /* resume_target is IRQ unsafe, remove target from * target_list in order to resume it with IRQ enabled. */ list_del_init(&nt->list); spin_unlock_irqrestore(&target_list_lock, flags); resume_target(nt); /* netpoll_setup() took a net_device reference and dropped the RTNL * before returning, all while this target was off target_list and * thus invisible to netconsole_netdev_event(). If the device was * unregistered in that window the NETDEV_UNREGISTER notifier could not * tear this target down, which would leak the reference and hang * unregister_netdevice(). Re-check under the RTNL before re-publishing: * taking it across the check and the list_add() serialises against the * notifier (which also runs under the RTNL), so the device is either * still registered (the notifier will find the re-added target) or * already unregistering (we drop the reference here). */ rtnl_lock(); if (nt->state == STATE_ENABLED && nt->np.dev && nt->np.dev->reg_state != NETREG_REGISTERED) { netcons_release_dev(nt); nt->state = STATE_DISABLED; } /* 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_add(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); rtnl_unlock(); out_unlock: dynamic_netconsole_mutex_unlock(); } /* Allocate and initialize with defaults. * Note that these targets get their config_item fields zeroed-out. */ static struct netconsole_target *alloc_and_init(void) { struct netconsole_target *nt; nt = kzalloc_obj(*nt); if (!nt) return nt; if (IS_ENABLED(CONFIG_NETCONSOLE_EXTENDED_LOG)) nt->extended = true; if (IS_ENABLED(CONFIG_NETCONSOLE_PREPEND_RELEASE)) nt->release = true; nt->np.name = "netconsole"; strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->np.local_port = 6665; nt->np.remote_port = 6666; eth_broadcast_addr(nt->np.remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); return nt; } /* Clean up every target in the cleanup_list and move the clean targets back to * the main target_list. */ static void netconsole_process_cleanups_core(void) { struct netconsole_target *nt, *tmp; unsigned long flags; /* The cleanup needs RTNL locked */ ASSERT_RTNL(); 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->state == STATE_ENABLED); netcons_release_dev(nt); /* moved the cleaned target to target_list. Need to hold both * locks */ spin_lock_irqsave(&target_list_lock, flags); list_move(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); } WARN_ON_ONCE(!list_empty(&target_cleanup_list)); mutex_unlock(&target_cleanup_list_lock); } static void netconsole_print_banner(struct netpoll *np) { np_info(np, "local port %d\n", np->local_port); if (np->ipv6) np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); else np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); np_info(np, "interface name '%s'\n", np->dev_name); np_info(np, "local ethernet address '%pM'\n", np->dev_mac); np_info(np, "remote port %d\n", np->remote_port); if (np->ipv6) np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); else np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); np_info(np, "remote ethernet address %pM\n", np->remote_mac); } /* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is * populated, 1 if IPv6 is populated, and -1 upon failure. */ static int netpoll_parse_ip_addr(const char *str, union inet_addr *addr) { const char *end = NULL; int len; len = strlen(str); if (!len) return -1; if (str[len - 1] == '\n') len -= 1; if (in4_pton(str, len, (void *)addr, -1, &end) > 0 && (!end || *end == 0 || *end == '\n')) return 0; if (IS_ENABLED(CONFIG_IPV6) && in6_pton(str, len, (void *)addr, -1, &end) > 0 && (!end || *end == 0 || *end == '\n')) return 1; return -1; } #ifdef CONFIG_NETCONSOLE_DYNAMIC /* * Our subsystem hierarchy is: * * /sys/kernel/config/netconsole/ * | * <target>/ * | enabled * | release * | dev_name * | local_port * | remote_port * | local_ip * | remote_ip * | local_mac * | remote_mac * | transmit_errors * | userdata/ * | <key>/ * | value * | ... * | * <target>/... */