26 KiB
Netmode Implementation Guide
Table of Contents
- Overview
- Architecture
- Directory Structure
- Configuration System
- Mode Implementation
- Creating a New Network Mode
- Environment Variables
- Hooks and Scripts
- Data Model Integration
- 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 │ │ │ │ /<mode> │ │
│ └─────────────┘ └──────────────┘ └───────────────┘ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────┐ │ │
│ └────────▶│ Env Var Manager │◀───────────┘ │
│ │ (NETMODE_*) │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Hook Execution │ │
│ │ pre/post/lib │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ UCI Reconfig │ │
│ │ network/dhcp/ │ │
│ │ firewall/etc │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Execution Flow
- Trigger: System boot or
service netmode restart - Configuration Load: Read UCI netmode config
- Mode Detection: Get mode from
netmode.global.modeorfw_printenv - Change Detection: Compare with last saved mode in
/etc/netmodes/.last_mode - Environment Setup: Configure
NETMODE_*environment variables - Pre-hooks: Execute
/lib/netmode/pre/*scripts - UCI Copy: Copy mode-specific UCI files from
/etc/netmodes/<mode>/uci/ - Generic Scripts: Execute
/lib/netmode/*scripts - Mode Scripts: Execute
/etc/netmodes/<mode>/scripts/*in order - Last Mode Update: Save current mode to
.last_mode - Post-hooks: Execute
/lib/netmode/post/*scripts - 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
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
{
"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
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:
{
"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:
sh /etc/uci-defaults/40_netmode_populated_supported_modes
Or manually add to /etc/config/netmode:
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
#!/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:
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:
/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
# 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_:
NETMODE_<argument_name>="<value>"
Examples
For PPPoE mode:
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
#!/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
- Set: Before pre-hooks (if mode is being switched)
- Available: During pre-hooks, generic scripts, and mode scripts
- 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/<mode>/uci/* → /etc/config/
↓
3. /lib/netmode/* (Generic mode-switch scripts)
↓
4. /etc/netmodes/<mode>/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:
#!/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
#!/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:
#!/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:
#!/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
<GetParameterValues>
<ParameterNames>
<string>Device.X_IOWRT_EU_NetMode.Mode</string>
</ParameterNames>
</GetParameterValues>
Set PPPoE Mode with Credentials
<SetParameterValues>
<ParameterList>
<ParameterValueStruct>
<Name>Device.X_IOWRT_EU_NetMode.Mode</Name>
<Value>routed-pppoe</Value>
</ParameterValueStruct>
<ParameterValueStruct>
<Name>Device.X_IOWRT_EU_NetMode.SupportedModes.2.SupportedArguments.1.Value</Name>
<Value>username@isp.com</Value>
</ParameterValueStruct>
<ParameterValueStruct>
<Name>Device.X_IOWRT_EU_NetMode.SupportedModes.2.SupportedArguments.2.Value</Name>
<Value>password123</Value>
</ParameterValueStruct>
</ParameterList>
</SetParameterValues>
Trigger Mode Switch
# After setting parameters via TR-069/USP
service netmode restart
# Or reload
service netmode reload
BBF Service Configuration
File: bbfdm_service.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):
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-name>/ - Mode script created and executable:
scripts/10-<mode-name> - 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
logger -s -p user.info -t "netmode-test" "Starting mode test"
2. Set Mode via UCI
uci set netmode.global.enabled='1'
uci set netmode.global.mode='<mode-name>'
# Set required arguments
uci set netmode.@supported_args[X].value='<value>'
uci commit netmode
3. Trigger Mode Switch
service netmode restart
4. Monitor Logs
logread -f | grep netmode
Expected output:
netmode: Switching to [<mode>] Mode
netmode: Executing /lib/netmode/pre scripts
netmode: Generating L3 network configuration
netmode: Executing [<mode>], script [10-<mode>]
netmode: Switching to Mode [<mode>] done, last mode updated
netmode: Executing /lib/netmode/post scripts
5. Verify Configuration
# 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
# 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
#!/bin/sh
# test-netmode.sh
MODE="$1"
ARGS="$2"
if [ -z "$MODE" ]; then
echo "Usage: $0 <mode> [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:
./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:
# Check if enabled
uci get netmode.global.enabled
# Check if mode exists
ls /etc/netmodes/<mode>/scripts/
# Check script permissions
ls -la /etc/netmodes/<mode>/scripts/
Solution:
chmod +x /etc/netmodes/<mode>/scripts/*
Issue: Required arguments missing
Symptoms: Mode script exits early
Debug:
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:
# 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:
# 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:
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:
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
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
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:
echo "/etc/netmodes/custom-mode/custom.conf" >> /lib/upgrade/keep.d/netmode
Best Practices
Script Development
-
Always source required libraries:
. /lib/functions.sh . /usr/share/libubox/jshn.sh -
Use logger for all output:
logger -s -p user.info -t "netmode" "Message" -
Validate environment variables:
if [ -z "$NETMODE_required_arg" ]; then logger -s -p user.err -t "netmode" "Missing required argument" exit 1 fi -
Use
uci -qfor non-critical operations:uci -q delete network.wan.old_option -
Always commit after changes:
uci -q commit network
Security
-
Clear sensitive environment variables:
- Automatic via
cleanup_env_vars() - Mark sensitive parameters as "Secure" in data model
- Automatic via
-
Validate input:
# 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}$' -
Avoid command injection:
# BAD eval "uci set network.wan.ip=$NETMODE_ipaddr" # GOOD uci set network.wan.ip="$NETMODE_ipaddr"
Performance
- Minimize reboots: Use
service reloadwhen possible - Batch UCI operations: Commit once at the end
- 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