iopsys-feed/netmode/docs/ADVANCED_MODE_IMPLEMENTATION.md
Mohd Husaam Mehdi 40240a7152 netmode: update handling of direct interfaces
the idea is to make them similar to route interface, to avoid
confusion

* they can be also be mgmt/inet/iptv
* they will have default proto set to dhcp
* syntax is now direct:vlan:100 or direct:transparent
2026-01-23 18:34:10 +05:30

12 KiB

Advanced Mode - Implementation Summary

Overview

The advanced mode is a unified network configuration mode that consolidates and extends the functionality of the previous bridged and routed-multi-service modes into a single, flexible interface.

Design Rationale

Problems with Old Approach

  1. Mode Fragmentation: Separate modes for bridged and routed scenarios
  2. Limited Flexibility: Couldn't mix bridges and routed interfaces
  3. Confusing Naming: "bridged" mode actually supported standalone interfaces too
  4. Parameter Proliferation: routed-multi-service had 6+ parameters for just 3 services
  5. No Scalability: Adding new services required new parameters

New Unified Approach

The advanced mode uses a declarative, array-based configuration:

interface_names:  wan,    iptv,           mgmt
interface_types:  route:vlan:100, bridge:tagged:200, direct:vlan:300
ports:       WAN,    LAN1-LAN2-WAN,  WAN

Benefits:

  • Single mode for all scenarios
  • Scalable (add N interfaces without new parameters)
  • Flexible (mix bridge/route/standalone)
  • Intuitive syntax
  • Self-documenting configuration

Architecture

File Structure

netmode/
├── files/
│   ├── etc/netmodes/advanced/
│   │   └── scripts/
│   │       └── 10-advanced           # Main mode script
│   ├── lib/netmode/
│   │   └── advanced_helper.sh        # Helper library
│   └── etc/netmodes/supported_modes.json
└── docs/
    ├── ADVANCED_MODE_GUIDE.md         # Complete guide
    └── ADVANCED_MODE_QUICK_REFERENCE.md

Components

1. advanced_helper.sh

Purpose: Core library for interface creation

Key Functions:

  • parse_interface_type() - Parse interface type specifications
  • create_bridge() - Create bridge interfaces with VLAN/QinQ
  • create_routed_interface() - Create routed interfaces with VLAN/MACVLAN
  • create_standalone_interface() - Create direct VLAN interfaces
  • parse_port_list() - Resolve port macros to device names
  • resolve_device_name() - Resolve LAN1/WAN to actual device names
  • cleanup_interfaces() - Clean up all interfaces before applying new config

2. 10-advanced Script

Purpose: Main mode script

Flow:

  1. Parse environment variables (NETMODE_*)
  2. Split comma-separated values
  3. Loop through each interface
  4. Parse interface type
  5. Call appropriate creation function (bridge/route/direct)
  6. Configure multicast, DHCP, firewall
  7. Update service dependencies

3. supported_modes.json

Purpose: Mode definition for UCI import

Configuration:

{
  "name": "advanced",
  "description": "Advanced Mode - Unified configuration...",
  "supported_args": [
    {
      "name": "interface_names",
      "description": "Interface names (comma-separated...)",
      "type": "string"
    },
    ...
  ]
}

Interface Type Syntax

Design Philosophy

Format: MODE:SUBTYPE[:PARAMS][:MODIFIERS]

Examples:

  • bridge:transparent - Mode=bridge, Subtype=transparent
  • bridge:tagged:100 - Mode=bridge, Subtype=tagged, Param=VID
  • route:vlan:100:AA:BB:CC:DD:EE:FF - Mode=route, Subtype=vlan, Params=VID+MAC
  • direct:vlan:2501-n - Mode=direct, Param=VID, Modifier=proto_none

Parsing Logic

The parse_interface_type() function:

  1. Extract modifiers (-n, -d)
  2. Parse mode prefix (bridge:/route:/direct:)
  3. Parse subtype (transparent/tagged/vlan/macvlan)
  4. Parse parameters (VID, SVID, MAC address)
  5. Export to environment variables for caller

UCI Device Resolution

Problem

Port macros (LAN1, LAN2, WAN) are logical names that need to be mapped to actual hardware interfaces.

Solution

resolve_device_name() {
    local device_id="$1"
    local resolved_name=""

    # Try UCI device section
    resolved_name="$(uci -q get network.${device_id}.name)"

    # Fallback to input
    if [ -z "$resolved_name" ]; then
        resolved_name="$device_id"
    fi

    echo "$resolved_name"
}

Example:

LAN1 → uci get network.LAN1.name → eth1
WAN  → uci get network.WAN.name  → ae_wan

Port List Resolution

The parse_port_list() function:

  1. Check for "ALL" → Resolve all LAN1-8 + WAN
  2. Parse dash-separated → LAN1-LAN2-WAN → resolve each
  3. Return space-separated → "eth1 eth2 ae_wan"

VLAN Device Creation

802.1Q (C-tag)

create_vlan_device "eth0" "100" "8021q"

Creates:

config device 'eth0__100'
    option type '8021q'
    option enabled '1'
    option vid '100'
    option ifname 'eth0'
    option name 'eth0.100'

802.1ad (S-tag)

create_vlan_device "eth0" "300" "8021ad"

Creates:

config device 'eth0__300'
    option type '8021ad'
    option enabled '1'
    option vid '300'
    option ifname 'eth0'
    option name 'eth0.300'

QinQ (Double Tagging)

For bridge:qinq:100:300:

# Create S-tag first
svlan=$(create_vlan_device "eth0" "300" "8021ad")  # eth0.300

# Create C-tag on top of S-tag
cvlan=$(create_vlan_device "$svlan" "100" "8021q")  # eth0.300.100

Result: eth0.300.100 (S-tag 300, C-tag 100)

MACVLAN Device Creation

For route:macvlan:AA:BB:CC:DD:EE:FF:

create_macvlan_device "ae_wan" "AA:BB:CC:DD:EE:FF" "iptv"

Creates:

config device 'iptv_macvlan'
    option type 'macvlan'
    option enabled '1'
    option ifname 'ae_wan'
    option name 'iptv_macvlan'
    option macaddr 'AA:BB:CC:DD:EE:FF'
    option mode 'passthru'

Bridge Creation

Transparent Bridge

For bridge:transparent with ports='ALL':

create_bridge "wan" "bridge:transparent" "ALL"

Creates:

config interface 'wan'
    option proto 'dhcp'
    option device 'br-wan'

config device 'br_wan'
    option name 'br-wan'
    option type 'bridge'
    option bridge_empty '1'
    list ports 'eth1'
    list ports 'eth2'
    list ports 'ae_wan'

VLAN-Tagged Bridge

For bridge:tagged:100 with ports='ALL':

Creates VLAN devices on all ports first, then adds to bridge:

config device 'br_mgmt'
    option name 'br-mgmt'
    option type 'bridge'
    list ports 'eth1.100'
    list ports 'eth2.100'
    list ports 'ae_wan.100'

Routed Interface Creation

For route:vlan:100:

create_routed_interface "wan" "vlan" "100" "" "dhcp" "ae_wan" "0"

Creates:

config device 'ae_wan__100'
    option type '8021q'
    option vid '100'
    option ifname 'ae_wan'
    option name 'ae_wan.100'

config interface 'wan'
    option proto 'dhcp'
    option device 'ae_wan.100'

Firewall Logic

The advanced mode has intelligent firewall handling:

configure_firewall() {
    local has_routed=0

    # Check if ANY interface is routed
    for if_type in $interface_types; do
        if echo "$if_type" | grep -q "^route:"; then
            has_routed=1
            break
        fi
    done

    if [ "$has_routed" = "1" ]; then
        uci set firewall.globals.enabled="1"  # Enable for routed
    else
        uci set firewall.globals.enabled="0"  # Disable for bridge-only
    fi
}

Logic:

  • If any interface is routed → Enable firewall
  • If all interfaces are bridges → Disable firewall

Environment Variable Flow

Input (UCI → Environment)

# In netmode init script
export NETMODE_interface_names="wan,iptv,mgmt"
export NETMODE_interface_types="route:vlan:100,route:vlan:200,route:vlan:300"
export NETMODE_ports="WAN,WAN,WAN"

Parsing (Script)

# In 10-advanced script
local interface_names="${NETMODE_interface_names:-wan}"
local interface_types="${NETMODE_interface_types:-bridge:transparent}"
local ports="${NETMODE_ports:-ALL}"

# Split by comma
IFS=','
for name in $interface_names; do
    names_arr="$names_arr $name"
done

Output (UCI Network Config)

config interface 'wan'
    option proto 'dhcp'
    option device 'ae_wan.100'

config interface 'iptv'
    option proto 'dhcp'
    option device 'ae_wan.200'
...

Cleanup Strategy

Before applying new configuration, all existing interfaces are cleaned up:

cleanup_interfaces() {
    # Delete VLAN devices (8021q and 8021ad)
    for vlandev_sec in $(uci show network | grep -E "\.type='(8021q|8021ad)'" ...); do
        uci delete "$vlandev_sec"
    done

    # Delete MACVLAN devices
    for macvlandev_sec in $(uci show network | grep "\.type='macvlan'" ...); do
        uci delete "$macvlandev_sec"
    done

    # Delete bridge devices
    for brdev_sec in $(uci show network | grep "\.type='bridge'" ...); do
        uci delete "$brdev_sec"
    done

    # Delete standard interfaces
    uci delete network.lan
    uci delete network.wan
    uci delete network.wan6
}

This ensures a clean slate for the new configuration.

Migration Path

From bridged Mode

Before:

mode='bridged'
interface_names='wan,lan100'
interface_types='transparent,tagged:100'
ports='ALL,LAN1-LAN2'

After:

mode='advanced'
interface_names='wan,lan100'
interface_types='bridge:transparent,bridge:tagged:100'
ports='ALL,LAN1-LAN2'

Change: Add bridge: prefix to types.

From routed-multi-service Mode

Before:

mode='routed-multi-service'
inet_vlanid='100'
iptv_vlanid='200'
mgmt_vlanid='300'

After:

mode='advanced'
interface_names='wan,iptv,mgmt'
interface_types='route:vlan:100,route:vlan:200,route:vlan:300'
ports='WAN,WAN,WAN'

Change: Explicit interface names and unified syntax.

Testing Approach

Unit Testing

Test individual helper functions:

# Test device resolution
resolve_device_name "LAN1"  # Should return eth1

# Test port parsing
parse_port_list "LAN1-LAN2-WAN"  # Should return "eth1 eth2 ae_wan"

# Test type parsing
parse_interface_type "bridge:qinq:100:300-n"
# Should set: mode=bridge, vlan_type=qinq, cvid=100, svid=300, proto=none

Integration Testing

Test complete scenarios:

# Test transparent bridge
uci set netmode.global.mode='advanced'
uci set netmode.@supported_args[0].value='wan'
uci set netmode.@supported_args[1].value='bridge:transparent'
uci set netmode.@supported_args[2].value='ALL'
uci commit netmode
service netmode restart

# Verify
brctl show | grep br-wan

Validation

# Check UCI output
uci show network

# Check actual interfaces
ip addr show
brctl show
ip -d link show type vlan

# Check logs
logread | grep netmode-advanced

Performance Considerations

Comma Splitting Optimization

The script uses efficient IFS-based splitting:

local OLD_IFS="$IFS"
IFS=','
for name in $interface_names; do
    names_arr="$names_arr $name"
done
IFS="$OLD_IFS"

This is faster than using cut or awk in loops.

UCI Batching

All UCI commands are batched, with a single uci commit at the end:

# Multiple uci set commands
uci set ...
uci set ...
uci set ...

# Single commit
uci commit network

Logging

Logging is selective - info level for major steps, debug for details:

_log "Creating interface $idx/$total_interfaces"  # Info
logger -s -p user.debug -t "$_log_prefix" "Adding port: $port"  # Debug

Security Considerations

Input Validation

  • VLANs IDs: 1-4094
  • MAC addresses: Validated format
  • Port names: Resolved through UCI (trusted source)

Privilege Separation

  • Script runs as root (required for network config)
  • No user input directly executed
  • Environment variables sanitized through UCI

Future Enhancements

Possible future additions:

  1. Static IP support: route:vlan:100:static:192.168.1.1
  2. Port roles: ports='LAN1(tagged),LAN2(untagged)'
  3. Bridge STP: bridge:transparent:stp
  4. IPv6 specific: route:vlan:100:ipv6
  5. Validation: Pre-flight checks for VLAN conflicts

Backward Compatibility

Status: ⚠️ Breaking change by design

The old bridged and routed-multi-service modes are replaced by advanced mode. This is acceptable because:

  1. This is the first deployment of advanced features
  2. No existing production deployments use old syntax
  3. Cleaner architecture without legacy baggage
  4. Documentation focuses on new syntax only

Summary

The advanced mode represents a significant architectural improvement:

  • Unified: One mode for all scenarios
  • Scalable: Array-based configuration
  • Flexible: Mix bridges, routed, standalone
  • Intuitive: Self-documenting syntax
  • Powerful: VLAN, QinQ, MACVLAN support
  • Clean: No backward compatibility burden

Implementation Version: 1.0 Date: 2024-12-12 Status: Production Ready