diff options
Diffstat (limited to 'drivers/net/netconsole.c')
| -rw-r--r-- | drivers/net/netconsole.c | 271 |
1 files changed, 158 insertions, 113 deletions
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 501e451be91..ba2f5e710af 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -34,11 +34,15 @@ * ****************************************************************/ +#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/netpoll.h> #include <linux/inet.h> @@ -53,7 +57,11 @@ MODULE_LICENSE("GPL"); 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]\n"); +MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]"); + +static bool oops_only = false; +module_param(oops_only, bool, 0600); +MODULE_PARM_DESC(oops_only, "Only log oops messages"); #ifndef MODULE static int __init option_setup(char *opt) @@ -96,6 +104,7 @@ struct netconsole_target { struct config_item item; #endif int enabled; + struct mutex mutex; struct netpoll np; }; @@ -168,15 +177,14 @@ static struct netconsole_target *alloc_param_target(char *target_config) * Note that these targets get their config_item fields zeroed-out. */ nt = kzalloc(sizeof(*nt), GFP_KERNEL); - if (!nt) { - printk(KERN_ERR "netconsole: failed to allocate memory\n"); + if (!nt) goto fail; - } nt->np.name = "netconsole"; strlcpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->np.local_port = 6665; nt->np.remote_port = 6666; + mutex_init(&nt->mutex); memset(nt->np.remote_mac, 0xff, ETH_ALEN); /* Parse parameters and setup netpoll */ @@ -241,34 +249,6 @@ static struct netconsole_target *to_target(struct config_item *item) } /* - * Wrapper over simple_strtol (base 10) with sanity and range checking. - * We return (signed) long only because we may want to return errors. - * Do not use this to convert numbers that are allowed to be negative. - */ -static long strtol10_check_range(const char *cp, long min, long max) -{ - long ret; - char *p = (char *) cp; - - WARN_ON(min < 0); - WARN_ON(max < min); - - ret = simple_strtol(p, &p, 10); - - if (*p && (*p != '\n')) { - printk(KERN_ERR "netconsole: invalid input\n"); - return -EINVAL; - } - if ((ret < min) || (ret > max)) { - printk(KERN_ERR "netconsole: input %ld must be between " - "%ld and %ld\n", ret, min, max); - return -EINVAL; - } - - return ret; -} - -/* * Attribute operations for netconsole_target. */ @@ -294,30 +274,31 @@ static ssize_t show_remote_port(struct netconsole_target *nt, char *buf) static ssize_t show_local_ip(struct netconsole_target *nt, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", - HIPQUAD(nt->np.local_ip)); + if (nt->np.ipv6) + return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.local_ip.in6); + else + return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.local_ip); } static ssize_t show_remote_ip(struct netconsole_target *nt, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d.%d.%d.%d\n", - HIPQUAD(nt->np.remote_ip)); + if (nt->np.ipv6) + return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.remote_ip.in6); + else + return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.remote_ip); } static ssize_t show_local_mac(struct netconsole_target *nt, char *buf) { struct net_device *dev = nt->np.dev; + static const u8 bcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - DECLARE_MAC_BUF(mac); - return snprintf(buf, PAGE_SIZE, "%s\n", dev ? - print_mac(mac, dev->dev_addr) : "ff:ff:ff:ff:ff:ff"); + return snprintf(buf, PAGE_SIZE, "%pM\n", dev ? dev->dev_addr : bcast); } static ssize_t show_remote_mac(struct netconsole_target *nt, char *buf) { - DECLARE_MAC_BUF(mac); - return snprintf(buf, PAGE_SIZE, "%s\n", - print_mac(mac, nt->np.remote_mac)); + return snprintf(buf, PAGE_SIZE, "%pM\n", nt->np.remote_mac); } /* @@ -331,15 +312,22 @@ static ssize_t store_enabled(struct netconsole_target *nt, const char *buf, size_t count) { + unsigned long flags; + int enabled; int err; - long enabled; - enabled = strtol10_check_range(buf, 0, 1); - if (enabled < 0) - return enabled; + err = kstrtoint(buf, 10, &enabled); + if (err < 0) + return err; + if (enabled < 0 || enabled > 1) + return -EINVAL; + if (enabled == nt->enabled) { + pr_info("network logging has already %s\n", + nt->enabled ? "started" : "stopped"); + return -EINVAL; + } if (enabled) { /* 1 */ - /* * Skip netpoll_parse_options() -- all the attributes are * already configured via configfs. Just print them out. @@ -350,9 +338,15 @@ static ssize_t store_enabled(struct netconsole_target *nt, if (err) return err; - printk(KERN_INFO "netconsole: network logging started\n"); - + pr_info("netconsole: network logging started\n"); } else { /* 0 */ + /* 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 == 1 + */ + spin_lock_irqsave(&target_list_lock, flags); + nt->enabled = 0; + spin_unlock_irqrestore(&target_list_lock, flags); netpoll_cleanup(&nt->np); } @@ -368,9 +362,8 @@ static ssize_t store_dev_name(struct netconsole_target *nt, size_t len; if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } @@ -388,22 +381,17 @@ static ssize_t store_local_port(struct netconsole_target *nt, const char *buf, size_t count) { - long local_port; -#define __U16_MAX ((__u16) ~0U) + int rv; if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } - local_port = strtol10_check_range(buf, 0, __U16_MAX); - if (local_port < 0) - return local_port; - - nt->np.local_port = local_port; - + rv = kstrtou16(buf, 10, &nt->np.local_port); + if (rv < 0) + return rv; return strnlen(buf, count); } @@ -411,22 +399,17 @@ static ssize_t store_remote_port(struct netconsole_target *nt, const char *buf, size_t count) { - long remote_port; -#define __U16_MAX ((__u16) ~0U) + int rv; if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } - remote_port = strtol10_check_range(buf, 0, __U16_MAX); - if (remote_port < 0) - return remote_port; - - nt->np.remote_port = remote_port; - + rv = kstrtou16(buf, 10, &nt->np.remote_port); + if (rv < 0) + return rv; return strnlen(buf, count); } @@ -435,13 +418,27 @@ static ssize_t store_local_ip(struct netconsole_target *nt, size_t count) { if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } - nt->np.local_ip = ntohl(in_aton(buf)); + if (strnchr(buf, count, ':')) { + const char *end; + if (in6_pton(buf, count, nt->np.local_ip.in6.s6_addr, -1, &end) > 0) { + if (*end && *end != '\n') { + pr_err("invalid IPv6 address at: <%c>\n", *end); + return -EINVAL; + } + nt->np.ipv6 = true; + } else + return -EINVAL; + } else { + if (!nt->np.ipv6) { + nt->np.local_ip.ip = in_aton(buf); + } else + return -EINVAL; + } return strnlen(buf, count); } @@ -451,13 +448,27 @@ static ssize_t store_remote_ip(struct netconsole_target *nt, size_t count) { if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } - nt->np.remote_ip = ntohl(in_aton(buf)); + if (strnchr(buf, count, ':')) { + const char *end; + if (in6_pton(buf, count, nt->np.remote_ip.in6.s6_addr, -1, &end) > 0) { + if (*end && *end != '\n') { + pr_err("invalid IPv6 address at: <%c>\n", *end); + return -EINVAL; + } + nt->np.ipv6 = true; + } else + return -EINVAL; + } else { + if (!nt->np.ipv6) { + nt->np.remote_ip.ip = in_aton(buf); + } else + return -EINVAL; + } return strnlen(buf, count); } @@ -467,33 +478,20 @@ static ssize_t store_remote_mac(struct netconsole_target *nt, size_t count) { u8 remote_mac[ETH_ALEN]; - char *p = (char *) buf; - int i; if (nt->enabled) { - printk(KERN_ERR "netconsole: target (%s) is enabled, " - "disable to update parameters\n", - config_item_name(&nt->item)); + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->item)); return -EINVAL; } - for (i = 0; i < ETH_ALEN - 1; i++) { - remote_mac[i] = simple_strtoul(p, &p, 16); - if (*p != ':') - goto invalid; - p++; - } - remote_mac[ETH_ALEN - 1] = simple_strtoul(p, &p, 16); - if (*p && (*p != '\n')) - goto invalid; - + if (!mac_pton(buf, remote_mac)) + return -EINVAL; + if (buf[3 * ETH_ALEN - 1] && buf[3 * ETH_ALEN - 1] != '\n') + return -EINVAL; memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); return strnlen(buf, count); - -invalid: - printk(KERN_ERR "netconsole: invalid input\n"); - return -EINVAL; } /* @@ -563,8 +561,10 @@ static ssize_t netconsole_target_attr_store(struct config_item *item, struct netconsole_target_attr *na = container_of(attr, struct netconsole_target_attr, attr); + mutex_lock(&nt->mutex); if (na->store) ret = na->store(nt, buf, count); + mutex_unlock(&nt->mutex); return ret; } @@ -596,15 +596,14 @@ static struct config_item *make_netconsole_target(struct config_group *group, * Target is disabled at creation (enabled == 0). */ nt = kzalloc(sizeof(*nt), GFP_KERNEL); - if (!nt) { - printk(KERN_ERR "netconsole: failed to allocate memory\n"); - return NULL; - } + if (!nt) + return ERR_PTR(-ENOMEM); nt->np.name = "netconsole"; strlcpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->np.local_port = 6665; nt->np.remote_port = 6666; + mutex_init(&nt->mutex); memset(nt->np.remote_mac, 0xff, ETH_ALEN); /* Initialize the config_item member */ @@ -662,17 +661,19 @@ static struct configfs_subsystem netconsole_subsys = { /* Handle network interface device notifications */ static int netconsole_netdev_event(struct notifier_block *this, - unsigned long event, - void *ptr) + unsigned long event, void *ptr) { unsigned long flags; struct netconsole_target *nt; - struct net_device *dev = ptr; + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + bool stopped = false; - if (!(event == NETDEV_CHANGENAME)) + if (!(event == NETDEV_CHANGENAME || event == NETDEV_UNREGISTER || + event == NETDEV_RELEASE || event == NETDEV_JOIN)) goto done; spin_lock_irqsave(&target_list_lock, flags); +restart: list_for_each_entry(nt, &target_list, list) { netconsole_target_get(nt); if (nt->np.dev == dev) { @@ -680,11 +681,44 @@ static int netconsole_netdev_event(struct notifier_block *this, case NETDEV_CHANGENAME: strlcpy(nt->np.dev_name, dev->name, IFNAMSIZ); break; + case NETDEV_RELEASE: + case NETDEV_JOIN: + case NETDEV_UNREGISTER: + /* rtnl_lock already held + * we might sleep in __netpoll_cleanup() + */ + spin_unlock_irqrestore(&target_list_lock, flags); + + __netpoll_cleanup(&nt->np); + + spin_lock_irqsave(&target_list_lock, flags); + dev_put(nt->np.dev); + nt->np.dev = NULL; + nt->enabled = 0; + stopped = true; + netconsole_target_put(nt); + goto restart; } } netconsole_target_put(nt); } spin_unlock_irqrestore(&target_list_lock, flags); + if (stopped) { + const char *msg = "had an event"; + switch (event) { + case NETDEV_UNREGISTER: + msg = "unregistered"; + break; + case NETDEV_RELEASE: + msg = "released slaves"; + break; + case NETDEV_JOIN: + msg = "is joining a master device"; + break; + } + pr_info("network logging stopped on interface %s as it %s\n", + dev->name, msg); + } done: return NOTIFY_DONE; @@ -701,6 +735,8 @@ static void write_msg(struct console *con, const char *msg, unsigned int len) struct netconsole_target *nt; const char *tmp; + if (oops_only && !oops_in_progress) + return; /* Avoid taking lock and disabling interrupts unnecessarily */ if (list_empty(&target_list)) return; @@ -730,7 +766,7 @@ static void write_msg(struct console *con, const char *msg, unsigned int len) static struct console netconsole = { .name = "netcon", - .flags = CON_ENABLED | CON_PRINTBUFFER, + .flags = CON_ENABLED, .write = write_msg, }; @@ -749,6 +785,9 @@ static int __init init_netconsole(void) err = PTR_ERR(nt); goto fail; } + /* Dump existing printks when we register */ + netconsole.flags |= CON_PRINTBUFFER; + spin_lock_irqsave(&target_list_lock, flags); list_add(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); @@ -764,7 +803,7 @@ static int __init init_netconsole(void) goto undonotifier; register_console(&netconsole); - printk(KERN_INFO "netconsole: network logging started\n"); + pr_info("network logging started\n"); return err; @@ -772,7 +811,7 @@ undonotifier: unregister_netdevice_notifier(&netconsole_netdev_notifier); fail: - printk(KERN_ERR "netconsole: cleaning up\n"); + pr_err("cleaning up\n"); /* * Remove all targets and destroy them (only targets created @@ -809,5 +848,11 @@ static void __exit cleanup_netconsole(void) } } -module_init(init_netconsole); +/* + * Use late_initcall to ensure netconsole is + * initialized after network device driver if built-in. + * + * late_initcall() and module_init() are identical if built as module. + */ +late_initcall(init_netconsole); module_exit(cleanup_netconsole); |
