# Netmode Implementation Guide ## Table of Contents 1. [Overview](#overview) 2. [Architecture](#architecture) 3. [Directory Structure](#directory-structure) 4. [Configuration System](#configuration-system) 5. [Mode Implementation](#mode-implementation) 6. [Creating a New Network Mode](#creating-a-new-network-mode) 7. [Environment Variables](#environment-variables) 8. [Hooks and Scripts](#hooks-and-scripts) 9. [Data Model Integration](#data-model-integration) 10. [Testing and Validation](#testing-and-validation) --- ## Overview The netmode package provides a flexible mechanism for switching between different WAN network configurations on OpenWrt/iopsys systems. It enables easy transitions between routing modes (DHCP, PPPoE, Static IP) and bridged mode without manual configuration changes. ### Key Features - Dynamic WAN mode switching with reboot - Support for routed and bridged modes - UCI-based configuration - TR-069/USP data model integration via bbfdm - Extensible architecture for custom modes - Environment variable-based parameter passing - Pre/post mode switch hooks ### Version Information - Package Version: 1.1.11 - License: GPL-2.0-only --- ## Architecture ### Core Components ``` ┌─────────────────────────────────────────────────────────────┐ │ Netmode System │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ │ │ │ UCI Config │───▶│ Init Service │───▶│ Mode Scripts │ │ │ │ /etc/config │ │ (START=11) │ │ /etc/netmodes │ │ │ │ /netmode │ │ │ │ / │ │ │ └─────────────┘ └──────────────┘ └───────────────┘ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────┐ │ │ │ └────────▶│ Env Var Manager │◀───────────┘ │ │ │ (NETMODE_*) │ │ │ └──────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ Hook Execution │ │ │ │ pre/post/lib │ │ │ └──────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ │ │ │ UCI Reconfig │ │ │ │ network/dhcp/ │ │ │ │ firewall/etc │ │ │ └──────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Execution Flow 1. **Trigger**: System boot or `service netmode restart` 2. **Configuration Load**: Read UCI netmode config 3. **Mode Detection**: Get mode from `netmode.global.mode` or `fw_printenv` 4. **Change Detection**: Compare with last saved mode in `/etc/netmodes/.last_mode` 5. **Environment Setup**: Configure `NETMODE_*` environment variables 6. **Pre-hooks**: Execute `/lib/netmode/pre/*` scripts 7. **UCI Copy**: Copy mode-specific UCI files from `/etc/netmodes//uci/` 8. **Generic Scripts**: Execute `/lib/netmode/*` scripts 9. **Mode Scripts**: Execute `/etc/netmodes//scripts/*` in order 10. **Last Mode Update**: Save current mode to `.last_mode` 11. **Post-hooks**: Execute `/lib/netmode/post/*` scripts 12. **Cleanup**: Unset environment variables and clear argument values --- ## Directory Structure ``` netmode/ ├── Makefile # OpenWrt package definition ├── bbfdm_service.json # BBF data model service registration ├── IMPLEMENTATION_GUIDE.md # This document ├── DEVELOPER_GUIDE.md # Developer documentation ├── USER_GUIDE.md # User documentation └── files/ ├── etc/ │ ├── config/ │ │ └── netmode # UCI configuration file │ ├── init.d/ │ │ └── netmode # Procd init script (START=11) │ ├── uci-defaults/ │ │ ├── 40_netmode_populated_supported_modes │ │ ├── 40_netmode_set_default_netmode │ │ └── 62-netmode.l2mode │ └── netmodes/ │ ├── supported_modes.json # Mode definitions for import │ ├── .last_mode # Runtime: last applied mode │ ├── routed-dhcp/ │ │ ├── scripts/ │ │ │ └── 10-routed-dhcp │ │ └── uci/ # Optional: pre-configured UCI files │ ├── routed-pppoe/ │ │ └── scripts/ │ │ └── 10-routed-pppoe │ ├── routed-static/ │ │ └── scripts/ │ │ └── 10-routed-static │ └── bridged/ │ └── scripts/ │ └── 10-bridged └── lib/ ├── upgrade/ │ └── keep.d/ │ └── netmode # Files to keep during sysupgrade └── netmode/ ├── pre/ # Pre-mode-switch hooks ├── post/ # Post-mode-switch hooks │ └── datamodel_init.sh # Reboot after mode switch └── [default]/ # Generic mode-switch hooks ``` --- ## Configuration System ### UCI Configuration Format **File**: `/etc/config/netmode` ```uci config netmode 'global' option enabled '1' option mode 'routed-dhcp' config supported_modes option name 'routed-dhcp' option description 'DHCP' config supported_args option dm_parent '@supported_modes[0]' option name 'vlanid' option description 'VLAN ID' option required '0' option type 'integer' option value '' config supported_args option dm_parent '@supported_modes[0]' option name 'dns_servers' option description 'DNS Servers' option required '0' option type 'string' option value '' config supported_modes option name 'routed-pppoe' option description 'PPPoE' config supported_args option dm_parent '@supported_modes[1]' option name 'username' option description 'PPPoE Username' option required '1' option type 'string' option value '' config supported_args option dm_parent '@supported_modes[1]' option name 'password' option description 'PPPoE Password' option required '1' option type 'string' option value '' ``` ### Supported Modes JSON Schema **File**: `/etc/netmodes/supported_modes.json` ```json { "supported_modes": [ { "name": "routed-dhcp", "description": "DHCP", "supported_args": [ { "name": "vlanid", "description": "VLAN ID", "required": false, "type": "integer" }, { "name": "dns_servers", "description": "DNS Servers", "required": false, "type": "string" } ] } ] } ``` ### Configuration Fields | Field | Type | Description | |-------|------|-------------| | `name` | string | Unique identifier for the mode (must match directory name) | | `description` | string | Human-readable mode description | | `required` | boolean | If true, argument value must be provided | | `type` | string | Data type: `integer`, `string`, `boolean` | | `value` | string | Runtime value (set via TR-069/CLI) | | `dm_parent` | reference | Links argument to parent mode section | --- ## Mode Implementation ### Standard Modes #### 1. Routed DHCP Mode - **Name**: `routed-dhcp` - **Description**: Router with DHCP WAN - **Configuration**: - LAN: Static IP (192.168.1.1/24) - WAN: DHCP client - WAN6: DHCPv6 client - Firewall: Enabled - DHCP Server: Enabled on LAN - Multicast: L3 proxy mode #### 2. Routed PPPoE Mode - **Name**: `routed-pppoe` - **Description**: Router with PPPoE WAN - **Required Arguments**: `username`, `password` - **Optional Arguments**: `vlanid`, `mtu`, `dns_servers` - **Configuration**: - LAN: Static IP (192.168.1.1/24) - WAN: PPPoE - Firewall: Enabled - DHCP Server: Enabled on LAN - Multicast: L3 proxy mode #### 3. Routed Static Mode - **Name**: `routed-static` - **Description**: Router with Static IP WAN - **Required Arguments**: `ipaddr`, `netmask`, `gateway` - **Optional Arguments**: `vlanid`, `dns_servers` - **Configuration**: - LAN: Static IP (192.168.1.1/24) - WAN: Static IP - Firewall: Enabled - DHCP Server: Enabled on LAN - Multicast: L3 proxy mode #### 4. Bridged Mode - **Name**: `bridged` - **Description**: L2 Bridge Mode - **Configuration**: - LAN: DHCP client (bridged to WAN) - WAN: Deleted (ports added to br-lan) - Firewall: Disabled - DHCP Server: Disabled - Multicast: L2 snooping mode --- ## Creating a New Network Mode ### Step-by-Step Guide #### Step 1: Create Mode Directory Structure ```bash mkdir -p /etc/netmodes/my-custom-mode/scripts mkdir -p /etc/netmodes/my-custom-mode/uci ``` #### Step 2: Define Mode in supported_modes.json Add to `/etc/netmodes/supported_modes.json`: ```json { "supported_modes": [ { "name": "my-custom-mode", "description": "My Custom WAN Mode", "supported_args": [ { "name": "custom_param", "description": "Custom Parameter", "required": true, "type": "string" } ] } ] } ``` #### Step 3: Import Mode to UCI Run the uci-defaults script to populate UCI: ```bash sh /etc/uci-defaults/40_netmode_populated_supported_modes ``` Or manually add to `/etc/config/netmode`: ```uci config supported_modes option name 'my-custom-mode' option description 'My Custom WAN Mode' config supported_args option dm_parent '@supported_modes[N]' # Replace N with index option name 'custom_param' option description 'Custom Parameter' option required '1' option type 'string' option value '' ``` #### Step 4: Create Mode Script **File**: `/etc/netmodes/my-custom-mode/scripts/10-my-custom-mode` ```bash #!/bin/sh . /lib/functions.sh . /usr/share/libubox/jshn.sh source "/etc/device_info" logger -s -p user.info -t "netmode" "Applying custom mode configuration" # Access environment variables custom_value="$NETMODE_custom_param" # Get WAN device from board configuration wandev="$(uci -q get network.WAN.ifname)" # Configure network uci -q set network.lan=interface uci -q set network.lan.device='br-lan' uci -q set network.lan.proto='static' uci -q set network.lan.ipaddr='192.168.1.1' uci -q set network.lan.netmask='255.255.255.0' uci -q set network.wan=interface uci -q set network.wan.device="$wandev" uci -q set network.wan.proto='dhcp' # Add your custom configuration here uci -q commit network # Configure other subsystems uci -q set dhcp.lan.ignore=0 uci -q commit dhcp uci -q set firewall.globals.enabled="1" uci -q commit firewall logger -s -p user.info -t "netmode" "Custom mode configuration complete" ``` Make it executable: ```bash chmod +x /etc/netmodes/my-custom-mode/scripts/10-my-custom-mode ``` #### Step 5: (Optional) Pre-configure UCI Files If you have complex UCI configurations, place them in: ```bash /etc/netmodes/my-custom-mode/uci/network /etc/netmodes/my-custom-mode/uci/dhcp /etc/netmodes/my-custom-mode/uci/firewall ``` These will be copied to `/etc/config/` before mode scripts execute. #### Step 6: Test the Mode ```bash # Set the mode uci set netmode.global.mode='my-custom-mode' # Set required arguments (if any) uci set netmode.@supported_args[N].value='test-value' uci commit netmode # Restart netmode service service netmode restart # Check logs logread | grep netmode ``` --- ## Environment Variables ### Variable Naming Convention All mode arguments are exported as environment variables with the prefix `NETMODE_`: ```bash NETMODE_="" ``` ### Examples For PPPoE mode: ```bash NETMODE_username="user@isp.com" NETMODE_password="secret123" NETMODE_vlanid="100" NETMODE_mtu="1492" NETMODE_dns_servers="8.8.8.8,8.8.4.4" ``` ### Accessing in Scripts ```bash #!/bin/sh # Direct access echo "Username: $NETMODE_username" echo "VLAN ID: $NETMODE_vlanid" # With validation if [ -n "$NETMODE_vlanid" ] && [ "$NETMODE_vlanid" -ge 1 ]; then # Configure VLAN uci set network.wan_vlan.vid="$NETMODE_vlanid" fi # CSV parsing for lists if [ -n "$NETMODE_dns_servers" ]; then dns_servers="$(echo $NETMODE_dns_servers | tr ',' ' ')" for server in $dns_servers; do uci add_list network.wan.dns="$server" done fi ``` ### Variable Lifecycle 1. **Set**: Before pre-hooks (if mode is being switched) 2. **Available**: During pre-hooks, generic scripts, and mode scripts 3. **Cleared**: After post-hooks via `cleanup_env_vars()` ### Security Considerations - Password values marked as "Secure" in data model - Environment variables are process-local - Cleared after mode application - UCI values are cleared after use (`cleanup_arg_values()`) --- ## Hooks and Scripts ### Hook Execution Order ``` 1. /lib/netmode/pre/* (Pre-mode-switch hooks) ↓ 2. Copy /etc/netmodes//uci/* → /etc/config/ ↓ 3. /lib/netmode/* (Generic mode-switch scripts) ↓ 4. /etc/netmodes//scripts/* (Mode-specific scripts) ↓ 5. Save .last_mode ↓ 6. /lib/netmode/post/* (Post-mode-switch hooks) ↓ 7. cleanup_env_vars() ``` ### Script Naming Convention Scripts are executed in **lexicographical order**. Use numeric prefixes: ``` 10-first-script 20-second-script 30-third-script ``` ### Pre-hooks (`/lib/netmode/pre/`) **Purpose**: Prepare system before mode switch **Example Use Cases**: - Backup current configuration - Stop conflicting services - Validate prerequisites **Example**: ```bash #!/bin/sh # /lib/netmode/pre/10-backup logger -t "netmode" "Backing up current network config" cp /etc/config/network /tmp/network.backup ``` ### Post-hooks (`/lib/netmode/post/`) **Purpose**: Finalize mode switch and cleanup **Default Post-hook**: `datamodel_init.sh` ```bash #!/bin/sh # Reboot system after mode switch if [ ! -f /var/run/boot_complete ]; then exit 0 fi # Clear data model caches rm -f /etc/bbfdm/dmmap/PPP rm -f /etc/bbfdm/dmmap/IP rm -f /etc/bbfdm/dmmap/Ethernet sleep 5 reboot -f ``` **Example Additional Post-hook**: ```bash #!/bin/sh # /lib/netmode/post/20-notify logger -t "netmode" "Mode switch complete, notifying ACS" # Trigger TR-069 inform ``` ### Generic Scripts (`/lib/netmode/`) **Purpose**: Common operations for all modes **Example**: ```bash #!/bin/sh # /lib/netmode/10-common-setup logger -t "netmode" "Applying common network settings" # Set common timezone uci set system.@system[0].timezone='UTC' uci commit system ``` --- ## Data Model Integration ### BBF Data Model Structure **TR-181 Path**: `Device.X_IOWRT_EU_NetMode.` ``` Device.X_IOWRT_EU_NetMode. ├── Enable (boolean, r/w) ├── Mode (string, r/w) [Reference] ├── SupportedModesNumberOfEntries (unsignedInt, r) └── SupportedModes.{i}. ├── Name (string, r) [Unique, Linker] ├── Description (string, r) ├── SupportedArgumentsNumberOfEntries (unsignedInt, r) └── SupportedArguments.{i}. ├── Name (string, r) ├── Type (string, r/w) ├── Description (string, r) ├── Required (boolean, r) └── Value (string, r/w) [Secure] ``` ### Data Model Operations #### Get Current Mode ```xml Device.X_IOWRT_EU_NetMode.Mode ``` #### Set PPPoE Mode with Credentials ```xml Device.X_IOWRT_EU_NetMode.Mode routed-pppoe Device.X_IOWRT_EU_NetMode.SupportedModes.2.SupportedArguments.1.Value username@isp.com Device.X_IOWRT_EU_NetMode.SupportedModes.2.SupportedArguments.2.Value password123 ``` #### Trigger Mode Switch ```bash # After setting parameters via TR-069/USP service netmode restart # Or reload service netmode reload ``` ### BBF Service Configuration **File**: `bbfdm_service.json` ```json { "daemon": { "enable": "1", "service_name": "netmode", "unified_daemon": false, "services": [ { "parent_dm": "Device.", "object": "{BBF_VENDOR_PREFIX}NetMode" } ], "config": { "loglevel": "3" } } } ``` **Vendor Prefix Configuration** (Makefile): ```make config NETMODE_VENDOR_PREFIX depends on PACKAGE_netmode string "Vendor Extension used for netmode datamodel" default "" ``` If empty, inherits from `CONFIG_BBF_VENDOR_PREFIX`. --- ## Testing and Validation ### Pre-deployment Checklist - [ ] Mode directory created: `/etc/netmodes//` - [ ] Mode script created and executable: `scripts/10-` - [ ] Mode defined in `supported_modes.json` - [ ] UCI configuration populated (via uci-defaults) - [ ] Required arguments identified and documented - [ ] Environment variable handling implemented - [ ] UCI subsystems configured (network, dhcp, firewall) - [ ] Multicast configuration appropriate for mode - [ ] Service dependencies updated (cwmp, gateway, ssdpd) ### Manual Testing Procedure #### 1. Enable Debug Logging ```bash logger -s -p user.info -t "netmode-test" "Starting mode test" ``` #### 2. Set Mode via UCI ```bash uci set netmode.global.enabled='1' uci set netmode.global.mode='' # Set required arguments uci set netmode.@supported_args[X].value='' uci commit netmode ``` #### 3. Trigger Mode Switch ```bash service netmode restart ``` #### 4. Monitor Logs ```bash logread -f | grep netmode ``` Expected output: ``` netmode: Switching to [] Mode netmode: Executing /lib/netmode/pre scripts netmode: Generating L3 network configuration netmode: Executing [], script [10-] netmode: Switching to Mode [] done, last mode updated netmode: Executing /lib/netmode/post scripts ``` #### 5. Verify Configuration ```bash # Check network configuration uci show network | grep -E "lan|wan" # Check DHCP configuration uci show dhcp # Check firewall status uci show firewall.globals.enabled # Check last mode cat /etc/netmodes/.last_mode # Verify environment cleanup env | grep NETMODE ``` #### 6. Test Network Connectivity ```bash # Ping gateway ping -c 3 $(ip route | grep default | awk '{print $3}') # DNS resolution nslookup example.com # Check WAN IP ip addr show wan ``` ### Automated Testing Script ```bash #!/bin/sh # test-netmode.sh MODE="$1" ARGS="$2" if [ -z "$MODE" ]; then echo "Usage: $0 [args]" exit 1 fi echo "Testing mode: $MODE" # Set mode uci set netmode.global.mode="$MODE" # Parse and set arguments (format: "arg1=val1,arg2=val2") if [ -n "$ARGS" ]; then IFS=',' read -ra ARGLIST <<< "$ARGS" for arg in "${ARGLIST[@]}"; do name="${arg%%=*}" value="${arg#*=}" # Find matching argument section idx=$(uci show netmode | grep "name='$name'" | cut -d. -f2 | cut -d= -f1) uci set "netmode.$idx.value=$value" done fi uci commit netmode # Apply mode echo "Applying mode..." service netmode restart # Wait for completion sleep 2 # Verify echo "Verifying configuration..." cat /etc/netmodes/.last_mode echo "Mode test complete" ``` Usage: ```bash ./test-netmode.sh routed-pppoe "username=test@isp.com,password=secret" ``` ### Common Issues and Debugging #### Issue: Mode not switching **Symptoms**: `.last_mode` not updated **Debug**: ```bash # Check if enabled uci get netmode.global.enabled # Check if mode exists ls /etc/netmodes//scripts/ # Check script permissions ls -la /etc/netmodes//scripts/ ``` **Solution**: ```bash chmod +x /etc/netmodes//scripts/* ``` #### Issue: Required arguments missing **Symptoms**: Mode script exits early **Debug**: ```bash logread | grep "value.*missing" ``` **Solution**: Set all required argument values before switching mode. #### Issue: Environment variables not set **Symptoms**: Mode script cannot access parameters **Debug**: ```bash # Add to mode script: env | grep NETMODE | logger -t netmode-debug ``` **Solution**: Verify `dm_parent` references in UCI configuration. #### Issue: UCI configuration not applied **Symptoms**: Network doesn't change after mode switch **Debug**: ```bash # Check if UCI commit succeeded uci changes # Verify UCI syntax uci show network ``` **Solution**: Check for UCI syntax errors in mode script. --- ## Advanced Topics ### Custom Board Integration If using custom hardware, update board.json parsing in mode scripts: ```bash if [ -f /etc/board.json ]; then json_load_file /etc/board.json json_select network # Custom board handling json_select custom_wan 2>/dev/null json_get_var custom_dev device [ -n "$custom_dev" ] && wandev="$custom_dev" json_cleanup fi ``` ### VLAN Handling All routed modes support VLAN tagging via `vlanid` argument: ```bash if [ -n "$wandev" ] && echo "$NETMODE_vlanid" | grep -Eq '^[0-9]+$' && [ "$NETMODE_vlanid" -ge 1 ]; then vlandev="$wandev.$NETMODE_vlanid" vlandev_sec=$(echo $vlandev | tr '.' '_') uci -q set network.${vlandev_sec}=device uci -q set network.${vlandev_sec}.type="8021q" uci -q set network.${vlandev_sec}.name="$vlandev" uci -q set network.${vlandev_sec}.ifname="$wandev" uci -q set network.${vlandev_sec}.vid=$NETMODE_vlanid wandev="$vlandev" fi ``` ### Multicast Configuration **L3 Modes** (routed-*): Use multicast proxy ```bash l3_mcast_config() { logger -s -p user.info -t "netmode" "Generating L3 mcast configuration" rm -f /etc/config/mcast sh /rom/etc/uci-defaults/61-mcast_config_generate uci -q commit mcast } ``` **L2 Mode** (bridged): Use IGMP/MLD snooping ```bash l2_mcast_config() { # IGMP snooping uci -q set mcast.igmp_snooping_1=snooping uci -q set mcast.igmp_snooping_1.enable='1' uci -q set mcast.igmp_snooping_1.proto='igmp' uci -q set mcast.igmp_snooping_1.interface='br-lan' # MLD snooping uci -q set mcast.mld_snooping_1=snooping uci -q set mcast.mld_snooping_1.enable='1' uci -q set mcast.mld_snooping_1.proto='mld' uci -q commit mcast } ``` ### Preserving Configuration During Upgrade **File**: `/lib/upgrade/keep.d/netmode` ``` /etc/config/netmode /etc/netmodes/.last_mode ``` Add custom files: ```bash echo "/etc/netmodes/custom-mode/custom.conf" >> /lib/upgrade/keep.d/netmode ``` --- ## Best Practices ### Script Development 1. **Always source required libraries**: ```bash . /lib/functions.sh . /usr/share/libubox/jshn.sh ``` 2. **Use logger for all output**: ```bash logger -s -p user.info -t "netmode" "Message" ``` 3. **Validate environment variables**: ```bash if [ -z "$NETMODE_required_arg" ]; then logger -s -p user.err -t "netmode" "Missing required argument" exit 1 fi ``` 4. **Use `uci -q` for non-critical operations**: ```bash uci -q delete network.wan.old_option ``` 5. **Always commit after changes**: ```bash uci -q commit network ``` ### Security 1. **Clear sensitive environment variables**: - Automatic via `cleanup_env_vars()` - Mark sensitive parameters as "Secure" in data model 2. **Validate input**: ```bash # VLAN ID validation echo "$NETMODE_vlanid" | grep -Eq '^[0-9]+$' && [ "$NETMODE_vlanid" -ge 1 ] # IP address validation echo "$NETMODE_ipaddr" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$' ``` 3. **Avoid command injection**: ```bash # BAD eval "uci set network.wan.ip=$NETMODE_ipaddr" # GOOD uci set network.wan.ip="$NETMODE_ipaddr" ``` ### Performance 1. **Minimize reboots**: Use `service reload` when possible 2. **Batch UCI operations**: Commit once at the end 3. **Avoid redundant mode switches**: Compare with `.last_mode` --- ## References - OpenWrt UCI Documentation: https://openwrt.org/docs/guide-user/base-system/uci - Procd Init Scripts: https://openwrt.org/docs/guide-developer/procd-init-scripts - BBF TR-181 Data Model: https://usp-data-models.broadband-forum.org/ - iopsys bbfdm: https://dev.iopsys.eu/iopsys/bbfdm --- ## Support and Contributing For issues and contributions, please contact the iopsys development team. **Package Maintainer**: iopsys **License**: GPL-2.0-only **Version**: 1.1.11