iopsys-feed/netmode/docs/IMPLEMENTATION_GUIDE.md

26 KiB

Netmode Implementation Guide

Table of Contents

  1. Overview
  2. Architecture
  3. Directory Structure
  4. Configuration System
  5. Mode Implementation
  6. Creating a New Network Mode
  7. Environment Variables
  8. Hooks and Scripts
  9. Data Model Integration
  10. 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

  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/<mode>/uci/
  8. Generic Scripts: Execute /lib/netmode/* scripts
  9. Mode Scripts: Execute /etc/netmodes/<mode>/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

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

  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/<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

  1. Always source required libraries:

    . /lib/functions.sh
    . /usr/share/libubox/jshn.sh
    
  2. Use logger for all output:

    logger -s -p user.info -t "netmode" "Message"
    
  3. Validate environment variables:

    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:

    uci -q delete network.wan.old_option
    
  5. Always commit after changes:

    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:

    # 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:

    # 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


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