From b5316acc461d01cf5c0f2079a31797653152de5a Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 5 May 2026 12:35:56 -0400 Subject: [PATCH 1/3] fix(ethercat): drop iptables INPUT DROP + IPv6 disable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The plugin's "jitter isolation" path inserted `iptables -I INPUT 1 -i -j DROP` and wrote 1 to /proc/sys/net/ipv6/conf// disable_ipv6 on every master start. Both predate the dedicated bus thread and were aimed at trimming softirq pressure on the EtherCAT NIC by silencing inbound IP traffic that the kernel would otherwise process. The win was real but small (a few µs of scheduling jitter relief in the 99th percentile under heavy management traffic). Cost was huge on single-NIC hardware: dropping all eth0 INPUT disconnects SSH, the editor's runtime API, and ICMP — making the Pi unreachable for the duration of the run. Pi 4 ships with one Ethernet interface, so users hit this on their first build with EtherCAT enabled. Same problem on any device whose management and EtherCAT NICs are the same physical port. Strip both. Keep the actual jitter mitigations that matter and don't touch the rest of the system: NIC interrupt coalescing (rx-usecs/tx-usecs=0 via ethtool -C), receive/transmit offloads (GRO/GSO/TSO off via ethtool -K), and SO_BUSY_POLL on the SOEM raw socket. Those are NIC-local settings that don't break management paths. The legacy-state migration code stays so users upgrading from a previous version get the leftover iptables rule and IPv6 sysfs flip rolled back automatically on first start (legacy_iptables_delete + legacy_write_ipv6_disabled in migrate_legacy_iso). After the old state file is consumed, this runtime never inserts iptables rules or flips IPv6 sysctls itself. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../plugins/native/ethercat/ethercat_config.h | 20 +- .../native/ethercat/ethercat_iface_state.c | 173 ++++++------------ .../native/ethercat/ethercat_iface_state.h | 39 ++-- 3 files changed, 84 insertions(+), 148 deletions(-) diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_config.h b/core/src/drivers/plugins/native/ethercat/ethercat_config.h index 90a13b81..6d94be90 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_config.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_config.h @@ -429,20 +429,22 @@ typedef struct { #define ECAT_AVG_EWMA_SHIFT 5 /** - * @brief Per-interface external state captured by ecat_iface_state_apply(). + * @brief Per-interface NIC tuning state captured by ecat_iface_state_apply(). * - * Combines NIC-tuning save/restore (ethtool coalescing + offloads) and - * IP-stack isolation (iptables INPUT DROP, IPv6 sysctl) into a single - * struct, embedded in each ecat_master_instance_t. See - * ethercat_iface_state.h for the apply/revert API. + * Holds the pre-EtherCAT NIC settings (ethtool coalescing + offloads) so + * `ecat_iface_state_revert()` can roll them back on graceful shutdown, + * and so the next runtime start can recover from a crash. Embedded in + * each `ecat_master_instance_t`. See ethercat_iface_state.h for the + * apply/revert API. + * + * IP-stack isolation (iptables INPUT DROP, IPv6 sysctl) used to live + * here too; it was removed because it took out the user's only + * management path on single-NIC systems (Pi 4) and the win it bought + * — a few µs of softirq pressure relief — wasn't worth the cost. */ typedef struct { char iface[ECAT_IFNAME_MAX]; - /* IP-stack isolation */ - bool ipv6_disabled_by_us; - bool iptables_added; - /* NIC tuning -- ethtool -C (coalescing) */ bool coalescing_saved; int rx_usecs; diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c index 1f0a023f..4e1552b5 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c @@ -1,15 +1,27 @@ /** * @file ethercat_iface_state.c - * @brief Implementation of ethercat_iface_state.h (consolidated NIC tuning - * + IP-stack isolation with crash-recovery persistence). + * @brief Implementation of ethercat_iface_state.h. * - * On Linux this file owns three external mechanisms: - * - ethtool -C / -K (NIC coalescing and offloads) - * - iptables -I/-D (drop IP traffic on the EtherCAT NIC) - * - sysctl IPv6 (disable_ipv6 on the NIC) + * This module owns the NIC tuning the EtherCAT bus thread depends on: + * - ethtool -C (interrupt coalescing: rx-usecs / tx-usecs = 0) + * - ethtool -K (offload aggregation: GRO / GSO / TSO off) + * + * Both are runtime-only NIC settings (not persisted across reboots by + * the OS), so the only correctness concern is making sure a graceful + * shutdown — and a *crashed* runtime's next start — both restore them + * to the values that were in effect before the master was brought up. + * That's what the `/run/runtime/ecat_iface_.state` file is for: + * we write the captured "before" values there, and the next apply call + * checks for a leftover file and rolls back before re-applying. * * On non-Linux platforms apply/revert are no-ops; the SOEM raw socket * is the only thing that interacts with the NIC there. + * + * Earlier versions of this module also disabled IPv6 and inserted an + * iptables INPUT DROP rule on the EtherCAT NIC — those took out the + * user's only management path on single-NIC systems and were removed. + * The legacy state files those versions wrote are still detected on + * apply (`migrate_legacy_iso`) and rolled back, so upgrades self-heal. */ #include "ethercat_iface_state.h" @@ -30,60 +42,11 @@ #define ECAT_IFACE_STATE_DIR "/run/runtime" #define ECAT_IFACE_STATE_FMT ECAT_IFACE_STATE_DIR "/ecat_iface_%s.state" -/* Legacy persistence files from the pre-consolidation versions. Detected - * on apply, reverted, and removed before we proceed with the unified flow. */ +/* Legacy persistence files from earlier versions. Detected on apply, + * reverted, and removed before the current flow runs. */ #define ECAT_LEGACY_NIC_FMT ECAT_IFACE_STATE_DIR "/ecat_nic_saved_%s.conf" #define ECAT_LEGACY_ISO_FMT ECAT_IFACE_STATE_DIR "/ecat_iface_iso_%s.state" -/* ------------------------------------------------------------------ */ -/* ipv6 sysctl */ -/* ------------------------------------------------------------------ */ - -static int read_ipv6_disabled(const char *iface) -{ - char path[160]; - snprintf(path, sizeof(path), - "/proc/sys/net/ipv6/conf/%s/disable_ipv6", iface); - FILE *f = fopen(path, "r"); - if (!f) return -1; - int v = -1; - if (fscanf(f, "%d", &v) != 1) v = -1; - fclose(f); - return v; -} - -static int write_ipv6_disabled(const char *iface, int value) -{ - char path[160]; - snprintf(path, sizeof(path), - "/proc/sys/net/ipv6/conf/%s/disable_ipv6", iface); - FILE *f = fopen(path, "w"); - if (!f) return -1; - int rc = (fprintf(f, "%d\n", value) > 0) ? 0 : -1; - fclose(f); - return rc; -} - -/* ------------------------------------------------------------------ */ -/* iptables */ -/* ------------------------------------------------------------------ */ - -static int iptables_delete(const char *iface) -{ - char *argv[] = { - "iptables", "-D", "INPUT", "-i", (char *)iface, "-j", "DROP", NULL - }; - return ecat_run_argv("iptables", argv, NULL, 0); -} - -static int iptables_insert(const char *iface) -{ - char *argv[] = { - "iptables", "-I", "INPUT", "1", "-i", (char *)iface, "-j", "DROP", NULL - }; - return ecat_run_argv("iptables", argv, NULL, 0); -} - /* ------------------------------------------------------------------ */ /* ethtool output parsing */ /* ------------------------------------------------------------------ */ @@ -126,7 +89,7 @@ static int parse_ethtool_bool(const char *output, const char *key, int *value) } /* ------------------------------------------------------------------ */ -/* NIC capture / apply */ +/* NIC capture / apply / restore */ /* ------------------------------------------------------------------ */ static void capture_nic_settings(ecat_iface_state_t *s, plugin_logger_t *logger) @@ -276,8 +239,6 @@ static void persist_state(const ecat_iface_state_t *s, plugin_logger_t *logger) } fprintf(fp, "iface=%s\n", s->iface); - fprintf(fp, "iptables_added=%d\n", s->iptables_added ? 1 : 0); - fprintf(fp, "ipv6_disabled_by_us=%d\n", s->ipv6_disabled_by_us ? 1 : 0); if (s->coalescing_saved) { fprintf(fp, "rx_usecs=%d\n", s->rx_usecs); fprintf(fp, "tx_usecs=%d\n", s->tx_usecs); @@ -331,11 +292,7 @@ static bool load_state(const char *iface, ecat_iface_state_t *s) *eq = '\0'; const char *key = line; const char *val = eq + 1; - if (strcmp(key, "iptables_added") == 0) { - s->iptables_added = (atoi(val) != 0); - } else if (strcmp(key, "ipv6_disabled_by_us") == 0) { - s->ipv6_disabled_by_us = (atoi(val) != 0); - } else if (strcmp(key, "rx_usecs") == 0) { + if (strcmp(key, "rx_usecs") == 0) { s->rx_usecs = atoi(val); s->coalescing_saved = true; } else if (strcmp(key, "tx_usecs") == 0) { @@ -415,9 +372,37 @@ static void migrate_legacy_nic(const char *iface, plugin_logger_t *logger) } /* - * If an iface-isolation file from the pre-consolidation version exists, - * parse it, undo iptables / ipv6, and remove the file. + * If an iface-isolation file from a previous version exists (the one + * that recorded iptables INPUT DROP and IPv6 sysctl flips), undo what + * it describes and delete it. Done as a pure migration concern: this + * runtime never inserts iptables rules nor flips IPv6 sysctls itself. + * + * Run with `iptables -t filter -D INPUT -i -j DROP` and write + * 0 back to the IPv6 disable_ipv6 sysctl. Both ops are best-effort — + * if iptables isn't present (e.g. nftables-only host) or the IPv6 + * sysfs path doesn't exist, just skip and remove the stale file so + * the warning doesn't repeat on every restart. */ +static int legacy_iptables_delete(const char *iface) +{ + char *argv[] = { + "iptables", "-D", "INPUT", "-i", (char *)iface, "-j", "DROP", NULL + }; + return ecat_run_argv("iptables", argv, NULL, 0); +} + +static int legacy_write_ipv6_disabled(const char *iface, int value) +{ + char path[160]; + snprintf(path, sizeof(path), + "/proc/sys/net/ipv6/conf/%s/disable_ipv6", iface); + FILE *f = fopen(path, "w"); + if (!f) return -1; + int rc = (fprintf(f, "%d\n", value) > 0) ? 0 : -1; + fclose(f); + return rc; +} + static void migrate_legacy_iso(const char *iface, plugin_logger_t *logger) { char path[160]; @@ -450,9 +435,9 @@ static void migrate_legacy_iso(const char *iface, plugin_logger_t *logger) fclose(fp); if (ipt) - iptables_delete(iface); + legacy_iptables_delete(iface); if (ipv6) - write_ipv6_disabled(iface, 0); + legacy_write_ipv6_disabled(iface, 0); unlink(path); } @@ -468,18 +453,12 @@ static void recover_from_crash(const char *iface, plugin_logger_t *logger) plugin_logger_warn(logger, "Found stale iface state for %s " - "(iptables=%d, ipv6=%d, coalescing_saved=%d, offloads_saved=%d) - " + "(coalescing_saved=%d, offloads_saved=%d) - " "previous process likely crashed. Reverting before re-applying.", iface, - (int)prev.iptables_added, (int)prev.ipv6_disabled_by_us, (int)prev.coalescing_saved, (int)prev.offloads_saved); - if (prev.iptables_added) - iptables_delete(iface); - if (prev.ipv6_disabled_by_us) - write_ipv6_disabled(iface, 0); restore_nic_settings(&prev, logger); - remove_state_file(iface); } @@ -498,8 +477,8 @@ void ecat_iface_state_apply(ecat_iface_state_t *state, const char *iface, if (!ecat_iface_validate(iface, ECAT_IFACE_LINUX_STRICT)) { plugin_logger_warn(logger, "iface '%s' not a valid Linux interface name -- " - "NIC tuning (ethtool) and IP-stack isolation (iptables, IPv6) " - "are disabled for this master. Jitter may be higher.", + "NIC tuning (ethtool) is disabled for this master. " + "Jitter may be higher.", iface); return; } @@ -517,33 +496,6 @@ void ecat_iface_state_apply(ecat_iface_state_t *state, const char *iface, /* Capture the live NIC settings before we change them */ capture_nic_settings(state, logger); - /* IPv6: only flip if currently enabled, so we never undo an operator - * setting that was already disabled. */ - int ipv6 = read_ipv6_disabled(iface); - if (ipv6 == 0) { - if (write_ipv6_disabled(iface, 1) == 0) { - state->ipv6_disabled_by_us = true; - plugin_logger_info(logger, - "%s: disabled IPv6 (jitter isolation)", iface); - } else { - plugin_logger_warn(logger, - "%s: could not disable IPv6", iface); - } - } else if (ipv6 == 1) { - plugin_logger_debug(logger, "%s: IPv6 already disabled", iface); - } - - /* Delete-then-insert keeps the rule unique across re-runs */ - iptables_delete(iface); - if (iptables_insert(iface) == 0) { - state->iptables_added = true; - plugin_logger_info(logger, - "%s: inserted iptables INPUT DROP (jitter isolation)", iface); - } else { - plugin_logger_warn(logger, - "%s: iptables -I INPUT failed -- isolation skipped", iface); - } - /* Apply low-latency NIC tuning */ apply_low_latency_nic(iface, logger); @@ -557,17 +509,6 @@ void ecat_iface_state_revert(ecat_iface_state_t *state, plugin_logger_t *logger) return; /* Only undo what we applied, in reverse order */ - if (state->iptables_added) { - iptables_delete(state->iface); - plugin_logger_info(logger, - "%s: removed iptables INPUT DROP", state->iface); - state->iptables_added = false; - } - if (state->ipv6_disabled_by_us) { - write_ipv6_disabled(state->iface, 0); - plugin_logger_info(logger, "%s: re-enabled IPv6", state->iface); - state->ipv6_disabled_by_us = false; - } restore_nic_settings(state, logger); state->coalescing_saved = false; state->offloads_saved = false; diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h index 8341f131..9644d23c 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h @@ -1,27 +1,20 @@ /** * @file ethercat_iface_state.h - * @brief Unified per-interface external-state manager for the EtherCAT plugin. + * @brief Per-interface NIC-tuning state manager for the EtherCAT plugin. * - * Consolidates two mechanisms that previously lived in separate places - * with the same shape: - * - * - NIC tuning save/restore (ethtool -c / -k / -C / -K). - * Originally in ethercat_master.c (~460 lines). - * - * - IP-stack isolation (iptables INPUT DROP, IPv6 sysctl flip). - * Originally in ethercat_plugin.c (~280 lines). - * - * Both shared the same crash-recovery pattern (atomic write+rename in - * /run/runtime, file-on-disk serves as a reliquia after a SIGKILL or - * OOM so the next start can roll back what was applied). This module - * folds them into a single state struct, a single persistence file per - * interface, and a single apply/revert pair. + * Saves the NIC's pre-EtherCAT settings (ethtool -c / -k coalescing + * and offloads), applies the low-latency tuning the bus thread needs + * (rx/tx-usecs=0, GRO/GSO/TSO off), and persists the captured "before" + * values to /run/runtime so a crashed runtime's next start can revert + * the system to its original state. * * Persistence file: /run/runtime/ecat_iface_.state * * Migration: legacy files (ecat_nic_saved_.conf, - * ecat_iface_iso_.state) from earlier versions are detected on - * apply, reverted, and removed before the unified flow runs. + * ecat_iface_iso_.state) from earlier versions — the latter + * recorded iptables INPUT DROP + IPv6 sysctl flips, since removed — + * are detected on apply, reverted, and removed before the current + * flow runs. This makes upgrades from any prior version self-heal. */ #ifndef ETHERCAT_IFACE_STATE_H @@ -35,15 +28,15 @@ extern "C" { #endif /** - * @brief Apply low-latency NIC tuning + IP-stack isolation. + * @brief Apply low-latency NIC tuning. * * Sequence: - * 1. Migrate legacy state files (older format) and revert anything - * they describe. + * 1. Migrate legacy state files (older formats, including the + * iptables/IPv6 isolation file written by previous versions) and + * revert anything they describe. * 2. Recover from the unified state file if a previous run crashed. - * 3. Capture current NIC settings. - * 4. Apply low-latency settings (rx/tx usecs = 0, GRO/GSO/TSO off, - * iptables INPUT DROP, disable_ipv6 = 1). + * 3. Capture current NIC settings (ethtool -c / -k). + * 4. Apply low-latency settings (rx/tx-usecs = 0, GRO/GSO/TSO off). * 5. Persist the captured "before" values to /run/runtime so a crash * lets the next start undo what we just applied. * From 30c4b4e97c465fee4a3672acd0b7ffdfae8aaf56 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 5 May 2026 12:42:41 -0400 Subject: [PATCH 2/3] chore(ethercat): drop legacy iptables/IPv6 migration code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The iptables INPUT DROP + IPv6 disable was removed before this code shipped, so there's no installed-in-the-wild legacy state to clean up. Strip the migration shim: - migrate_legacy_iso() and its two helpers (legacy_iptables_delete, legacy_write_ipv6_disabled) - ECAT_LEGACY_ISO_FMT macro - the migrate_legacy_iso() call from ecat_iface_state_apply() Also clean up stale references in adjacent files' comments — proc helper banner, master.c socket-tuning header, config.c multi-master single-owner note, header banners on iface_state.{c,h}. ethercat_iface_state still keeps migrate_legacy_nic for users upgrading from the pre-consolidation NIC-tuning file format (ecat_nic_saved_.conf). That format DID ship. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../plugins/native/ethercat/ethercat_config.c | 6 +- .../plugins/native/ethercat/ethercat_config.h | 13 +-- .../native/ethercat/ethercat_iface_state.c | 83 +------------------ .../native/ethercat/ethercat_iface_state.h | 13 ++- .../plugins/native/ethercat/ethercat_master.c | 6 +- .../plugins/native/ethercat/ethercat_proc.h | 8 +- 6 files changed, 22 insertions(+), 107 deletions(-) diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_config.c b/core/src/drivers/plugins/native/ethercat/ethercat_config.c index 192b298d..42850b05 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_config.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_config.c @@ -909,9 +909,9 @@ int ecat_config_parse_all(const char *config_path, cJSON_Delete(root); /* Refuse configs where two masters share the same network interface. - * Per-iface external state (NIC tuning + iptables + ipv6) in - * ethercat_iface_state.c is single-owner; two masters on the same iface - * produce corrupted persistence on crash recovery. */ + * Per-iface NIC tuning state in ethercat_iface_state.c is + * single-owner; two masters on the same iface produce corrupted + * persistence on crash recovery. */ for (int i = 0; i < count; i++) { for (int j = i + 1; j < count; j++) { if (strcmp(instances[i].config.master.interface, diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_config.h b/core/src/drivers/plugins/native/ethercat/ethercat_config.h index 6d94be90..6e39cd4b 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_config.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_config.h @@ -290,10 +290,10 @@ void ecat_config_set_logger(plugin_logger_t *logger); /** * @brief Validation mode for interface names. * - * Different callers need different rules: NIC tuning / iptables paths must - * receive Linux-only names safe for /proc and external binaries; the scan - * and test commands accept any name the underlying socket layer accepts, - * including Windows NPF device paths like "\Device\NPF_{GUID}". + * Different callers need different rules: NIC tuning paths must receive + * Linux-only names safe for /proc and external binaries (ethtool); the + * scan and test commands accept any name the underlying socket layer + * accepts, including Windows NPF device paths like "\Device\NPF_{GUID}". */ typedef enum { ECAT_IFACE_LINUX_STRICT, /* alfanum + '_' '-', starts alpha, len 1..15 */ @@ -436,11 +436,6 @@ typedef struct { * and so the next runtime start can recover from a crash. Embedded in * each `ecat_master_instance_t`. See ethercat_iface_state.h for the * apply/revert API. - * - * IP-stack isolation (iptables INPUT DROP, IPv6 sysctl) used to live - * here too; it was removed because it took out the user's only - * management path on single-NIC systems (Pi 4) and the win it bought - * — a few µs of softirq pressure relief — wasn't worth the cost. */ typedef struct { char iface[ECAT_IFNAME_MAX]; diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c index 4e1552b5..0ac6d1b1 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c @@ -16,12 +16,6 @@ * * On non-Linux platforms apply/revert are no-ops; the SOEM raw socket * is the only thing that interacts with the NIC there. - * - * Earlier versions of this module also disabled IPv6 and inserted an - * iptables INPUT DROP rule on the EtherCAT NIC — those took out the - * user's only management path on single-NIC systems and were removed. - * The legacy state files those versions wrote are still detected on - * apply (`migrate_legacy_iso`) and rolled back, so upgrades self-heal. */ #include "ethercat_iface_state.h" @@ -42,10 +36,10 @@ #define ECAT_IFACE_STATE_DIR "/run/runtime" #define ECAT_IFACE_STATE_FMT ECAT_IFACE_STATE_DIR "/ecat_iface_%s.state" -/* Legacy persistence files from earlier versions. Detected on apply, - * reverted, and removed before the current flow runs. */ +/* Legacy NIC-tuning state file from the pre-consolidation version of + * this module. Detected on apply, reverted, and removed before the + * current flow runs. */ #define ECAT_LEGACY_NIC_FMT ECAT_IFACE_STATE_DIR "/ecat_nic_saved_%s.conf" -#define ECAT_LEGACY_ISO_FMT ECAT_IFACE_STATE_DIR "/ecat_iface_iso_%s.state" /* ------------------------------------------------------------------ */ /* ethtool output parsing */ @@ -371,76 +365,6 @@ static void migrate_legacy_nic(const char *iface, plugin_logger_t *logger) unlink(path); } -/* - * If an iface-isolation file from a previous version exists (the one - * that recorded iptables INPUT DROP and IPv6 sysctl flips), undo what - * it describes and delete it. Done as a pure migration concern: this - * runtime never inserts iptables rules nor flips IPv6 sysctls itself. - * - * Run with `iptables -t filter -D INPUT -i -j DROP` and write - * 0 back to the IPv6 disable_ipv6 sysctl. Both ops are best-effort — - * if iptables isn't present (e.g. nftables-only host) or the IPv6 - * sysfs path doesn't exist, just skip and remove the stale file so - * the warning doesn't repeat on every restart. - */ -static int legacy_iptables_delete(const char *iface) -{ - char *argv[] = { - "iptables", "-D", "INPUT", "-i", (char *)iface, "-j", "DROP", NULL - }; - return ecat_run_argv("iptables", argv, NULL, 0); -} - -static int legacy_write_ipv6_disabled(const char *iface, int value) -{ - char path[160]; - snprintf(path, sizeof(path), - "/proc/sys/net/ipv6/conf/%s/disable_ipv6", iface); - FILE *f = fopen(path, "w"); - if (!f) return -1; - int rc = (fprintf(f, "%d\n", value) > 0) ? 0 : -1; - fclose(f); - return rc; -} - -static void migrate_legacy_iso(const char *iface, plugin_logger_t *logger) -{ - char path[160]; - snprintf(path, sizeof(path), ECAT_LEGACY_ISO_FMT, iface); - FILE *fp = fopen(path, "r"); - if (!fp) - return; - - plugin_logger_warn(logger, - "Found legacy iface-isolation file %s - reverting and migrating", path); - - bool ipt = false, ipv6 = false; - char line[128]; - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') - line[len - 1] = '\0'; - char *eq = strchr(line, '='); - if (!eq) - continue; - *eq = '\0'; - const char *key = line; - const char *val = eq + 1; - if (strcmp(key, "iptables_added") == 0) { - ipt = (atoi(val) != 0); - } else if (strcmp(key, "ipv6_disabled_by_us") == 0) { - ipv6 = (atoi(val) != 0); - } - } - fclose(fp); - - if (ipt) - legacy_iptables_delete(iface); - if (ipv6) - legacy_write_ipv6_disabled(iface, 0); - unlink(path); -} - /* ------------------------------------------------------------------ */ /* Crash recovery from the unified state file */ /* ------------------------------------------------------------------ */ @@ -488,7 +412,6 @@ void ecat_iface_state_apply(ecat_iface_state_t *state, const char *iface, /* Migrate any leftover state from prior versions (best-effort) */ migrate_legacy_nic(iface, logger); - migrate_legacy_iso(iface, logger); /* Recover from a crash of the current-format file */ recover_from_crash(iface, logger); diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h index 9644d23c..65d45b8c 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h @@ -10,11 +10,9 @@ * * Persistence file: /run/runtime/ecat_iface_.state * - * Migration: legacy files (ecat_nic_saved_.conf, - * ecat_iface_iso_.state) from earlier versions — the latter - * recorded iptables INPUT DROP + IPv6 sysctl flips, since removed — - * are detected on apply, reverted, and removed before the current - * flow runs. This makes upgrades from any prior version self-heal. + * Migration: legacy NIC state file (ecat_nic_saved_.conf) from + * the pre-consolidation version of this module is detected on apply, + * reverted, and removed before the current flow runs. */ #ifndef ETHERCAT_IFACE_STATE_H @@ -31,9 +29,8 @@ extern "C" { * @brief Apply low-latency NIC tuning. * * Sequence: - * 1. Migrate legacy state files (older formats, including the - * iptables/IPv6 isolation file written by previous versions) and - * revert anything they describe. + * 1. Migrate the legacy NIC state file (older format) and revert + * anything it describes. * 2. Recover from the unified state file if a previous run crashed. * 3. Capture current NIC settings (ethtool -c / -k). * 4. Apply low-latency settings (rx/tx-usecs = 0, GRO/GSO/TSO off). diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_master.c b/core/src/drivers/plugins/native/ethercat/ethercat_master.c index 29122b50..e3a64580 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_master.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_master.c @@ -20,9 +20,9 @@ #include #include -/* Low-latency socket option for the SOEM raw socket (Linux only). All - * NIC tuning (ethtool coalescing/offloads) and IP-stack isolation - * (iptables, IPv6 sysctl) lives in ethercat_iface_state.{c,h}. */ +/* Low-latency socket option for the SOEM raw socket (Linux only). + * Per-interface NIC tuning (ethtool coalescing/offloads) lives in + * ethercat_iface_state.{c,h}. */ #if !defined(__CYGWIN__) && !defined(_WIN32) #include #define ECAT_BUSY_POLL_US 50 diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_proc.h b/core/src/drivers/plugins/native/ethercat/ethercat_proc.h index 8356fb2b..c584ce22 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_proc.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_proc.h @@ -2,10 +2,10 @@ * @file ethercat_proc.h * @brief Process-spawn helpers for the EtherCAT plugin. * - * Wraps fork+execvp so the plugin can invoke external binaries (ethtool, - * iptables) without going through a shell. Argv elements are passed - * verbatim to execvp, so user-controlled strings (e.g. the configured - * NIC interface name) cannot be reinterpreted as shell metacharacters. + * Wraps fork+execvp so the plugin can invoke external binaries (ethtool) + * without going through a shell. Argv elements are passed verbatim to + * execvp, so user-controlled strings (e.g. the configured NIC interface + * name) cannot be reinterpreted as shell metacharacters. */ #ifndef ETHERCAT_PROC_H From ef88c481ab7b2e017297890f0a2c3da942ee50c9 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 5 May 2026 14:20:01 -0400 Subject: [PATCH 3/3] =?UTF-8?q?chore(ethercat):=20drop=20migrate=5Flegacy?= =?UTF-8?q?=5Fnic=20=E2=80=94=20legacy=20file=20never=20shipped?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit migrate_legacy_nic was reading /run/runtime/ecat_nic_saved_.conf, the file format the pre-consolidation NIC-tuning code wrote (introduced in b1d28fa, only ever included in v4.1.0-rc.1, never reached main). No installed runtime ever produced that file, so the migration is dead code — same situation as the iptables auto-restore we already removed. Removes the migrate_legacy_nic function (55 lines duplicating load_state's parser), the ECAT_LEGACY_NIC_FMT define, the call site in ecat_iface_state_apply, and the migration note in the header. recover_from_crash + load_state remain unchanged: they handle the current-format file (/run/runtime/ecat_iface_.state) and that path is real — it's what persist_state writes. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../native/ethercat/ethercat_iface_state.c | 66 ------------------- .../native/ethercat/ethercat_iface_state.h | 4 -- 2 files changed, 70 deletions(-) diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c index 0ac6d1b1..393937ce 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c @@ -36,11 +36,6 @@ #define ECAT_IFACE_STATE_DIR "/run/runtime" #define ECAT_IFACE_STATE_FMT ECAT_IFACE_STATE_DIR "/ecat_iface_%s.state" -/* Legacy NIC-tuning state file from the pre-consolidation version of - * this module. Detected on apply, reverted, and removed before the - * current flow runs. */ -#define ECAT_LEGACY_NIC_FMT ECAT_IFACE_STATE_DIR "/ecat_nic_saved_%s.conf" - /* ------------------------------------------------------------------ */ /* ethtool output parsing */ /* ------------------------------------------------------------------ */ @@ -307,64 +302,6 @@ static bool load_state(const char *iface, ecat_iface_state_t *s) return true; } -/* ------------------------------------------------------------------ */ -/* Legacy file migration */ -/* ------------------------------------------------------------------ */ - -/* - * If a NIC settings file from the pre-consolidation version exists, - * parse it, restore the NIC, and remove the file. - */ -static void migrate_legacy_nic(const char *iface, plugin_logger_t *logger) -{ - char path[160]; - snprintf(path, sizeof(path), ECAT_LEGACY_NIC_FMT, iface); - FILE *fp = fopen(path, "r"); - if (!fp) - return; - - plugin_logger_warn(logger, - "Found legacy NIC state file %s - reverting and migrating", path); - - ecat_iface_state_t legacy; - memset(&legacy, 0, sizeof(legacy)); - strncpy(legacy.iface, iface, sizeof(legacy.iface) - 1); - legacy.iface[sizeof(legacy.iface) - 1] = '\0'; - - char line[128]; - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') - line[len - 1] = '\0'; - char *eq = strchr(line, '='); - if (!eq) - continue; - *eq = '\0'; - const char *key = line; - const char *val = eq + 1; - if (strcmp(key, "rx_usecs") == 0) { - legacy.rx_usecs = atoi(val); - legacy.coalescing_saved = true; - } else if (strcmp(key, "tx_usecs") == 0) { - legacy.tx_usecs = atoi(val); - legacy.coalescing_saved = true; - } else if (strcmp(key, "gro") == 0) { - legacy.gro = (strcmp(val, "on") == 0); - legacy.offloads_saved = true; - } else if (strcmp(key, "gso") == 0) { - legacy.gso = (strcmp(val, "on") == 0); - legacy.offloads_saved = true; - } else if (strcmp(key, "tso") == 0) { - legacy.tso = (strcmp(val, "on") == 0); - legacy.offloads_saved = true; - } - } - fclose(fp); - - restore_nic_settings(&legacy, logger); - unlink(path); -} - /* ------------------------------------------------------------------ */ /* Crash recovery from the unified state file */ /* ------------------------------------------------------------------ */ @@ -410,9 +347,6 @@ void ecat_iface_state_apply(ecat_iface_state_t *state, const char *iface, strncpy(state->iface, iface, sizeof(state->iface) - 1); state->iface[sizeof(state->iface) - 1] = '\0'; - /* Migrate any leftover state from prior versions (best-effort) */ - migrate_legacy_nic(iface, logger); - /* Recover from a crash of the current-format file */ recover_from_crash(iface, logger); diff --git a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h index 65d45b8c..4a2cdcfe 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.h @@ -9,10 +9,6 @@ * the system to its original state. * * Persistence file: /run/runtime/ecat_iface_.state - * - * Migration: legacy NIC state file (ecat_nic_saved_.conf) from - * the pre-consolidation version of this module is detected on apply, - * reverted, and removed before the current flow runs. */ #ifndef ETHERCAT_IFACE_STATE_H