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 90a13b81..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 */ @@ -429,20 +429,17 @@ 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. */ 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..393937ce 100644 --- a/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c +++ b/core/src/drivers/plugins/native/ethercat/ethercat_iface_state.c @@ -1,12 +1,18 @@ /** * @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. @@ -30,60 +36,6 @@ #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. */ -#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 +78,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 +228,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 +281,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) { @@ -356,106 +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); -} - -/* - * If an iface-isolation file from the pre-consolidation version exists, - * parse it, undo iptables / ipv6, and remove the file. - */ -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) - iptables_delete(iface); - if (ipv6) - write_ipv6_disabled(iface, 0); - unlink(path); -} - /* ------------------------------------------------------------------ */ /* Crash recovery from the unified state file */ /* ------------------------------------------------------------------ */ @@ -468,18 +314,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 +338,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; } @@ -507,43 +347,12 @@ 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); - migrate_legacy_iso(iface, logger); - /* Recover from a crash of the current-format file */ recover_from_crash(iface, logger); /* 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 +366,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..4a2cdcfe 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,14 @@ /** * @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. */ #ifndef ETHERCAT_IFACE_STATE_H @@ -35,15 +22,14 @@ 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 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. - * 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. * 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