diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg index 0b89a3f67..35bc99bb4 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg @@ -113,6 +113,16 @@ {"id": "vendor-class", "value": "Banana Pi BPI-R3"} ] } + }, + "ietf-ip:ipv6": { + "infix-dhcpv6-client:dhcp": { + "option": [ + {"id": "ntp-server"}, + {"id": "client-fqdn"}, + {"id": "domain-search"}, + {"id": "dns-server"} + ] + } } }, { @@ -284,6 +294,7 @@ "policy": [ { "name": "lan-to-wan", + "action": "accept", "ingress": [ "lan" ], diff --git a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg index a812d4210..caa6e87bf 100644 --- a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg +++ b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg @@ -72,6 +72,16 @@ {"id": "vendor-class", "value": "NanoPi R2S"} ] } + }, + "ietf-ip:ipv6": { + "infix-dhcpv6-client:dhcp": { + "option": [ + {"id": "ntp-server"}, + {"id": "client-fqdn"}, + {"id": "domain-search"}, + {"id": "dns-server"} + ] + } } } ] @@ -235,6 +245,7 @@ "policy": [ { "name": "lan-to-wan", + "action": "accept", "ingress": [ "lan" ], diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 463f03976..eba5b502a 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -68,6 +68,9 @@ All notable changes to the project are documented in this file. - Add support for property-based filtering with operators (contains, isequal, startswith, regex, ereregex) on message properties (msg, msgid, programname, hostname, source, data), with optional case-insensitive and negate modifiers +- Update factory configuration for BPi-R3 and NanoPi R2S boards to enable + DHCPv6 client on WAN interface and allow traffic forwarding from LAN to WAN + zone in the firewall (this is what most users expect) ### Fixes @@ -80,6 +83,12 @@ All notable changes to the project are documented in this file. - Fix #1255: serious regression in boot time, introduced in v25.10, delays the boot step "Mounting filesystems ...", from 30 seconds up to five minutes! - Fix broken intra-document links in container and tunnel documentation +- Fix `show dhcp-server` command crashing with invalid timestamp format. + DHCP lease expiry timestamps had double timezone suffix causing libyang + validation errors +- Fix `show dhcp-server` output alignment. The EXPIRES column was misaligned + when CLIENT ID field was empty, and CLIENT ID column was too narrow for + typical 20-character client IDs [latest-boot]: https://github.com/kernelkit/infix/releases/latest-boot [bpi-r3-emmc-documentation]: https://github.com/kernelkit/infix/blob/main/board/aarch64/bananapi-bpi-r3/README.md diff --git a/src/statd/python/cli_pretty/cli_pretty.py b/src/statd/python/cli_pretty/cli_pretty.py index ff617c950..0c6d3e694 100755 --- a/src/statd/python/cli_pretty/cli_pretty.py +++ b/src/statd/python/cli_pretty/cli_pretty.py @@ -154,7 +154,7 @@ class PadDhcpServer: ip = 17 mac = 19 host = 21 - cid = 19 + cid = 22 exp = 10 @@ -652,7 +652,20 @@ def get_formatted_value(self): else: return f"{self.value} W" + # Handle PWM fan sensors (reported as "other" type with milli scale) + # PWM duty cycle is reported as percentage (0-100) + elif self.value_type == 'other' and self.value_scale == 'milli': + # Check if this is likely a PWM sensor based on description or name + name_lower = self.name.lower() + desc_lower = (self.description or "").lower() + if 'pwm' in desc_lower or 'fan' in name_lower or 'fan' in desc_lower: + percent = self.value / 1000.0 + return f"{percent:.1f}%" + # Fall through for other "other" type sensors + # For unknown sensor types, show raw value + if self.value_type in ['other', 'unknown']: + return f"{self.value}" else: return f"{self.value} {self.value_type}" @@ -720,11 +733,11 @@ def __init__(self, data): now = datetime.now(timezone.utc) for lease in get_json_data([], self.data, 'leases', 'lease'): if lease["expires"] == "never": - exp = " never" + exp = "never" else: dt = datetime.strptime(lease['expires'], '%Y-%m-%dT%H:%M:%S%z') seconds = int((dt - now).total_seconds()) - exp = f" {self.format_duration(seconds)}" + exp = self.format_duration(seconds) self.leases.append({ "ip": lease["address"], "mac": lease["phys-address"], @@ -775,7 +788,7 @@ def print(self): row += f"{mac:<{PadDhcpServer.mac}}" row += f"{host:<{PadDhcpServer.host}}" row += f"{cid:<{PadDhcpServer.cid}}" - row += f"{exp:>{PadDhcpServer.exp - 1}}" + row += f"{exp:>{PadDhcpServer.exp}}" print(row) def print_stats(self): diff --git a/src/statd/python/yanger/ietf_hardware.py b/src/statd/python/yanger/ietf_hardware.py index 3535cbc63..876ab863d 100644 --- a/src/statd/python/yanger/ietf_hardware.py +++ b/src/statd/python/yanger/ietf_hardware.py @@ -297,7 +297,7 @@ def create_sensor(sensor_name, value, value_type, value_scale, label=None): except (FileNotFoundError, ValueError, IOError): continue - # Fan sensors + # Fan sensors (RPM from tachometer) for fan_file in glob.glob(os.path.join(hwmon_path, "fan*_input")): try: sensor_num = os.path.basename(fan_file).split('_')[0].replace('fan', '') @@ -314,6 +314,36 @@ def create_sensor(sensor_name, value, value_type, value_scale, label=None): except (FileNotFoundError, ValueError, IOError): continue + # PWM fan sensors (duty cycle percentage) + # Only add if no fan*_input exists for this device (avoid duplicates) + has_rpm_sensor = bool(glob.glob(os.path.join(hwmon_path, "fan*_input"))) + if not has_rpm_sensor: + for pwm_file in glob.glob(os.path.join(hwmon_path, "pwm[0-9]*")): + # Skip pwm*_enable, pwm*_mode, etc. - only process pwm1, pwm2, etc. + pwm_basename = os.path.basename(pwm_file) + if not pwm_basename.replace('pwm', '').isdigit(): + continue + try: + sensor_num = pwm_basename.replace('pwm', '') + pwm_raw = int(HOST.read(pwm_file).strip()) + # Convert PWM duty cycle (0-255) to percentage (0-100) + # Note: Some devices are inverted (255=off, 0=max), but we report as-is + # The value represents duty cycle, not necessarily fan speed + # Use "other" value-type since PWM duty cycle isn't a standard IETF type + value = int((pwm_raw / 255.0) * 100 * 1000) # Convert to milli-percent (0-100000) + label_file = os.path.join(hwmon_path, f"pwm{sensor_num}_label") + raw_label = None + if HOST.exists(label_file): + raw_label = HOST.read(label_file).strip() + label = normalize_sensor_name(raw_label) + sensor_name = f"{base_name}-{label}" + else: + sensor_name = base_name if sensor_num == '1' else f"{base_name}{sensor_num}" + # Use "PWM Fan" as description so it displays nicely in show hardware + add_sensor(base_name, create_sensor(sensor_name, value, "other", "milli", raw_label or "PWM Fan")) + except (FileNotFoundError, ValueError, IOError): + continue + # Voltage sensors for voltage_file in glob.glob(os.path.join(hwmon_path, "in*_input")): try: diff --git a/src/statd/python/yanger/infix_dhcp_server.py b/src/statd/python/yanger/infix_dhcp_server.py index 3c90d6d4c..08ed4cad5 100755 --- a/src/statd/python/yanger/infix_dhcp_server.py +++ b/src/statd/python/yanger/infix_dhcp_server.py @@ -23,7 +23,7 @@ def leases(leases_file): else: dt = datetime.fromtimestamp(int(tokens[0]), tz=timezone.utc) - expires = dt.isoformat() + "+00:00" + expires = dt.isoformat() row = { "expires": expires,