iopsys-feed/netmode/files/etc/netmodes/advanced/scripts/10-advanced
2026-02-19 19:58:08 +05:30

679 lines
19 KiB
Bash
Executable file

#!/bin/sh
#
# Advanced Mode Script
# Unified configuration for bridges, routed interfaces, and standalone interfaces
# Replaces: bridged mode and routed-multi-service mode
#
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
. /lib/netmode/advanced_helper.sh
[ -f /etc/device_info ] && source "/etc/device_info"
_log() {
logger -s -p user.info -t "netmode-advanced" "$*"
}
IPTV_IFACES=""
MGMT_IFACES=""
INET_IFACES=""
IPTV_DEVS=""
WAN_PORT=""
MACVLAN_PRESENT=0
BRIDGE_VLAN_PRESENT=0
TYPES_ARR=""
NAMES_ARR=""
PORTS_ARR=""
LAN_ARR=""
MTU_ARR=""
#
# Main Interface Configuration
#
configure_interfaces() {
_log "Starting advanced interface configuration"
# Get configuration from single combined parameter
local interface_configs="${NETMODE_interface_configs:-bridge:transparent;ALL}"
local mac_addrs="${NETMODE_macaddrs:-}"
_log "Interface configs: $interface_configs"
_log "MAC addresses: $mac_addrs"
# Parse the combined configuration into parallel arrays
local OLD_IFS="$IFS"
IFS=','
for entry in $interface_configs; do
# Split each entry by semicolon - first two fields are fixed: type;port
local type_part=$(echo "$entry" | cut -d';' -f1)
local port_part=$(echo "$entry" | cut -d';' -f2)
# Count semicolons to determine number of fields
local semicolon_count=$(echo "$entry" | tr -cd ';' | wc -c)
local num_fields=$((semicolon_count + 1))
# Search for mtu- in any field after the first two
local mtu_part="n"
local field_num=3
while [ "$field_num" -le "$num_fields" ]; do
local field=$(echo "$entry" | cut -d';' -f${field_num})
# Check if this field contains mtu-
if [[ "$field" == "mtu-"* ]]; then
mtu_part="${field#mtu-}"
break
fi
field_num=$((field_num + 1))
done
[ -z "$TYPES_ARR" ] && TYPES_ARR="$type_part" || TYPES_ARR="$TYPES_ARR $type_part"
[ -z "$PORTS_ARR" ] && PORTS_ARR="$port_part" || PORTS_ARR="$PORTS_ARR $port_part"
[ -z "$MTU_ARR" ] && MTU_ARR="$mtu_part" || MTU_ARR="$MTU_ARR $mtu_part"
done
IFS="$OLD_IFS"
# Count elements
set -- $TYPES_ARR
local total_interfaces=$#
local type_count=$#
set -- $PORTS_ARR
local port_count=$#
local mac_count=0
[ -n "$mac_addrs" ] && {
IFS=','
for mac in $mac_addrs; do
mac_count=$((mac_count + 1))
done
IFS="$OLD_IFS"
}
_log "Element counts: types=$type_count, ports=$port_count, macs=$mac_count"
# Validate counts match
if [ "$type_count" != "$port_count" ]; then
_log "ERROR: Number of interface types ($type_count) does not match number of ports ($port_count)"
return 1
fi
if [ "$mac_count" -gt 0 -a "$mac_count" != "$type_count" ]; then
_log "WARNING: Number of MAC addresses ($mac_count) does not match number of interfaces ($type_count)"
_log "Some interfaces will use default MAC addresses"
fi
# Validate each parameter
for type in $TYPES_ARR; do
validate_interface_type "$type" || {
return 1
}
done
for port_spec in $PORTS_ARR; do
validate_port_spec "$port_spec" || {
return 1
}
done
for mtu in $MTU_ARR; do
validate_mtu "$mtu" || {
return 1
}
done
if [ -n "$mac_addrs" ]; then
IFS=','
for mac in $mac_addrs; do
validate_mac_address "$mac" "1" || {
IFS="$OLD_IFS"
return 1
}
done
IFS="$OLD_IFS"
fi
_log "Configuration validation passed"
# Clean up existing configuration
cleanup_interfaces
# Convert MAC addresses to array
local macs_arr=""
if [ -n "$mac_addrs" ]; then
IFS=','
for mac in $mac_addrs; do
macs_arr="$macs_arr $mac"
done
IFS="$OLD_IFS"
fi
_log "Total interfaces to create: $total_interfaces"
# Get WAN port for routed interfaces
local wan_port=$(get_wan_port)
WAN_PORT="$wan_port"
_log "WAN port: $WAN_PORT"
# First pass: Determine interface names based on rules
local wan_assigned=0
local wan6_assigned=0
local lan_assigned=0
local iface_counter=1
local idx=1
for if_type in $TYPES_ARR; do
set -- $PORTS_ARR
shift $((idx - 1))
local port_list="${1:-ALL}"
# Parse interface type to get mode and purpose
parse_interface_type "$if_type"
local mode="$PARSE_MODE"
local purpose="$PARSE_PURPOSE"
local proto="$PARSE_PROTO"
local if_name=""
if [ "$purpose" = "lan" ]; then
if_name="lan"
lan_assigned=1
elif [ "$purpose" = "wan" ]; then
if_name="wan"
wan_assigned=1
elif [ "$proto" = "pppoe" ]; then
if_name="wan"
wan_assigned=1
elif [ "$proto" = "dhcpv6" ] && [ "$wan6_assigned" -eq 0 ]; then
if_name="wan6"
wan6_assigned=1
elif [ "$mode" = "bridge" -o "$mode" = "brvlan" ] && [ "$port_list" = "ALL" ] && [ "$wan_assigned" -eq 0 ]; then
if_name="wan"
wan_assigned=1
# If no wan yet, interface with -inet will be called wan
elif [ "$wan_assigned" -eq 0 ] && [ "$purpose" = "inet" ]; then
if_name="wan"
wan_assigned=1
# If no wan yet, interface with WAN/ALL port will be called wan
elif [ "$wan_assigned" -eq 0 ] && [ "$port_list" = "WAN" -o "$port_list" = "ALL" ] && [ "$purpose" != "mgmt" ]; then
if_name="wan"
wan_assigned=1
# For all bridge interfaces with ALL_LAN ports, the first one will be called lan
elif [ "$mode" = "bridge" -o "$mode" = "brvlan" ] && [ "$port_list" = "ALL_LAN" ] && [ "$lan_assigned" -eq 0 ]; then
if_name="lan"
lan_assigned=1
# Rule 5: Default - iface1, iface2, etc.
else
if_name="iface${iface_counter}"
iface_counter=$((iface_counter + 1))
fi
[ -z "$NAMES_ARR" ] && NAMES_ARR="$if_name" || NAMES_ARR="$NAMES_ARR $if_name"
# keep a list of lan interfaces so that they can be added to lan zone
if [ "$purpose" = "lan" ]; then
[ -z "$LAN_ARR" ] && LAN_ARR="$if_name" || LAN_ARR="$LAN_ARR $if_name"
fi
idx=$((idx + 1))
done
_log "Generated interface names: $NAMES_ARR"
# Validate generated names
for name in $NAMES_ARR; do
validate_interface_name "$name" || {
return 1
}
done
# Second pass: Create each interface
idx=1
for if_name in $NAMES_ARR; do
# Get corresponding type, ports, and MAC address
set -- $TYPES_ARR
shift $((idx - 1))
local if_type="${1:-bridge:transparent}"
set -- $PORTS_ARR
shift $((idx - 1))
local port_list="${1:-ALL}"
set -- $MTU_ARR
shift $((idx - 1))
local if_mtu="${1}"
set -- $macs_arr
shift $((idx - 1))
local if_mac="${1:-}"
_log "Creating interface $idx/$total_interfaces: name=$if_name, type=$if_type, ports=$port_list, mac=$if_mac, mtu=$if_mtu"
# Parse interface type
parse_interface_type "$if_type"
local mode="$PARSE_MODE"
local vlan_type="$PARSE_VLAN_TYPE"
local cvid="$PARSE_CVID"
local svid="$PARSE_SVID"
local mac_addr="$PARSE_MAC_ADDR"
local proto="$PARSE_PROTO"
local disabled="$PARSE_DISABLED"
local purpose="$PARSE_PURPOSE"
# PPPoE overhead handling for underlying device MTU
if [ "$proto" = "pppoe" ]; then
if_mtu="$((if_mtu + 8))"
_log "Proto is pppoe so mtu increased by 8: $if_mtu"
fi
if [ "$purpose" = "iptv" ]; then
IPTV_IFACES="$IPTV_IFACES $if_name"
elif [ "$purpose" = "inet" ]; then
INET_IFACES="$INET_IFACES $if_name"
elif [ "$purpose" = "mgmt" ] || [[ "$proto" == "*dhcp*" ]]; then
MGMT_IFACES="$MGMT_IFACES $if_name"
fi
if [ "$vlan_type" = "macvlan" ] || [ "$mac_count" -gt 0 ]; then
MACVLAN_PRESENT=1
fi
_log "Parsed: mode=$mode, vlan_type=$vlan_type, cvid=$cvid, svid=$svid, mac=$mac_addr, proto=$proto, purpose=$purpose"
case "$mode" in
bridge)
# Create bridge using helper function
create_bridge "$if_name" "$if_type" "$port_list" "$if_mac" "$if_mtu"
;;
brvlan)
# Create bridge with VLAN filtering
BRIDGE_VLAN_PRESENT=1
bridge_vlan_mtu="$if_mtu"
create_bridge_vlan_filtering "$if_name" "$if_type" "$port_list" "$if_mac"
;;
device-ref)
# Create interface that references device from another interface
# cvid contains the reference interface number
# Note: MTU is not set for device-ref mode since it references an existing device
# The referenced device should already have MTU set
local ref_if_name=""
local ref_if_num="$cvid"
local if_idx=1
for n in $NAMES_ARR; do
if [ "$if_idx" = "$ref_if_num" ]; then
ref_if_name="$n"
break
fi
if_idx=$((if_idx+1))
done
local ref_device=$(uci -q get "network.${ref_if_name}.device")
if [ -z "$ref_device" ]; then
_log "ERROR: Reference interface '$ref_if_name' not found or has no device"
exit 1
fi
_log "Creating interface $if_name referencing device $ref_device from interface $ref_if_name"
# Create interface using the same device as reference interface
uci -q delete "network.${if_name}"
uci -q set "network.${if_name}=interface"
uci -q set "network.${if_name}.proto=${proto}"
uci -q set "network.${if_name}.device=${ref_device}"
# Set MAC address if provided
if [ -n "$if_mac" ]; then
local resolved_mac=$(resolve_mac_address "$if_mac")
uci -q set "network.${if_name}.macaddr=${resolved_mac}"
_log "Setting MAC address: $if_mac -> $resolved_mac"
fi
[ "$disabled" = "1" ] && uci -q set "network.${if_name}.disabled=1"
;;
route)
# Create routed interface
port_list="${port_list//:u/}"
local base_device=""
if [ "$port_list" = "WAN" -o "$port_list" = "wan" ]; then
base_device="$wan_port"
else
# Use first port from list
local actual_ports=$(parse_port_list "$port_list")
base_device=$(echo "$actual_ports" | awk '{print $1}')
fi
create_routed_interface "$if_name" "$vlan_type" "$cvid" "$mac_addr" "$proto" "$base_device" "$disabled" "$purpose" "$if_mtu"
;;
direct)
# Create standalone VLAN interface
port_list="${port_list//:u/}"
local base_device=""
if [ "$port_list" = "WAN" -o "$port_list" = "wan" ]; then
base_device="$wan_port"
else
local actual_ports=$(parse_port_list "$port_list")
base_device=$(echo "$actual_ports" | awk '{print $1}')
fi
create_standalone_interface "$if_name" "$vlan_type" "$cvid" "$if_mac" "$proto" "$base_device" "$disabled" "$if_mtu"
;;
esac
idx=$((idx + 1))
done
if [ "$BRIDGE_VLAN_PRESENT" -eq 1 ]; then
# create the shared bridge once with all collected ports from bridge-vlan interfaces
# the last mtu used for bridge-vlan interface will be used to set MTU for the shared bridge
# bridge-vlan is a special case for mtu because there is one "shared" bridge being created for all vlans
# this shared bridge will have base ports as its members
# since bridge vlan ports do not have their own sections
# so we can only set one mtu, and we can set it on the shared bridge
# here we are picking the last mtu that provided for a bridge-vlan interface
create_shared_bridge "$bridge_vlan_mtu" "$wan_assigned"
fi
# Commit network changes
uci -q commit network
IPTV_IFACES="$(echo "$IPTV_IFACES" | xargs)"
INET_IFACES="$(echo "$INET_IFACES" | xargs)"
MGMT_IFACES="$(echo "$MGMT_IFACES" | xargs)"
_log "Interface configuration completed"
}
#
# Configure L3 Multicast (Proxy)
#
configure_l3_mcast() {
_log "Configuring L3 multicast (Proxy) for $IPTV_DEVS"
# Remove proxy sections
uci -q delete mcast.igmp_proxy_1
uci -q delete mcast.mc_proxy_MLD
uci -q delete mcast.igmp_snooping_1
uci -q delete mcast.mld_snooping_1
IPTV_DEVS="$(echo "$IPTV_DEVS" | xargs | tr ' ' '\n' | sort -u)"
uci -q add mcast proxy
uci -q rename mcast.@proxy[-1]="mc_proxy_MLD"
uci -q set mcast.@proxy[-1].enable="1"
uci -q set mcast.@proxy[-1].proto="mld"
uci -q set mcast.@proxy[-1].version="2"
uci -q set mcast.@proxy[-1].robustness="2"
uci -q set mcast.@proxy[-1].query_interval="125"
uci -q set mcast.@proxy[-1].query_response_interval="100"
uci -q set mcast.@proxy[-1].last_member_query_interval="10"
uci -q set mcast.@proxy[-1].fast_leave="1"
uci -q set mcast.@proxy[-1].snooping_mode="2"
uci -q add_list mcast.@proxy[-1].downstream_interface="br-lan"
IFS=" "
for itf in $IPTV_DEVS; do
uci -q add_list mcast.@proxy[-1].upstream_interface="$itf"
done
uci -q add mcast proxy
uci -q rename mcast.@proxy[-1]="igmp_proxy_1"
uci -q set mcast.@proxy[-1].enable="1"
uci -q set mcast.@proxy[-1].proto="igmp"
uci -q set mcast.@proxy[-1].version="2"
uci -q set mcast.@proxy[-1].robustness="2"
uci -q set mcast.@proxy[-1].query_interval="125"
uci -q set mcast.@proxy[-1].query_response_interval="100"
uci -q set mcast.@proxy[-1].last_member_query_interval="10"
uci -q set mcast.@proxy[-1].fast_leave="1"
uci -q set mcast.@proxy[-1].snooping_mode="2"
uci -q add_list mcast.@proxy[-1].downstream_interface="br-lan"
IFS=" "
for itf in $IPTV_DEVS; do
uci -q add_list mcast.@proxy[-1].upstream_interface="$itf"
done
uci -q add_list mcast.@proxy[-1].filter="239.0.0.0/8"
uci -q commit mcast
_log "L3 multicast configuration complete"
}
#
# Configure L2 Multicast (Snooping)
#
configure_l2_mcast() {
_log "Configuring L2 multicast (snooping)"
# Remove proxy sections
uci -q delete mcast.igmp_proxy_1
uci -q delete mcast.mc_proxy_MLD
# Get all bridge names from network UCI
local bridge_list=""
local bridge_names=""
local br_device=""
# Query all network sections and filter for bridge type
bridge_list=$(uci -q show network | grep "\.type='bridge'" | cut -d'.' -f2)
# Convert to space-separated list
for bridge in $bridge_list; do
br_device="$(uci -q get network.${bridge}.name)"
if [ -z "$bridge_names" ]; then
[ -n "$br_device" ] && bridge_names="$br_device"
else
[ -n "$br_device" ] && bridge_names="$bridge_names $br_device"
fi
done
if [ -z "$bridge_names" ]; then
_log "No bridges found for multicast configuration"
return
fi
_log "Found bridges: $bridge_names"
# Add 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.version='2'
uci -q set mcast.igmp_snooping_1.robustness='2'
uci -q set mcast.igmp_snooping_1.query_interval='125'
uci -q set mcast.igmp_snooping_1.query_response_interval='100'
uci -q set mcast.igmp_snooping_1.last_member_query_interval='10'
uci -q set mcast.igmp_snooping_1.fast_leave='1'
uci -q set mcast.igmp_snooping_1.snooping_mode='2'
uci -q set mcast.igmp_snooping_1.interface="$bridge_names"
# to avoid multiple additions over the course of netmode reloads
uci -q del_list mcast.igmp_snooping_1.filter='239.0.0.0/8'
uci -q add_list mcast.igmp_snooping_1.filter='239.0.0.0/8'
# Add 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 set mcast.mld_snooping_1.version='2'
uci -q set mcast.mld_snooping_1.robustness='2'
uci -q set mcast.mld_snooping_1.query_interval='125'
uci -q set mcast.mld_snooping_1.query_response_interval='100'
uci -q set mcast.mld_snooping_1.last_member_query_interval='10'
uci -q set mcast.mld_snooping_1.fast_leave='1'
uci -q set mcast.mld_snooping_1.snooping_mode='2'
uci -q set mcast.mld_snooping_1.interface="$bridge_names"
uci -q commit mcast
_log "L2 multicast configuration complete"
}
#
# Configure DHCP
#
configure_dhcp() {
_log "Configuring DHCP"
# Check if we have any static interfaces (will be configured by post-hook)
local interface_names="${NAMES_ARR:-wan}"
local interface_types="${TYPES_ARR:-bridge:transparent}"
local has_static_lan=0
local idx=1
for if_name in $interface_names; do
# Get corresponding type
local type_idx=$idx
set -- $interface_types
shift $((type_idx - 1))
local if_type="${1:-bridge:transparent}"
# Check if this is lan interface with static proto
if [ "$if_name" = "lan" ] && echo "$if_type" | grep -q -- '-static$'; then
has_static_lan=1
break
fi
idx=$((idx + 1))
done
uci -q get network.lan && has_static_lan="1"
if [ "$has_static_lan" = "1" ]; then
_log "LAN interface with static IP detected - DHCP server will be configured by post-hook"
# Don't disable DHCP for LAN, it will be configured by 15-static_lan.sh
# Only disable DHCP on WAN
uci -q set dhcp.wan.ignore=1 2>/dev/null
/etc/init.d/odhcpd enable
else
# Disable DHCP server on LAN (advanced mode without static LAN)
uci -q set dhcp.lan.ignore=1
# Disable DHCP on WAN if it exists
uci -q set dhcp.wan.ignore=1 2>/dev/null
/etc/init.d/odhcpd disable
_log "DHCP server disabled on lan"
fi
_log "Disabling DHCP server on interfaces: $WAN_BASED_IFACES"
for iface in $WAN_BASED_IFACES; do
uci -q set dhcp.$iface=dhcp
uci -q set dhcp.$iface.interface="$iface"
uci -q set dhcp.$iface.ignore=1
done
uci -q commit dhcp
}
#
# Configure Firewall
#
configure_firewall() {
_log "Configuring firewall"
# Check if any interface is routed | direct
local interface_types="${TYPES_ARR:-bridge:transparent}"
local has_routed=0
for if_type in $interface_types; do
if echo "$if_type" | grep -qE "route|direct"; then
has_routed=1
break
fi
done
if [ "$has_routed" = "1" ]; then
_log "Check and update firewall"
# Enable firewall for routed interfaces
ensure_firewall_layout
fi
uci -q set firewall.globals.enabled="1"
uci -q commit firewall
_log "Firewall enabled"
}
#
# Update Service Dependencies
#
configure_services() {
# Get first interface name for services
local interface_names="${MGMT_IFACES:-wan}"
local service_interface=""
if echo "$interface_names" | grep -qF 'wan'; then
service_interface="wan"
else
for if_name in $interface_names; do
service_interface="$if_name"
break
done
fi
_log "Updating service configurations with interface: $service_interface"
# Update CWMP Agent WAN Interface
uci -q set cwmp.cpe.default_wan_interface="$service_interface"
uci -q commit cwmp
# Update gateway WAN Interface
uci -q set gateway.global.wan_interface="$service_interface"
uci -q commit gateway
# Disable SSDPD
uci -q set ssdpd.ssdp.enabled="0"
uci -q commit ssdpd
_log "Service configurations updated"
}
#
# Main Execution
#
_log "========================================="
_log "Starting Advanced Mode Configuration"
_log "========================================="
# Main execution with error handling
if ! configure_interfaces; then
_log "========================================="
_log "ERROR: Advanced Mode Configuration Failed"
_log "Please check the logs above for details"
_log "========================================="
exit 1
fi
if [ "$MACVLAN_PRESENT" -eq 1 ] || echo "$TYPES_ARR" | grep -q "BaseMACAddress"; then
_log "Macvlan interface with mac addr present, not generating default macoffset file"
else
_log "Macvlan interface with mac addr not present, generating default macoffset file"
configure_macoffset
fi
if [ -n "$IPTV_DEVS" ]; then
configure_l3_mcast
else
up_itf="$(uci -q get network.wan.device)"
if [ -z "$up_itf" ] || [[ "$up_itf" == "br-*" ]]; then
configure_l2_mcast
else
IPTV_DEVS="$up_itf"
configure_l3_mcast
fi
fi
WAN_BASED_IFACES="$(collect_interfaces_with_wan_port)"
configure_dhcp
configure_firewall
configure_services
_log "========================================="
_log "Advanced Mode Configuration Complete"
_log "========================================="
exit 0