diff --git a/netmode/files/:w b/netmode/files/:w new file mode 100644 index 000000000..c36104cfa --- /dev/null +++ b/netmode/files/:w @@ -0,0 +1,576 @@ +#!/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 + +# +# Main Interface Configuration +# +configure_interfaces() { + _log "Starting advanced interface configuration" + + # Get configuration from environment variables + local interface_names="${NETMODE_interface_names:-wan}" + local interface_types="${NETMODE_interface_types:-bridge:transparent}" + local ports="${NETMODE_ports:-ALL}" + local mac_addrs="${NETMODE_macaddrs:-}" + + _log "Interface names: $interface_names" + _log "Interface types: $interface_types" + _log "Ports: $ports" + _log "MAC addresses: $mac_addrs" + + # Validate configuration before proceeding + _log "Validating configuration..." + + # Count elements in each parameter + local name_count=$(echo "$interface_names" | tr ',' '\n' | wc -l) + local type_count=$(echo "$interface_types" | tr ',' '\n' | wc -l) + local port_count=$(echo "$ports" | tr ',' '\n' | wc -l) + local mac_count=0 + [ -n "$mac_addrs" ] && mac_count=$(echo "$mac_addrs" | tr ',' '\n' | wc -l) + + _log "Element counts: names=$name_count, types=$type_count, ports=$port_count, macs=$mac_count" + + # Validate counts match + if [ "$name_count" != "$type_count" ]; then + _log "ERROR: Number of interface names ($name_count) does not match number of interface types ($type_count)" + _log "interface_names: $interface_names" + _log "interface_types: $interface_types" + return 1 + fi + + if [ "$name_count" != "$port_count" ]; then + _log "ERROR: Number of interface names ($name_count) does not match number of ports ($port_count)" + _log "interface_names: $interface_names" + _log "ports: $ports" + return 1 + fi + + if [ "$mac_count" -gt 0 -a "$mac_count" != "$name_count" ]; then + _log "WARNING: Number of MAC addresses ($mac_count) does not match number of interfaces ($name_count)" + _log "Some interfaces will use default MAC addresses" + fi + + # Validate each parameter + local idx=1 + local OLD_IFS="$IFS" + IFS=',' + + for name in $interface_names; do + validate_interface_name "$name" || { + IFS="$OLD_IFS" + return 1 + } + done + + for type in $interface_types; do + validate_interface_type "$type" || { + IFS="$OLD_IFS" + return 1 + } + done + + for port_spec in $ports; do + validate_port_spec "$port_spec" || { + IFS="$OLD_IFS" + return 1 + } + done + + if [ -n "$mac_addrs" ]; then + for mac in $mac_addrs; do + validate_mac_address "$mac" "1" || { + IFS="$OLD_IFS" + return 1 + } + done + fi + + IFS="$OLD_IFS" + + _log "Configuration validation passed" + + # Clean up existing configuration + cleanup_interfaces + + # Split comma-separated values into arrays + local names_arr="" + local types_arr="" + local ports_arr="" + local macs_arr="" + + # Save and set IFS for comma splitting + local OLD_IFS="$IFS" + IFS=',' + + for name in $interface_names; do + names_arr="$names_arr $name" + done + + for type in $interface_types; do + types_arr="$types_arr $type" + done + + for port_spec in $ports; do + ports_arr="$ports_arr $port_spec" + done + + for mac in $mac_addrs; do + macs_arr="$macs_arr $mac" + done + + # Restore IFS + IFS="$OLD_IFS" + + # Convert to arrays for indexing + set -- $names_arr + local total_interfaces=$# + + _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" + + # Create each interface + local idx=1 + for if_name in $names_arr; do + # Get corresponding type, ports, and MAC address + local type_idx=$idx + local ports_idx=$idx + local mac_idx=$idx + + set -- $types_arr + shift $((type_idx - 1)) + local if_type="${1:-bridge:transparent}" + + set -- $ports_arr + shift $((ports_idx - 1)) + local port_list="${1:-ALL}" + + set -- $macs_arr + shift $((mac_idx - 1)) + local if_mac="${1:-}" + + _log "Creating interface $idx/$total_interfaces: name=$if_name, type=$if_type, ports=$port_list, mac=$if_mac" + + # 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" + + if [ "$purpose" = "iptv" ]; then + IPTV_IFACES="$IPTV_IFACES $if_name" + elif [ "$purpose" = "inet" ]; then + INET_IFACES="$INET_IFACES $if_name" + elif [ "$purpose" = "mgmt" ]; 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" + ;; + + brvlan) + # Create bridge with VLAN filtering + BRIDGE_VLAN_PRESENT=1 + 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 name + local ref_if_name="$cvid" + 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" + ;; + + 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" ;; + 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 + create_shared_bridge + 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 add mcast proxy + uci rename mcast.@proxy[-1]="mc_proxy_MLD" + uci set mcast.@proxy[-1].enable="1" + uci set mcast.@proxy[-1].proto="mld" + uci set mcast.@proxy[-1].version="2" + uci set mcast.@proxy[-1].robustness="2" + uci set mcast.@proxy[-1].query_interval="125" + uci set mcast.@proxy[-1].query_response_interval="100" + uci set mcast.@proxy[-1].last_member_query_interval="10" + uci set mcast.@proxy[-1].fast_leave="1" + uci set mcast.@proxy[-1].snooping_mode="2" + uci add_list mcast.@proxy[-1].downstream_interface="br-lan" + + IFS=" " + for itf in $IPTV_DEVS; do + uci add_list mcast.@proxy[-1].upstream_interface="$itf" + done + + + uci add mcast proxy + uci rename mcast.@proxy[-1]="igmp_proxy_1" + uci set mcast.@proxy[-1].enable="1" + uci set mcast.@proxy[-1].proto="igmp" + uci set mcast.@proxy[-1].version="2" + uci set mcast.@proxy[-1].robustness="2" + uci set mcast.@proxy[-1].query_interval="125" + uci set mcast.@proxy[-1].query_response_interval="100" + uci set mcast.@proxy[-1].last_member_query_interval="10" + uci set mcast.@proxy[-1].fast_leave="1" + uci set mcast.@proxy[-1].snooping_mode="2" + uci add_list mcast.@proxy[-1].downstream_interface="br-lan" + + IFS=" " + for itf in $IPTV_DEVS; do + uci add_list mcast.@proxy[-1].upstream_interface="$itf" + done + + uci 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="${NETMODE_interface_names:-wan}" + local interface_types="${NETMODE_interface_types:-bridge:transparent}" + local has_static_lan=0 + + local OLD_IFS="$IFS" + IFS=',' + 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 + IFS="$OLD_IFS" + + 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" + fi + + local dhcp_ifaces="$(collect_interfaces_with_wan_port)" + + _log "Disabling DHCP server on interfaces: $dhcp_ifaces" + for iface in $dhcp_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 + local interface_types="${NETMODE_interface_types:-bridge:transparent}" + local has_routed=0 + + local OLD_IFS="$IFS" + IFS=',' + for if_type in $interface_types; do + if echo "$if_type" | grep -q "^route:"; then + has_routed=1 + break + fi + done + IFS="$OLD_IFS" + + if [ "$has_routed" = "1" ]; then + # 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() { + _log "Updating service configurations" + + # Get first interface name for services + local interface_names="${NETMODE_interface_names:-wan}" + local IFS=',' + local first_interface="" + + for if_name in $interface_names; do + first_interface="$if_name" + break + done + + # Update CWMP Agent WAN Interface + uci -q set cwmp.cpe.default_wan_interface="$first_interface" + uci -q commit cwmp + + # Update gateway WAN Interface + uci -q set gateway.global.wan_interface="$first_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 "$NETMODE_interface_types" | 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 + configure_l2_mcast +fi + +configure_dhcp +configure_firewall +configure_services + +_log "=========================================" +_log "Advanced Mode Configuration Complete" +_log "=========================================" + +exit 0