diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index c1812a98365b..49b2243a20b2 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -61,6 +61,19 @@ MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_USERDATA_ITEMS 256 #define MAX_PRINT_CHUNK 1000 +/* + * Sizing for the per-target fallback skb pool consulted by find_skb() + * when its GFP_ATOMIC allocation fails so messages still get out under + * memory pressure. + */ +#define MAX_UDP_CHUNK 1460 +#define MAX_SKBS 32 +#define MAX_SKB_SIZE \ + (sizeof(struct ethhdr) + \ + sizeof(struct iphdr) + \ + sizeof(struct udphdr) + \ + MAX_UDP_CHUNK) + 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-macaddr]"); @@ -162,14 +175,19 @@ enum target_state { * @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) + * @local_port: Source UDP port of the target (read-write). + * @remote_port: Destination UDP port of the target (read-write). + * @remote_mac: Destination ethernet address of the target (read-write). * @buf: The buffer used to send the full msg to the network stack * @resume_wq: Workqueue to resume deactivated target + * @skb_pool: Per-target fallback skb pool consulted by find_skb() when + * its GFP_ATOMIC allocation fails. Lifetime brackets a + * successful netpoll_setup() / netpoll_cleanup() pair on @np. + * @refill_wq: Work item that asynchronously tops @skb_pool back up to + * MAX_SKBS after find_skb() drains an entry. */ struct netconsole_target { struct list_head list; @@ -190,11 +208,15 @@ struct netconsole_target { bool extended; bool release; struct netpoll np; + u16 local_port, remote_port; + u8 remote_mac[ETH_ALEN]; /* 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; + struct sk_buff_head skb_pool; + struct work_struct refill_wq; }; #ifdef CONFIG_NETCONSOLE_DYNAMIC @@ -292,11 +314,56 @@ static void netcons_release_dev(struct netconsole_target *nt) memset(&nt->np.dev_name, 0, IFNAMSIZ); } +static void refill_skbs(struct netconsole_target *nt) +{ + struct sk_buff_head *skb_pool = &nt->skb_pool; + struct sk_buff *skb; + + while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { + skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); + if (!skb) + break; + + skb_queue_tail(skb_pool, skb); + } +} + +static void refill_skbs_work_handler(struct work_struct *work) +{ + struct netconsole_target *nt = + container_of(work, struct netconsole_target, refill_wq); + + refill_skbs(nt); +} + +/* Seed the per-target skb pool that find_skb() falls back to. The queue + * head and refill work are set up once in alloc_and_init(); this only + * (re)fills the pool. Pair with netconsole_skb_pool_flush(). + */ +static void netconsole_skb_pool_init(struct netconsole_target *nt) +{ + refill_skbs(nt); +} + +static void netconsole_skb_pool_flush(struct netconsole_target *nt) +{ + cancel_work_sync(&nt->refill_wq); + skb_queue_purge_reason(&nt->skb_pool, SKB_CONSUMED); +} + /* Attempts to resume logging to a deactivated target. */ static void resume_target(struct netconsole_target *nt) { + /* Initialise the skb pool before netpoll_setup() makes nt->np.dev + * visible to target_list walkers (e.g. netconsole_netdev_event), + * which otherwise may move the target to the cleanup list and + * call netconsole_skb_pool_flush() on uninitialised state. + */ + netconsole_skb_pool_init(nt); + if (netpoll_setup(&nt->np)) { /* netpoll fails setup once, do not try again. */ + netconsole_skb_pool_flush(nt); nt->state = STATE_DISABLED; return; } @@ -358,6 +425,7 @@ static void process_resume_target(struct work_struct *work) rtnl_lock(); if (nt->state == STATE_ENABLED && nt->np.dev && nt->np.dev->reg_state != NETREG_REGISTERED) { + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); nt->state = STATE_DISABLED; } @@ -393,11 +461,14 @@ static struct netconsole_target *alloc_and_init(void) 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->local_port = 6665; + nt->remote_port = 6666; + eth_broadcast_addr(nt->remote_mac); nt->state = STATE_DISABLED; INIT_WORK(&nt->resume_wq, process_resume_target); + /* Set up the skb pool primitives once; enabling only refills it. */ + skb_queue_head_init(&nt->skb_pool); + INIT_WORK(&nt->refill_wq, refill_skbs_work_handler); return nt; } @@ -417,6 +488,7 @@ static void netconsole_process_cleanups_core(void) 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); + netconsole_skb_pool_flush(nt); netcons_release_dev(nt); /* moved the cleaned target to target_list. Need to hold both * locks @@ -429,21 +501,23 @@ static void netconsole_process_cleanups_core(void) mutex_unlock(&target_cleanup_list_lock); } -static void netconsole_print_banner(struct netpoll *np) +static void netconsole_print_banner(struct netconsole_target *nt) { - np_info(np, "local port %d\n", np->local_port); + struct netpoll *np = &nt->np; + + np_info(np, "local port %d\n", nt->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); + np_info(np, "remote port %d\n", nt->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); + np_info(np, "remote ethernet address %pM\n", nt->remote_mac); } /* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is @@ -561,12 +635,12 @@ static ssize_t dev_name_show(struct config_item *item, char *buf) static ssize_t local_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.local_port); + return sysfs_emit(buf, "%d\n", to_target(item)->local_port); } static ssize_t remote_port_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%d\n", to_target(item)->np.remote_port); + return sysfs_emit(buf, "%d\n", to_target(item)->remote_port); } static ssize_t local_ip_show(struct config_item *item, char *buf) @@ -599,7 +673,7 @@ static ssize_t local_mac_show(struct config_item *item, char *buf) static ssize_t remote_mac_show(struct config_item *item, char *buf) { - return sysfs_emit(buf, "%pM\n", to_target(item)->np.remote_mac); + return sysfs_emit(buf, "%pM\n", to_target(item)->remote_mac); } static ssize_t transmit_errors_show(struct config_item *item, char *buf) @@ -756,11 +830,21 @@ static ssize_t enabled_store(struct config_item *item, * Skip netconsole_parser_cmdline() -- all the attributes are * already configured via configfs. Just print them out. */ - netconsole_print_banner(&nt->np); + netconsole_print_banner(nt); + + /* Initialise the skb pool before netpoll_setup() so the pool + * is valid as soon as nt->np.dev becomes visible to + * target_list walkers (netconsole_netdev_event), which would + * otherwise call netconsole_skb_pool_flush() on uninitialised + * state. + */ + netconsole_skb_pool_init(nt); ret = netpoll_setup(&nt->np); - if (ret) + if (ret) { + netconsole_skb_pool_flush(nt); goto out_unlock; + } nt->state = STATE_ENABLED; pr_info("network logging started\n"); @@ -885,7 +969,7 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.local_port); + ret = kstrtou16(buf, 10, &nt->local_port); if (ret < 0) goto out_unlock; ret = count; @@ -907,7 +991,7 @@ static ssize_t remote_port_store(struct config_item *item, goto out_unlock; } - ret = kstrtou16(buf, 10, &nt->np.remote_port); + ret = kstrtou16(buf, 10, &nt->remote_port); if (ret < 0) goto out_unlock; ret = count; @@ -994,7 +1078,7 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, goto out_unlock; if (buf[MAC_ADDR_STR_LEN] && buf[MAC_ADDR_STR_LEN] != '\n') goto out_unlock; - memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); + memcpy(nt->remote_mac, remote_mac, ETH_ALEN); ret = count; out_unlock: @@ -1481,15 +1565,15 @@ static void drop_netconsole_target(struct config_group *group, mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); - /* A STATE_DEACTIVATED target may have been moved to - * target_cleanup_list by netconsole_netdev_event() but not yet - * processed by netconsole_process_cleanups_core(). Unlinking it below - * hides it from the cleanup worker, so this path has to clean it up - * itself. Record that the target still owns a netpoll before the - * state is downgraded. + /* A target moved to target_cleanup_list by netconsole_netdev_event() + * but not yet processed still owns a netpoll; unlinking it below hides + * it from the cleanup worker, so this path must tear it down itself. + * This covers NETDEV_UNREGISTER (STATE_DEACTIVATED) and + * NETDEV_RELEASE / NETDEV_JOIN (STATE_DISABLED); key off nt->np.dev, + * which stays set until the netpoll is cleaned up. */ needs_cleanup = nt->state == STATE_ENABLED || - nt->state == STATE_DEACTIVATED; + nt->state == STATE_DEACTIVATED || nt->np.dev; /* Disable deactivated target to prevent races between resume attempt * and target removal. */ @@ -1514,8 +1598,10 @@ static void drop_netconsole_target(struct config_group *group, * netpoll_cleanup() is idempotent (it skips when np->dev is NULL), so * it is safe even if the cleanup worker already tore the netpoll down. */ - if (needs_cleanup) + if (needs_cleanup) { + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); + } config_item_put(&nt->group.cg_item); } @@ -1706,7 +1792,7 @@ static struct notifier_block netconsole_netdev_notifier = { * pool locks and is therefore not NMI-safe. Skip the refill when called * from NMI context; the next non-NMI caller will top the pool back up. */ -static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) +static struct sk_buff *netcons_skb_pop(struct netconsole_target *nt, int len) { struct sk_buff *skb; @@ -1718,19 +1804,21 @@ static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) if (!in_nmi()) net_warn_ratelimited("netconsole: dropping message, requested skb len %d exceeds pool buffer size %zu on %s\n", len, (size_t)MAX_SKB_SIZE, - np->dev->name); + nt->np.dev->name); return NULL; } - skb = skb_dequeue(&np->skb_pool); + skb = skb_dequeue(&nt->skb_pool); if (!in_nmi()) - schedule_work(&np->refill_wq); + schedule_work(&nt->refill_wq); return skb; } -static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) +static struct sk_buff *find_skb(struct netconsole_target *nt, int len, + int reserve) { + struct netpoll *np = &nt->np; int count = 0; struct sk_buff *skb; @@ -1739,7 +1827,7 @@ static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) skb = alloc_skb(len, GFP_ATOMIC | __GFP_NOWARN); if (!skb) - skb = netcons_skb_pop(np, len); + skb = netcons_skb_pop(nt, len); if (!skb) { if (++count < 10) { @@ -1779,8 +1867,9 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, udph->check = CSUM_MANGLED_0; } -static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) +static void push_udp(struct netconsole_target *nt, struct sk_buff *skb, int len) { + struct netpoll *np = &nt->np; struct udphdr *udph; int udp_len; @@ -1790,21 +1879,22 @@ static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) skb_reset_transport_header(skb); udph = udp_hdr(skb); - udph->source = htons(np->local_port); - udph->dest = htons(np->remote_port); + udph->source = htons(nt->local_port); + udph->dest = htons(nt->remote_port); udph->len = htons(udp_len); netpoll_udp_checksum(np, skb, len); } -static void push_eth(struct netpoll *np, struct sk_buff *skb) +static void push_eth(struct netconsole_target *nt, struct sk_buff *skb) { + struct netpoll *np = &nt->np; struct ethhdr *eth; eth = skb_push(skb, ETH_HLEN); skb_reset_mac_header(skb); ether_addr_copy(eth->h_source, np->dev->dev_addr); - ether_addr_copy(eth->h_dest, np->remote_mac); + ether_addr_copy(eth->h_dest, nt->remote_mac); if (np->ipv6) eth->h_proto = htons(ETH_P_IPV6); else @@ -1861,8 +1951,10 @@ static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) skb->protocol = htons(ETH_P_IPV6); } -static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) +static int netpoll_send_udp(struct netconsole_target *nt, const char *msg, + int len) { + struct netpoll *np = &nt->np; int total_len, ip_len, udp_len; struct sk_buff *skb; @@ -1877,7 +1969,7 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) total_len = ip_len + LL_RESERVED_SPACE(np->dev); - skb = find_skb(np, total_len + np->dev->needed_tailroom, + skb = find_skb(nt, total_len + np->dev->needed_tailroom, total_len - len); if (!skb) return -ENOMEM; @@ -1885,12 +1977,12 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) skb_copy_to_linear_data(skb, msg, len); skb_put(skb, len); - push_udp(np, skb, len); + push_udp(nt, skb, len); if (np->ipv6) push_ipv6(np, skb, len); else push_ipv4(np, skb, len); - push_eth(np, skb); + push_eth(nt, skb); skb->dev = np->dev; return (int)netpoll_send_skb(np, skb); @@ -1908,7 +2000,7 @@ static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) */ static void send_udp(struct netconsole_target *nt, const char *msg, int len) { - int result = netpoll_send_udp(&nt->np, msg, len); + int result = netpoll_send_udp(nt, msg, len); if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) { if (result == NET_XMIT_DROP) { @@ -2203,8 +2295,9 @@ __releases(&target_list_lock) spin_unlock_irqrestore(&target_list_lock, flags); } -static int netconsole_parser_cmdline(struct netpoll *np, char *opt) +static int netconsole_parser_cmdline(struct netconsole_target *nt, char *opt) { + struct netpoll *np = &nt->np; bool ipversion_set = false; char *cur = opt; char *delim; @@ -2215,7 +2308,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (!delim) goto parse_failed; *delim = 0; - if (kstrtou16(cur, 10, &np->local_port)) + if (kstrtou16(cur, 10, &nt->local_port)) goto parse_failed; cur = delim; } @@ -2262,7 +2355,7 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) *delim = 0; if (*cur == ' ' || *cur == '\t') np_info(np, "warning: whitespace is not allowed\n"); - if (kstrtou16(cur, 10, &np->remote_port)) + if (kstrtou16(cur, 10, &nt->remote_port)) goto parse_failed; cur = delim; } @@ -2284,11 +2377,11 @@ static int netconsole_parser_cmdline(struct netpoll *np, char *opt) if (*cur != 0) { /* MAC address */ - if (!mac_pton(cur, np->remote_mac)) + if (!mac_pton(cur, nt->remote_mac)) goto parse_failed; } - netconsole_print_banner(np); + netconsole_print_banner(nt); return 0; @@ -2326,14 +2419,22 @@ static struct netconsole_target *alloc_param_target(char *target_config, } /* Parse parameters and setup netpoll */ - err = netconsole_parser_cmdline(&nt->np, target_config); + err = netconsole_parser_cmdline(nt, target_config); if (err) goto fail; + /* Initialise the skb pool before netpoll_setup() so the pool is + * valid as soon as nt->np.dev becomes visible. The target is not + * yet on target_list, so a netdev event cannot reach it here, but + * mirror the configfs path for symmetry. + */ + netconsole_skb_pool_init(nt); + err = netpoll_setup(&nt->np); if (err) { pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); + netconsole_skb_pool_flush(nt); if (!IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) /* only fail if dynamic reconfiguration is set, * otherwise, keep the target in the list, but disabled. @@ -2355,6 +2456,8 @@ static struct netconsole_target *alloc_param_target(char *target_config, static void free_param_target(struct netconsole_target *nt) { cancel_work_sync(&nt->resume_wq); + if (nt->state == STATE_ENABLED) + netconsole_skb_pool_flush(nt); netpoll_cleanup(&nt->np); #ifdef CONFIG_NETCONSOLE_DYNAMIC kfree(nt->userdata); diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 88f7daa8560e..79315461a7b1 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -21,20 +21,6 @@ union inet_addr { struct in6_addr in6; }; -/* - * Maximum payload netpoll's preallocated skb pool can carry. Keep this in - * sync with the buffer size used by refill_skbs() in net/core/netpoll.c; - * callers (e.g. netconsole) use it to detect requests the pool can never - * satisfy and avoid dequeuing a pooled skb that would later trip - * skb_over_panic() in skb_put(). - */ -#define MAX_UDP_CHUNK 1460 -#define MAX_SKB_SIZE \ - (sizeof(struct ethhdr) + \ - sizeof(struct iphdr) + \ - sizeof(struct udphdr) + \ - MAX_UDP_CHUNK) - struct netpoll { struct net_device *dev; netdevice_tracker dev_tracker; @@ -49,10 +35,6 @@ struct netpoll { union inet_addr local_ip, remote_ip; bool ipv6; - u16 local_port, remote_port; - u8 remote_mac[ETH_ALEN]; - struct sk_buff_head skb_pool; - struct work_struct refill_wq; }; #define np_info(np, fmt, ...) \ diff --git a/net/core/netpoll.c b/net/core/netpoll.c index aed415d3cd74..f8da1048ea3a 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -36,12 +36,6 @@ #include #include -/* - * We maintain a small pool of fully-sized skbs, to make sure the - * message gets out even in extreme OOM situations. - */ - -#define MAX_SKBS 32 #define USEC_PER_POLL 50 static unsigned int carrier_timeout = 4; @@ -213,22 +207,6 @@ void netpoll_poll_enable(struct net_device *dev) up(&ni->dev_lock); } -static void refill_skbs(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - struct sk_buff *skb; - - skb_pool = &np->skb_pool; - - while (READ_ONCE(skb_pool->qlen) < MAX_SKBS) { - skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC | __GFP_NOWARN); - if (!skb) - break; - - skb_queue_tail(skb_pool, skb); - } -} - void netpoll_zap_completion_queue(void) { unsigned long flags; @@ -351,32 +329,12 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -static void skb_pool_flush(struct netpoll *np) -{ - struct sk_buff_head *skb_pool; - - cancel_work_sync(&np->refill_wq); - skb_pool = &np->skb_pool; - skb_queue_purge_reason(skb_pool, SKB_CONSUMED); -} - -static void refill_skbs_work_handler(struct work_struct *work) -{ - struct netpoll *np = - container_of(work, struct netpoll, refill_wq); - - refill_skbs(np); -} - int __netpoll_setup(struct netpoll *np, struct net_device *ndev) { struct netpoll_info *npinfo; const struct net_device_ops *ops; int err; - skb_queue_head_init(&np->skb_pool); - INIT_WORK(&np->refill_wq, refill_skbs_work_handler); - if (ndev->priv_flags & IFF_DISABLE_NETPOLL) { np_err(np, "%s doesn't support polling, aborting\n", ndev->name); @@ -411,9 +369,6 @@ int __netpoll_setup(struct netpoll *np, struct net_device *ndev) np->dev = ndev; strscpy(np->dev_name, ndev->name, IFNAMSIZ); - /* fill up the skb queue */ - refill_skbs(np); - /* last thing to do is link it to the net device structure */ rcu_assign_pointer(ndev->npinfo, npinfo); @@ -603,7 +558,7 @@ int netpoll_setup(struct netpoll *np) err = __netpoll_setup(np, ndev); if (err) - goto flush; + goto put; rtnl_unlock(); /* Make sure all NAPI polls which started before dev->npinfo @@ -614,8 +569,6 @@ int netpoll_setup(struct netpoll *np) return 0; -flush: - skb_pool_flush(np); put: DEBUG_NET_WARN_ON_ONCE(np->dev); if (ip_overwritten) @@ -659,8 +612,6 @@ static void __netpoll_cleanup(struct netpoll *np) disable_delayed_work_sync(&npinfo->tx_work); call_rcu(&npinfo->rcu, rcu_cleanup_netpoll_info); } - - skb_pool_flush(np); } void __netpoll_free(struct netpoll *np)