Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Merge branch 'netconsole-support-automatic-target-recovery'

Andre Carvalho says:

====================
netconsole: support automatic target recovery

This patchset introduces target resume capability to netconsole allowing
it to recover targets when underlying low-level interface comes back
online.

The patchset starts by refactoring netconsole state representation in
order to allow representing deactivated targets (targets that are
disabled due to interfaces unregister).

It then modifies netconsole to handle NETDEV_REGISTER events for such
targets, setups netpoll and forces the device UP. Targets are matched with
incoming interfaces depending on how they were bound in netconsole
(by mac or interface name). For these reasons, we also attempt resuming
on NETDEV_CHANGENAME.

The patchset includes a selftest that validates netconsole target state
transitions and that target is functional after resumed.
====================

Link: https://patch.msgid.link/20260118-netcons-retrigger-v11-0-4de36aebcf48@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+391 -74
+236 -69
drivers/net/netconsole.c
··· 39 39 #include <linux/u64_stats_sync.h> 40 40 #include <linux/utsname.h> 41 41 #include <linux/rtnetlink.h> 42 + #include <linux/workqueue.h> 42 43 43 44 MODULE_AUTHOR("Matt Mackall <mpm@selenic.com>"); 44 45 MODULE_DESCRIPTION("Console driver for network interfaces"); ··· 86 85 /* This needs to be a mutex because netpoll_cleanup might sleep */ 87 86 static DEFINE_MUTEX(target_cleanup_list_lock); 88 87 88 + static struct workqueue_struct *netconsole_wq; 89 + 89 90 /* 90 91 * Console driver for netconsoles. Register only consoles that have 91 92 * an associated target of the same type. ··· 122 119 MAX_SYSDATA_ITEMS = 4, 123 120 }; 124 121 122 + enum target_state { 123 + STATE_DISABLED, 124 + STATE_ENABLED, 125 + STATE_DEACTIVATED, 126 + }; 127 + 125 128 /** 126 129 * struct netconsole_target - Represents a configured netconsole target. 127 130 * @list: Links this target into the target_list. ··· 139 130 * @sysdata_fields: Sysdata features enabled. 140 131 * @msgcounter: Message sent counter. 141 132 * @stats: Packet send stats for the target. Used for debugging. 142 - * @enabled: On / off knob to enable / disable target. 133 + * @state: State of the target. 143 134 * Visible from userspace (read-write). 144 - * We maintain a strict 1:1 correspondence between this and 145 - * whether the corresponding netpoll is active or inactive. 135 + * From a userspace perspective, the target is either enabled or 136 + * disabled. Internally, although both STATE_DISABLED and 137 + * STATE_DEACTIVATED correspond to inactive targets, the latter is 138 + * due to automatic interface state changes and will try 139 + * recover automatically, if the interface comes back 140 + * online. 146 141 * Also, other parameters of a target may be modified at 147 - * runtime only when it is disabled (enabled == 0). 142 + * runtime only when it is disabled (state != STATE_ENABLED). 148 143 * @extended: Denotes whether console is extended or not. 149 144 * @release: Denotes whether kernel release version should be prepended 150 145 * to the message. Depends on extended console. ··· 162 149 * local_mac (read-only) 163 150 * remote_mac (read-write) 164 151 * @buf: The buffer used to send the full msg to the network stack 152 + * @resume_wq: Workqueue to resume deactivated target 165 153 */ 166 154 struct netconsole_target { 167 155 struct list_head list; ··· 179 165 u32 msgcounter; 180 166 #endif 181 167 struct netconsole_target_stats stats; 182 - bool enabled; 168 + enum target_state state; 183 169 bool extended; 184 170 bool release; 185 171 struct netpoll np; 186 172 /* protected by target_list_lock */ 187 173 char buf[MAX_PRINT_CHUNK]; 174 + struct work_struct resume_wq; 188 175 }; 189 176 190 177 #ifdef CONFIG_NETCONSOLE_DYNAMIC ··· 222 207 config_group_put(&nt->group); 223 208 } 224 209 210 + static void dynamic_netconsole_mutex_lock(void) 211 + { 212 + mutex_lock(&dynamic_netconsole_mutex); 213 + } 214 + 215 + static void dynamic_netconsole_mutex_unlock(void) 216 + { 217 + mutex_unlock(&dynamic_netconsole_mutex); 218 + } 219 + 225 220 #else /* !CONFIG_NETCONSOLE_DYNAMIC */ 226 221 227 222 static int __init dynamic_netconsole_init(void) ··· 259 234 int cmdline_count) 260 235 { 261 236 } 237 + 238 + static void dynamic_netconsole_mutex_lock(void) 239 + { 240 + } 241 + 242 + static void dynamic_netconsole_mutex_unlock(void) 243 + { 244 + } 245 + 262 246 #endif /* CONFIG_NETCONSOLE_DYNAMIC */ 247 + 248 + /* Check if the target was bound by mac address. */ 249 + static bool bound_by_mac(struct netconsole_target *nt) 250 + { 251 + return is_valid_ether_addr(nt->np.dev_mac); 252 + } 253 + 254 + /* Attempts to resume logging to a deactivated target. */ 255 + static void resume_target(struct netconsole_target *nt) 256 + { 257 + if (netpoll_setup(&nt->np)) { 258 + /* netpoll fails setup once, do not try again. */ 259 + nt->state = STATE_DISABLED; 260 + return; 261 + } 262 + 263 + nt->state = STATE_ENABLED; 264 + pr_info("network logging resumed on interface %s\n", nt->np.dev_name); 265 + } 266 + 267 + /* Checks if a deactivated target matches a device. */ 268 + static bool deactivated_target_match(struct netconsole_target *nt, 269 + struct net_device *ndev) 270 + { 271 + if (nt->state != STATE_DEACTIVATED) 272 + return false; 273 + 274 + if (bound_by_mac(nt)) 275 + return !memcmp(nt->np.dev_mac, ndev->dev_addr, ETH_ALEN); 276 + return !strncmp(nt->np.dev_name, ndev->name, IFNAMSIZ); 277 + } 278 + 279 + /* Process work scheduled for target resume. */ 280 + static void process_resume_target(struct work_struct *work) 281 + { 282 + struct netconsole_target *nt; 283 + unsigned long flags; 284 + 285 + nt = container_of(work, struct netconsole_target, resume_wq); 286 + 287 + dynamic_netconsole_mutex_lock(); 288 + 289 + spin_lock_irqsave(&target_list_lock, flags); 290 + /* Check if target is still deactivated as it may have been disabled 291 + * while resume was being scheduled. 292 + */ 293 + if (nt->state != STATE_DEACTIVATED) { 294 + spin_unlock_irqrestore(&target_list_lock, flags); 295 + goto out_unlock; 296 + } 297 + 298 + /* resume_target is IRQ unsafe, remove target from 299 + * target_list in order to resume it with IRQ enabled. 300 + */ 301 + list_del_init(&nt->list); 302 + spin_unlock_irqrestore(&target_list_lock, flags); 303 + 304 + resume_target(nt); 305 + 306 + /* At this point the target is either enabled or disabled and 307 + * was cleaned up before getting deactivated. Either way, add it 308 + * back to target list. 309 + */ 310 + spin_lock_irqsave(&target_list_lock, flags); 311 + list_add(&nt->list, &target_list); 312 + spin_unlock_irqrestore(&target_list_lock, flags); 313 + 314 + out_unlock: 315 + dynamic_netconsole_mutex_unlock(); 316 + } 263 317 264 318 /* Allocate and initialize with defaults. 265 319 * Note that these targets get their config_item fields zeroed-out. ··· 361 257 nt->np.local_port = 6665; 362 258 nt->np.remote_port = 6666; 363 259 eth_broadcast_addr(nt->np.remote_mac); 260 + nt->state = STATE_DISABLED; 261 + INIT_WORK(&nt->resume_wq, process_resume_target); 364 262 365 263 return nt; 366 264 } ··· 381 275 mutex_lock(&target_cleanup_list_lock); 382 276 list_for_each_entry_safe(nt, tmp, &target_cleanup_list, list) { 383 277 /* all entries in the cleanup_list needs to be disabled */ 384 - WARN_ON_ONCE(nt->enabled); 278 + WARN_ON_ONCE(nt->state == STATE_ENABLED); 385 279 do_netpoll_cleanup(&nt->np); 280 + if (bound_by_mac(nt)) 281 + memset(&nt->np.dev_name, 0, IFNAMSIZ); 386 282 /* moved the cleaned target to target_list. Need to hold both 387 283 * locks 388 284 */ ··· 506 398 507 399 static ssize_t enabled_show(struct config_item *item, char *buf) 508 400 { 509 - return sysfs_emit(buf, "%d\n", to_target(item)->enabled); 401 + return sysfs_emit(buf, "%d\n", to_target(item)->state == STATE_ENABLED); 510 402 } 511 403 512 404 static ssize_t extended_show(struct config_item *item, char *buf) ··· 588 480 struct netconsole_target *nt = to_target(item->ci_parent); 589 481 bool cpu_nr_enabled; 590 482 591 - mutex_lock(&dynamic_netconsole_mutex); 483 + dynamic_netconsole_mutex_lock(); 592 484 cpu_nr_enabled = !!(nt->sysdata_fields & SYSDATA_CPU_NR); 593 - mutex_unlock(&dynamic_netconsole_mutex); 485 + dynamic_netconsole_mutex_unlock(); 594 486 595 487 return sysfs_emit(buf, "%d\n", cpu_nr_enabled); 596 488 } ··· 602 494 struct netconsole_target *nt = to_target(item->ci_parent); 603 495 bool taskname_enabled; 604 496 605 - mutex_lock(&dynamic_netconsole_mutex); 497 + dynamic_netconsole_mutex_lock(); 606 498 taskname_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME); 607 - mutex_unlock(&dynamic_netconsole_mutex); 499 + dynamic_netconsole_mutex_unlock(); 608 500 609 501 return sysfs_emit(buf, "%d\n", taskname_enabled); 610 502 } ··· 615 507 struct netconsole_target *nt = to_target(item->ci_parent); 616 508 bool release_enabled; 617 509 618 - mutex_lock(&dynamic_netconsole_mutex); 510 + dynamic_netconsole_mutex_lock(); 619 511 release_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME); 620 - mutex_unlock(&dynamic_netconsole_mutex); 512 + dynamic_netconsole_mutex_unlock(); 621 513 622 514 return sysfs_emit(buf, "%d\n", release_enabled); 623 515 } ··· 655 547 struct netconsole_target *nt = to_target(item->ci_parent); 656 548 bool msgid_enabled; 657 549 658 - mutex_lock(&dynamic_netconsole_mutex); 550 + dynamic_netconsole_mutex_lock(); 659 551 msgid_enabled = !!(nt->sysdata_fields & SYSDATA_MSGID); 660 - mutex_unlock(&dynamic_netconsole_mutex); 552 + dynamic_netconsole_mutex_unlock(); 661 553 662 554 return sysfs_emit(buf, "%d\n", msgid_enabled); 663 555 } ··· 673 565 const char *buf, size_t count) 674 566 { 675 567 struct netconsole_target *nt = to_target(item); 568 + bool enabled, current_enabled; 676 569 unsigned long flags; 677 - bool enabled; 678 570 ssize_t ret; 679 571 680 - mutex_lock(&dynamic_netconsole_mutex); 572 + dynamic_netconsole_mutex_lock(); 681 573 ret = kstrtobool(buf, &enabled); 682 574 if (ret) 683 575 goto out_unlock; 684 576 577 + /* When the user explicitly enables or disables a target that is 578 + * currently deactivated, reset its state to disabled. The DEACTIVATED 579 + * state only tracks interface-driven deactivation and should _not_ 580 + * persist when the user manually changes the target's enabled state. 581 + */ 582 + if (nt->state == STATE_DEACTIVATED) 583 + nt->state = STATE_DISABLED; 584 + 685 585 ret = -EINVAL; 686 - if (enabled == nt->enabled) { 586 + current_enabled = nt->state == STATE_ENABLED; 587 + if (enabled == current_enabled) { 687 588 pr_info("network logging has already %s\n", 688 - nt->enabled ? "started" : "stopped"); 589 + current_enabled ? "started" : "stopped"); 689 590 goto out_unlock; 690 591 } 691 592 ··· 727 610 if (ret) 728 611 goto out_unlock; 729 612 730 - nt->enabled = true; 613 + nt->state = STATE_ENABLED; 731 614 pr_info("network logging started\n"); 732 615 } else { /* false */ 733 616 /* We need to disable the netconsole before cleaning it up 734 617 * otherwise we might end up in write_msg() with 735 - * nt->np.dev == NULL and nt->enabled == true 618 + * nt->np.dev == NULL and nt->state == STATE_ENABLED 736 619 */ 737 620 mutex_lock(&target_cleanup_list_lock); 738 621 spin_lock_irqsave(&target_list_lock, flags); 739 - nt->enabled = false; 622 + nt->state = STATE_DISABLED; 740 623 /* Remove the target from the list, while holding 741 624 * target_list_lock 742 625 */ ··· 753 636 /* Deferred cleanup */ 754 637 netconsole_process_cleanups(); 755 638 out_unlock: 756 - mutex_unlock(&dynamic_netconsole_mutex); 639 + dynamic_netconsole_mutex_unlock(); 757 640 return ret; 758 641 } 759 642 ··· 764 647 bool release; 765 648 ssize_t ret; 766 649 767 - mutex_lock(&dynamic_netconsole_mutex); 768 - if (nt->enabled) { 650 + dynamic_netconsole_mutex_lock(); 651 + if (nt->state == STATE_ENABLED) { 769 652 pr_err("target (%s) is enabled, disable to update parameters\n", 770 653 config_item_name(&nt->group.cg_item)); 771 654 ret = -EINVAL; ··· 780 663 781 664 ret = strnlen(buf, count); 782 665 out_unlock: 783 - mutex_unlock(&dynamic_netconsole_mutex); 666 + dynamic_netconsole_mutex_unlock(); 784 667 return ret; 785 668 } 786 669 ··· 791 674 bool extended; 792 675 ssize_t ret; 793 676 794 - mutex_lock(&dynamic_netconsole_mutex); 795 - if (nt->enabled) { 677 + dynamic_netconsole_mutex_lock(); 678 + if (nt->state == STATE_ENABLED) { 796 679 pr_err("target (%s) is enabled, disable to update parameters\n", 797 680 config_item_name(&nt->group.cg_item)); 798 681 ret = -EINVAL; ··· 806 689 nt->extended = extended; 807 690 ret = strnlen(buf, count); 808 691 out_unlock: 809 - mutex_unlock(&dynamic_netconsole_mutex); 692 + dynamic_netconsole_mutex_unlock(); 810 693 return ret; 811 694 } 812 695 ··· 815 698 { 816 699 struct netconsole_target *nt = to_target(item); 817 700 818 - mutex_lock(&dynamic_netconsole_mutex); 819 - if (nt->enabled) { 701 + dynamic_netconsole_mutex_lock(); 702 + if (nt->state == STATE_ENABLED) { 820 703 pr_err("target (%s) is enabled, disable to update parameters\n", 821 704 config_item_name(&nt->group.cg_item)); 822 - mutex_unlock(&dynamic_netconsole_mutex); 705 + dynamic_netconsole_mutex_unlock(); 823 706 return -EINVAL; 824 707 } 825 708 826 709 strscpy(nt->np.dev_name, buf, IFNAMSIZ); 827 710 trim_newline(nt->np.dev_name, IFNAMSIZ); 828 711 829 - mutex_unlock(&dynamic_netconsole_mutex); 712 + dynamic_netconsole_mutex_unlock(); 830 713 return strnlen(buf, count); 831 714 } 832 715 ··· 836 719 struct netconsole_target *nt = to_target(item); 837 720 ssize_t ret = -EINVAL; 838 721 839 - mutex_lock(&dynamic_netconsole_mutex); 840 - if (nt->enabled) { 722 + dynamic_netconsole_mutex_lock(); 723 + if (nt->state == STATE_ENABLED) { 841 724 pr_err("target (%s) is enabled, disable to update parameters\n", 842 725 config_item_name(&nt->group.cg_item)); 843 726 goto out_unlock; ··· 848 731 goto out_unlock; 849 732 ret = strnlen(buf, count); 850 733 out_unlock: 851 - mutex_unlock(&dynamic_netconsole_mutex); 734 + dynamic_netconsole_mutex_unlock(); 852 735 return ret; 853 736 } 854 737 ··· 858 741 struct netconsole_target *nt = to_target(item); 859 742 ssize_t ret = -EINVAL; 860 743 861 - mutex_lock(&dynamic_netconsole_mutex); 862 - if (nt->enabled) { 744 + dynamic_netconsole_mutex_lock(); 745 + if (nt->state == STATE_ENABLED) { 863 746 pr_err("target (%s) is enabled, disable to update parameters\n", 864 747 config_item_name(&nt->group.cg_item)); 865 748 goto out_unlock; ··· 870 753 goto out_unlock; 871 754 ret = strnlen(buf, count); 872 755 out_unlock: 873 - mutex_unlock(&dynamic_netconsole_mutex); 756 + dynamic_netconsole_mutex_unlock(); 874 757 return ret; 875 758 } 876 759 ··· 881 764 ssize_t ret = -EINVAL; 882 765 int ipv6; 883 766 884 - mutex_lock(&dynamic_netconsole_mutex); 885 - if (nt->enabled) { 767 + dynamic_netconsole_mutex_lock(); 768 + if (nt->state == STATE_ENABLED) { 886 769 pr_err("target (%s) is enabled, disable to update parameters\n", 887 770 config_item_name(&nt->group.cg_item)); 888 771 goto out_unlock; ··· 895 778 896 779 ret = strnlen(buf, count); 897 780 out_unlock: 898 - mutex_unlock(&dynamic_netconsole_mutex); 781 + dynamic_netconsole_mutex_unlock(); 899 782 return ret; 900 783 } 901 784 ··· 906 789 ssize_t ret = -EINVAL; 907 790 int ipv6; 908 791 909 - mutex_lock(&dynamic_netconsole_mutex); 910 - if (nt->enabled) { 792 + dynamic_netconsole_mutex_lock(); 793 + if (nt->state == STATE_ENABLED) { 911 794 pr_err("target (%s) is enabled, disable to update parameters\n", 912 795 config_item_name(&nt->group.cg_item)); 913 796 goto out_unlock; ··· 920 803 921 804 ret = strnlen(buf, count); 922 805 out_unlock: 923 - mutex_unlock(&dynamic_netconsole_mutex); 806 + dynamic_netconsole_mutex_unlock(); 924 807 return ret; 925 808 } 926 809 ··· 941 824 u8 remote_mac[ETH_ALEN]; 942 825 ssize_t ret = -EINVAL; 943 826 944 - mutex_lock(&dynamic_netconsole_mutex); 945 - if (nt->enabled) { 827 + dynamic_netconsole_mutex_lock(); 828 + if (nt->state == STATE_ENABLED) { 946 829 pr_err("target (%s) is enabled, disable to update parameters\n", 947 830 config_item_name(&nt->group.cg_item)); 948 831 goto out_unlock; ··· 956 839 957 840 ret = strnlen(buf, count); 958 841 out_unlock: 959 - mutex_unlock(&dynamic_netconsole_mutex); 842 + dynamic_netconsole_mutex_unlock(); 960 843 return ret; 961 844 } 962 845 ··· 1077 960 return -EMSGSIZE; 1078 961 1079 962 mutex_lock(&netconsole_subsys.su_mutex); 1080 - mutex_lock(&dynamic_netconsole_mutex); 963 + dynamic_netconsole_mutex_lock(); 1081 964 1082 965 ret = strscpy(udm->value, buf, sizeof(udm->value)); 1083 966 if (ret < 0) ··· 1091 974 goto out_unlock; 1092 975 ret = count; 1093 976 out_unlock: 1094 - mutex_unlock(&dynamic_netconsole_mutex); 977 + dynamic_netconsole_mutex_unlock(); 1095 978 mutex_unlock(&netconsole_subsys.su_mutex); 1096 979 return ret; 1097 980 } ··· 1119 1002 return ret; 1120 1003 1121 1004 mutex_lock(&netconsole_subsys.su_mutex); 1122 - mutex_lock(&dynamic_netconsole_mutex); 1005 + dynamic_netconsole_mutex_lock(); 1123 1006 curr = !!(nt->sysdata_fields & SYSDATA_MSGID); 1124 1007 if (msgid_enabled == curr) 1125 1008 goto unlock_ok; ··· 1131 1014 1132 1015 unlock_ok: 1133 1016 ret = strnlen(buf, count); 1134 - mutex_unlock(&dynamic_netconsole_mutex); 1017 + dynamic_netconsole_mutex_unlock(); 1135 1018 mutex_unlock(&netconsole_subsys.su_mutex); 1136 1019 return ret; 1137 1020 } ··· 1148 1031 return ret; 1149 1032 1150 1033 mutex_lock(&netconsole_subsys.su_mutex); 1151 - mutex_lock(&dynamic_netconsole_mutex); 1034 + dynamic_netconsole_mutex_lock(); 1152 1035 curr = !!(nt->sysdata_fields & SYSDATA_RELEASE); 1153 1036 if (release_enabled == curr) 1154 1037 goto unlock_ok; ··· 1160 1043 1161 1044 unlock_ok: 1162 1045 ret = strnlen(buf, count); 1163 - mutex_unlock(&dynamic_netconsole_mutex); 1046 + dynamic_netconsole_mutex_unlock(); 1164 1047 mutex_unlock(&netconsole_subsys.su_mutex); 1165 1048 return ret; 1166 1049 } ··· 1177 1060 return ret; 1178 1061 1179 1062 mutex_lock(&netconsole_subsys.su_mutex); 1180 - mutex_lock(&dynamic_netconsole_mutex); 1063 + dynamic_netconsole_mutex_lock(); 1181 1064 curr = !!(nt->sysdata_fields & SYSDATA_TASKNAME); 1182 1065 if (taskname_enabled == curr) 1183 1066 goto unlock_ok; ··· 1189 1072 1190 1073 unlock_ok: 1191 1074 ret = strnlen(buf, count); 1192 - mutex_unlock(&dynamic_netconsole_mutex); 1075 + dynamic_netconsole_mutex_unlock(); 1193 1076 mutex_unlock(&netconsole_subsys.su_mutex); 1194 1077 return ret; 1195 1078 } ··· 1207 1090 return ret; 1208 1091 1209 1092 mutex_lock(&netconsole_subsys.su_mutex); 1210 - mutex_lock(&dynamic_netconsole_mutex); 1093 + dynamic_netconsole_mutex_lock(); 1211 1094 curr = !!(nt->sysdata_fields & SYSDATA_CPU_NR); 1212 1095 if (cpu_nr_enabled == curr) 1213 1096 /* no change requested */ ··· 1223 1106 1224 1107 unlock_ok: 1225 1108 ret = strnlen(buf, count); 1226 - mutex_unlock(&dynamic_netconsole_mutex); 1109 + dynamic_netconsole_mutex_unlock(); 1227 1110 mutex_unlock(&netconsole_subsys.su_mutex); 1228 1111 return ret; 1229 1112 } ··· 1285 1168 ud = to_userdata(&group->cg_item); 1286 1169 nt = userdata_to_target(ud); 1287 1170 1288 - mutex_lock(&dynamic_netconsole_mutex); 1171 + dynamic_netconsole_mutex_lock(); 1289 1172 update_userdata(nt); 1290 1173 config_item_put(item); 1291 - mutex_unlock(&dynamic_netconsole_mutex); 1174 + dynamic_netconsole_mutex_unlock(); 1292 1175 } 1293 1176 1294 1177 static struct configfs_attribute *userdata_attrs[] = { ··· 1427 1310 static void drop_netconsole_target(struct config_group *group, 1428 1311 struct config_item *item) 1429 1312 { 1430 - unsigned long flags; 1431 1313 struct netconsole_target *nt = to_target(item); 1314 + unsigned long flags; 1315 + 1316 + dynamic_netconsole_mutex_lock(); 1432 1317 1433 1318 spin_lock_irqsave(&target_list_lock, flags); 1319 + /* Disable deactivated target to prevent races between resume attempt 1320 + * and target removal. 1321 + */ 1322 + if (nt->state == STATE_DEACTIVATED) 1323 + nt->state = STATE_DISABLED; 1434 1324 list_del(&nt->list); 1435 1325 spin_unlock_irqrestore(&target_list_lock, flags); 1326 + 1327 + dynamic_netconsole_mutex_unlock(); 1328 + 1329 + /* Now that the target has been marked disabled no further work 1330 + * can be scheduled. Existing work will skip as targets are not 1331 + * deactivated anymore. Cancel any scheduled resume and wait for 1332 + * completion. 1333 + */ 1334 + cancel_work_sync(&nt->resume_wq); 1436 1335 1437 1336 /* 1438 1337 * The target may have never been enabled, or was manually disabled 1439 1338 * before being removed so netpoll may have already been cleaned up. 1440 1339 */ 1441 - if (nt->enabled) 1340 + if (nt->state == STATE_ENABLED) 1442 1341 netpoll_cleanup(&nt->np); 1443 1342 1444 1343 config_item_put(&nt->group.cg_item); ··· 1551 1418 static int netconsole_netdev_event(struct notifier_block *this, 1552 1419 unsigned long event, void *ptr) 1553 1420 { 1554 - unsigned long flags; 1555 - struct netconsole_target *nt, *tmp; 1556 1421 struct net_device *dev = netdev_notifier_info_to_dev(ptr); 1422 + struct netconsole_target *nt, *tmp; 1557 1423 bool stopped = false; 1424 + unsigned long flags; 1558 1425 1559 1426 if (!(event == NETDEV_CHANGENAME || event == NETDEV_UNREGISTER || 1560 - event == NETDEV_RELEASE || event == NETDEV_JOIN)) 1427 + event == NETDEV_RELEASE || event == NETDEV_JOIN || 1428 + event == NETDEV_REGISTER)) 1561 1429 goto done; 1562 1430 1563 1431 mutex_lock(&target_cleanup_list_lock); ··· 1572 1438 break; 1573 1439 case NETDEV_RELEASE: 1574 1440 case NETDEV_JOIN: 1441 + /* transition target to DISABLED instead of 1442 + * DEACTIVATED when (de)enslaving devices as 1443 + * their targets should not be automatically 1444 + * resumed when the interface is brought up. 1445 + */ 1446 + nt->state = STATE_DISABLED; 1447 + list_move(&nt->list, &target_cleanup_list); 1448 + stopped = true; 1449 + break; 1575 1450 case NETDEV_UNREGISTER: 1576 - nt->enabled = false; 1451 + nt->state = STATE_DEACTIVATED; 1577 1452 list_move(&nt->list, &target_cleanup_list); 1578 1453 stopped = true; 1579 1454 } 1580 1455 } 1456 + if ((event == NETDEV_REGISTER || event == NETDEV_CHANGENAME) && 1457 + deactivated_target_match(nt, dev)) 1458 + /* Schedule resume on a workqueue as it will attempt 1459 + * to UP the device, which can't be done as part of this 1460 + * notifier. 1461 + */ 1462 + queue_work(netconsole_wq, &nt->resume_wq); 1581 1463 netconsole_target_put(nt); 1582 1464 } 1583 1465 spin_unlock_irqrestore(&target_list_lock, flags); ··· 1870 1720 1871 1721 spin_lock_irqsave(&target_list_lock, flags); 1872 1722 list_for_each_entry(nt, &target_list, list) 1873 - if (nt->extended && nt->enabled && netif_running(nt->np.dev)) 1723 + if (nt->extended && nt->state == STATE_ENABLED && 1724 + netif_running(nt->np.dev)) 1874 1725 send_ext_msg_udp(nt, msg, len); 1875 1726 spin_unlock_irqrestore(&target_list_lock, flags); 1876 1727 } ··· 1891 1740 1892 1741 spin_lock_irqsave(&target_list_lock, flags); 1893 1742 list_for_each_entry(nt, &target_list, list) { 1894 - if (!nt->extended && nt->enabled && netif_running(nt->np.dev)) { 1743 + if (!nt->extended && nt->state == STATE_ENABLED && 1744 + netif_running(nt->np.dev)) { 1895 1745 /* 1896 1746 * We nest this inside the for-each-target loop above 1897 1747 * so that we're able to get as much logging out to ··· 2048 1896 */ 2049 1897 goto fail; 2050 1898 } else { 2051 - nt->enabled = true; 1899 + nt->state = STATE_ENABLED; 2052 1900 } 2053 1901 populate_configfs_item(nt, cmdline_count); 2054 1902 ··· 2062 1910 /* Cleanup netpoll for given target (from boot/module param) and free it */ 2063 1911 static void free_param_target(struct netconsole_target *nt) 2064 1912 { 1913 + cancel_work_sync(&nt->resume_wq); 2065 1914 netpoll_cleanup(&nt->np); 2066 1915 #ifdef CONFIG_NETCONSOLE_DYNAMIC 2067 1916 kfree(nt->userdata); ··· 2117 1964 } 2118 1965 } 2119 1966 1967 + netconsole_wq = alloc_workqueue("netconsole", WQ_UNBOUND, 0); 1968 + if (!netconsole_wq) { 1969 + err = -ENOMEM; 1970 + goto fail; 1971 + } 1972 + 2120 1973 err = register_netdevice_notifier(&netconsole_netdev_notifier); 2121 1974 if (err) 2122 1975 goto fail; ··· 2145 1986 fail: 2146 1987 pr_err("cleaning up\n"); 2147 1988 1989 + if (netconsole_wq) 1990 + flush_workqueue(netconsole_wq); 2148 1991 /* 2149 1992 * Remove all targets and destroy them (only targets created 2150 1993 * from the boot/module option exist here). Skipping the list ··· 2156 1995 list_del(&nt->list); 2157 1996 free_param_target(nt); 2158 1997 } 1998 + 1999 + if (netconsole_wq) 2000 + destroy_workqueue(netconsole_wq); 2159 2001 2160 2002 return err; 2161 2003 } ··· 2173 2009 unregister_console(&netconsole); 2174 2010 dynamic_netconsole_exit(); 2175 2011 unregister_netdevice_notifier(&netconsole_netdev_notifier); 2012 + flush_workqueue(netconsole_wq); 2176 2013 2177 2014 /* 2178 2015 * Targets created via configfs pin references on our module ··· 2187 2022 list_del(&nt->list); 2188 2023 free_param_target(nt); 2189 2024 } 2025 + 2026 + destroy_workqueue(netconsole_wq); 2190 2027 } 2191 2028 2192 2029 /*
+1
tools/testing/selftests/drivers/net/Makefile
··· 19 19 netcons_cmdline.sh \ 20 20 netcons_fragmented_msg.sh \ 21 21 netcons_overflow.sh \ 22 + netcons_resume.sh \ 22 23 netcons_sysdata.sh \ 23 24 netcons_torture.sh \ 24 25 netpoll_basic.py \
+30 -5
tools/testing/selftests/drivers/net/lib/sh/lib_netcons.sh
··· 203 203 function cleanup_netcons() { 204 204 # delete netconsole dynamic reconfiguration 205 205 # do not fail if the target is already disabled 206 - if [[ ! -d "${NETCONS_PATH}" ]] 206 + local TARGET_PATH=${1:-${NETCONS_PATH}} 207 + 208 + if [[ ! -d "${TARGET_PATH}" ]] 207 209 then 208 210 # in some cases this is called before netcons path is created 209 211 return 210 212 fi 211 - if [[ $(cat "${NETCONS_PATH}"/enabled) != 0 ]] 213 + if [[ $(cat "${TARGET_PATH}"/enabled) != 0 ]] 212 214 then 213 - echo 0 > "${NETCONS_PATH}"/enabled || true 215 + echo 0 > "${TARGET_PATH}"/enabled || true 214 216 fi 215 217 # Remove all the keys that got created during the selftest 216 - find "${NETCONS_PATH}/userdata/" -mindepth 1 -type d -delete 218 + find "${TARGET_PATH}/userdata/" -mindepth 1 -type d -delete 217 219 # Remove the configfs entry 218 - rmdir "${NETCONS_PATH}" 220 + rmdir "${TARGET_PATH}" 219 221 } 220 222 221 223 function cleanup() { ··· 377 375 echo "SKIP: netconsole should be compiled as a module" >&2 378 376 exit "${ksft_skip}" 379 377 fi 378 + } 379 + 380 + function wait_target_state() { 381 + local TARGET=${1} 382 + local STATE=${2} 383 + local TARGET_PATH="${NETCONS_CONFIGFS}"/"${TARGET}" 384 + local ENABLED=0 385 + 386 + if [ "${STATE}" == "enabled" ] 387 + then 388 + ENABLED=1 389 + fi 390 + 391 + if [ ! -d "$TARGET_PATH" ]; then 392 + echo "FAIL: Target does not exist." >&2 393 + exit "${ksft_fail}" 394 + fi 395 + 396 + local CHECK_CMD="grep \"$ENABLED\" \"$TARGET_PATH/enabled\"" 397 + slowwait 2 sh -c "test -n \"\$($CHECK_CMD)\"" || { 398 + echo "FAIL: ${TARGET} is not ${STATE}." >&2 399 + exit "${ksft_fail}" 400 + } 380 401 } 381 402 382 403 # A wrapper to translate protocol version to udp version
+124
tools/testing/selftests/drivers/net/netcons_resume.sh
··· 1 + #!/usr/bin/env bash 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + # This test validates that netconsole is able to resume a target that was 5 + # deactivated when its interface was removed when the interface is brought 6 + # back up. 7 + # 8 + # The test configures a netconsole target and then removes netdevsim module to 9 + # cause the interface to disappear. Targets are configured via cmdline to ensure 10 + # targets bound by interface name and mac address can be resumed. 11 + # The test verifies that the target moved to disabled state before adding 12 + # netdevsim and the interface back. 13 + # 14 + # Finally, the test verifies that the target is re-enabled automatically and 15 + # the message is received on the destination interface. 16 + # 17 + # Author: Andre Carvalho <asantostc@gmail.com> 18 + 19 + set -euo pipefail 20 + 21 + SCRIPTDIR=$(dirname "$(readlink -e "${BASH_SOURCE[0]}")") 22 + 23 + source "${SCRIPTDIR}"/lib/sh/lib_netcons.sh 24 + 25 + SAVED_SRCMAC="" # to be populated later 26 + SAVED_DSTMAC="" # to be populated later 27 + 28 + modprobe netdevsim 2> /dev/null || true 29 + rmmod netconsole 2> /dev/null || true 30 + 31 + check_netconsole_module 32 + 33 + function cleanup() { 34 + cleanup_netcons "${NETCONS_CONFIGFS}/cmdline0" 35 + do_cleanup 36 + rmmod netconsole 37 + } 38 + 39 + function trigger_reactivation() { 40 + # Add back low level module 41 + modprobe netdevsim 42 + # Recreate namespace and two interfaces 43 + set_network 44 + # Restore MACs 45 + ip netns exec "${NAMESPACE}" ip link set "${DSTIF}" \ 46 + address "${SAVED_DSTMAC}" 47 + if [ "${BINDMODE}" == "mac" ]; then 48 + ip link set dev "${SRCIF}" down 49 + ip link set dev "${SRCIF}" address "${SAVED_SRCMAC}" 50 + # Rename device in order to trigger target resume, as initial 51 + # when device was recreated it didn't have correct mac address. 52 + ip link set dev "${SRCIF}" name "${TARGET}" 53 + fi 54 + } 55 + 56 + function trigger_deactivation() { 57 + # Start by storing mac addresses so we can be restored in reactivate 58 + SAVED_DSTMAC=$(ip netns exec "${NAMESPACE}" \ 59 + cat /sys/class/net/"$DSTIF"/address) 60 + SAVED_SRCMAC=$(mac_get "${SRCIF}") 61 + # Remove low level module 62 + rmmod netdevsim 63 + } 64 + 65 + trap cleanup EXIT 66 + 67 + # Run the test twice, with different cmdline parameters 68 + for BINDMODE in "ifname" "mac" 69 + do 70 + echo "Running with bind mode: ${BINDMODE}" >&2 71 + # Set current loglevel to KERN_INFO(6), and default to KERN_NOTICE(5) 72 + echo "6 5" > /proc/sys/kernel/printk 73 + 74 + # Create one namespace and two interfaces 75 + set_network 76 + 77 + # Create the command line for netconsole, with the configuration from 78 + # the function above 79 + CMDLINE=$(create_cmdline_str "${BINDMODE}") 80 + 81 + # The content of kmsg will be save to the following file 82 + OUTPUT_FILE="/tmp/${TARGET}-${BINDMODE}" 83 + 84 + # Load the module, with the cmdline set 85 + modprobe netconsole "${CMDLINE}" 86 + # Expose cmdline target in configfs 87 + mkdir "${NETCONS_CONFIGFS}/cmdline0" 88 + 89 + # Target should be enabled 90 + wait_target_state "cmdline0" "enabled" 91 + 92 + # Trigger deactivation by unloading netdevsim module. Target should be 93 + # disabled. 94 + trigger_deactivation 95 + wait_target_state "cmdline0" "disabled" 96 + 97 + # Trigger reactivation by loading netdevsim, recreating the network and 98 + # restoring mac addresses. Target should be re-enabled. 99 + trigger_reactivation 100 + wait_target_state "cmdline0" "enabled" 101 + 102 + # Listen for netconsole port inside the namespace and destination 103 + # interface 104 + listen_port_and_save_to "${OUTPUT_FILE}" & 105 + # Wait for socat to start and listen to the port. 106 + wait_local_port_listen "${NAMESPACE}" "${PORT}" udp 107 + # Send the message 108 + echo "${MSG}: ${TARGET}" > /dev/kmsg 109 + # Wait until socat saves the file to disk 110 + busywait "${BUSYWAIT_TIMEOUT}" test -s "${OUTPUT_FILE}" 111 + # Make sure the message was received in the dst part 112 + # and exit 113 + validate_msg "${OUTPUT_FILE}" 114 + 115 + # kill socat in case it is still running 116 + pkill_socat 117 + # Cleanup & unload the module 118 + cleanup 119 + 120 + echo "${BINDMODE} : Test passed" >&2 121 + done 122 + 123 + trap - EXIT 124 + exit "${EXIT_STATUS}"