Side Toggle Switch for Tailscale Exit Node

Has anyone had any success in modifying the gl_tailscale script to use a defined exit node when the toggle switch on the side of the router is turned on?

Hello,

Sorry, the router firmware does not support the toggle on/off the exit node of Tailscale.

Thanks for your suggestion! I will collect this request to the PM team to evaluate.

Hi Bruce,

I was able to get this working. The script below will specify the exit_node_ip to be 100.70.85.109 (my exit node’s IP address) whenever the side toggle switch is turned on. It will revert to the UCI setting (tailscale.settings.exit_node_ip) when the switch is turned off. Anyone wanting to run the command below will need to change the IP of my exit node 100.70.85.109 to the IP of the exit node they’re wanting to use when the side switch is toggled on.

This solution updates the Tailscale configuration on a GL.iNet Slate 7 router to toggle the exit node based on the physical switch position. It:

• Modifies /usr/bin/gl_tailscale to set exit_node_ip to 100.70.85.109 when the switch is “on” or uses the UCI setting when “off”.

• Creates /usr/bin/gl_tailscale_switch_monitor to check the switch status every 5 seconds and restart Tailscale if it changes.

• Ensures guest network routing uses the same switch-determined exit_node_ip.

• Adds the monitor script to startup and runs it in the background.

Router: GL.iNet Slate 7

Caution: This overwrites /usr/bin/gl_tailscale. Back up before proceeding.

Command to Install

Copy and paste this into your router’s CLI via SSH. Replace 100.70.85.109 with your desired exit node IP


cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
cat << 'EOF' > /usr/bin/gl_tailscale
#!/bin/sh

. /lib/functions/gl_util.sh

action="$1"
TS_POLICY_ROUTE=/tmp/ts_policy_route
TS_FIREWALL_SECTION=gltailscale
TAILSCALE_ROUTE_TABLE=52
TAILSCALE_DNS_SERVER="100.100.100.100"

if [ "$action" = "set_route" ];then
    sleep 5
    ip route add 100.64.0.0/10 dev tailscale0
fi

add_policy_route()
{
    target=$1
    if [ -n "$target" ]; then
        route_param="to $target table main"
        /sbin/ip rule add $route_param
        echo $route_param >> $TS_POLICY_ROUTE
    fi
}

del_policy_route()
{
    /sbin/ip rule del to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50
    if [ -f $TS_POLICY_ROUTE ]; then
        while read line; do
            if [ -n "$line" ]; then
                /sbin/ip rule del $line
            fi
        done < $TS_POLICY_ROUTE
        rm $TS_POLICY_ROUTE
    fi
}

add_guest_policy_route()
{
    guest_disable=$(uci -q get network.guest.disabled)
    exit_node_ip=$1
    if [ -n "$exit_node_ip" ] && [ "$guest_disable" = "0" ]; then
        guestip=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].address')
        guestmask=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$guestip" ] && [ -n "$guestmask" ] ;then
            guest_network=$(ipcalc_network $guestip $guestmask | awk -F= '/NETWORK/{print $2}')
            guest_mask=$(ipcalc_network $guestip $guestmask | awk -F= '/PREFIX/{print $2}')
        fi

        if [ -n "$guest_network" ] && [ -n "$guest_mask" ]; then
            guest_ip="$guest_network/$guest_mask"
            policy_route_param="from $guest_ip table main"
            /sbin/ip rule add $policy_route_param
            echo $policy_route_param >> $TS_POLICY_ROUTE
        fi
    fi
}

add_exit_node_rule()
{
    count=0
    while [ $count -le 5 ]
    do
        rule_exist=$(ip rule | grep "from all fwmark 0x80000/0x80000 lookup")
        ts_prio=$(ip rule | grep "from all lookup 52" |  awk -F':' '{print $1}' | head -n 1)
        if [ "$rule_exist" = "" ]; then
            if [ -n "$ts_prio" ]; then
                pre_prio=$(($ts_prio-1))
                policy_route_param="from all fwmark 0x80000/0x80000 lookup main prio $pre_prio"
                /sbin/ip rule add $policy_route_param
                echo $policy_route_param >> $TS_POLICY_ROUTE
            fi
        else
            break
        fi
        count=$(($count+1))
        sleep 1
    done
}

add_ts_fw_rule()
{
    if [ -f /etc/firewall.tailscale.sh ]; then
        /etc/firewall.tailscale.sh &
    fi
}

modify_dns_resolv()
{
    dns_suffix=$(timeout 2 tailscale status -json | jsonfilter -e '@.MagicDNSSuffix')
    rule_exist=$(grep "ts.net/100.100.100.100" /etc/dnsmasq.conf)
    domain_orig=$(uci get dhcp.@dnsmasq[0].domain)
    domain_orig_clean=$(echo "$domain_orig" | sed "s/.*ts.net //g")

    if [ "$1" = "1" ]; then
        if [ "$rule_exist" = "" ]; then
            [ "$dns_suffix" = "" ] && dns_suffix="ts.net"
            echo "server=/$dns_suffix/100.100.100.100" >> /etc/dnsmasq.conf
            uci set dhcp.@dnsmasq[0].domain="$dns_suffix $domain_orig_clean"
            uci commit dhcp
            /etc/init.d/dnsmasq restart
        fi
    else
        uci set dhcp.@dnsmasq[0].domain="$domain_orig_clean"
        uci commit dhcp
        if [ -n "$rule_exist" ]; then
            sed -i '/ts\.net\/100\.100\.100\.100/d' /etc/dnsmasq.conf
            /etc/init.d/dnsmasq restart
        fi
    fi
}

get_switch_status()
{
    status=$(get_switch_button_status)
    if [ "$status" = "no support" ]; then
        echo "no_support"
    elif [ "$status" = "on" ]; then
        echo "on"
    else
        echo "off"
    fi
}

if [ "$action" = "restart" ];then
    for count in `seq 1 10`
    do
        [ $(pgrep -f "tailscaled") ] && break
        sleep 1
    done

    /etc/init.d/tailscale restart

    del_policy_route
    add_ts_fw_rule

    sys_mode=$(uci -q get glconfig.general.mode)
    if [ "$sys_mode" != "router" ]; then
        /etc/init.d/tailscale stop
        modify_dns_resolv 0
        exit 0
    fi

    enabled=$(uci -q get tailscale.settings.enabled)
    if [ "$enabled" = "1" ]; then
        /sbin/ip rule add to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50

        wanip=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].address')
        wanmask=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$wanip" ] && [ -n "$wanmask" ] ;then
            wan_network=$(ipcalc_network $wanip $wanmask | awk -F= '/NETWORK/{print $2}')
            wan_mask=$(ipcalc_network $wanip $wanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$wan_network" -a -n "$wan_mask" ] && wan_ip="$wan_network/$wan_mask"

        secondwanip=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].address')
        secondwanmask=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$secondwanip" ] && [ -n "$secondwanmask" ] ;then
            secondwan_network=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/NETWORK/{print $2}')
            secondwan_mask=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$secondwan_network" -a -n "$secondwan_mask" ] && secondwan_ip="$secondwan_network/$secondwan_mask"

        wwanip=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].address')
        wwanmask=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$wwanip" ] && [ -n "$wwanmask" ] ;then
            wwan_network=$(ipcalc_network $wwanip $wwanmask | awk -F= '/NETWORK/{print $2}')
            wwan_mask=$(ipcalc_network $wwanip $wwanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$wwan_network" -a -n "$wwan_mask" ] && wwan_ip="$wwan_network/$wan_mask"

        tetheringip=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].address')
        tetheringmask=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$tetheringip" ] && [ -n "$tetheringmask" ] ;then
            tethering_network=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/NETWORK/{print $2}')
            tethering_mask=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$tethering_network" -a -n "$tethering_mask" ] && tethering_ip="$tethering_network/$tethering_mask"

        lanip=$(uci -q get network.lan.ipaddr)
        lanmask="$(uci -q get network.lan.netmask)"
        lan_network=$(ipcalc_network $lanip $lanmask | awk -F= '/NETWORK/{print $2}')
        lan_mask=$(ipcalc_network $lanip $lanmask | awk -F= '/PREFIX/{print $2}')
        [ -n "$lan_network" -a -n "$lan_mask" ] && lan_ip="$lan_network/$lan_mask"

        switch_status=$(get_switch_status)
        if [ "$switch_status" = "on" ]; then
            exit_node_ip="100.70.85.109"
        else
            exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)
        fi
        [ -n "$exit_node_ip" ] && lan_enabled="1" || lan_enabled=$(uci -q get tailscale.settings.lan_enabled)
        [ -n "$exit_node_ip" ] && wan_enabled="1" || wan_enabled=$(uci -q get tailscale.settings.wan_enabled)
        
        if [ "$lan_enabled" = "1" ]; then
            if [ -n "$lan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$lan_ip" || routes="$lan_ip"
            fi
        fi

        if [ "$wan_enabled" = "1" ]; then
            if [ -n "$wan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$wan_ip" || routes="$wan_ip"
            fi

            if [ -n "$wwan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$wwan_ip" || routes="$wwan_ip"
            fi

            if [ -n "$secondwan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$secondwan_ip" || routes="$secondwan_ip"
            fi

            if [ -n "$tethering_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$tethering_ip" || routes="$tethering_ip"
            fi
        fi

        if [ -n "$routes" ]; then
            param="--advertise-routes=$routes"
        else
            param=""
        fi
        
        if [ -n "$exit_node_ip" ];then
            add_exit_node_rule
            param="$param --exit-node-allow-lan-access --exit-node=$exit_node_ip"
        fi

        [ -n "$lan_ip" ] && add_policy_route $lan_ip

        [ -n "$wan_ip" ] && add_policy_route $wan_ip

        [ -n "$secondwan_ip" ] && add_policy_route $secondwan_ip

        [ -n "$wwan_ip" ] && add_policy_route $wwan_ip

        [ -n "$tethering_ip" ] && add_policy_route $tethering_ip

        add_guest_policy_route "$exit_node_ip"
        
        timeout 10 /usr/sbin/tailscale up --reset --accept-routes $param --timeout 3s --accept-dns=false > /dev/null
    else
        /etc/init.d/tailscale stop
    fi

    modify_dns_resolv $enabled
fi
EOF
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh

. /lib/functions/gl_util.sh

STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"

get_switch_status() {
    status=$(get_switch_button_status)
    if [ "$status" = "no support" ]; then
        echo "no_support"
    elif [ "$status" = "on" ]; then
        echo "on"
    else
        echo "off"
    fi
}

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

if [ ! -f "$STATE_FILE" ]; then
    echo "unknown" > "$STATE_FILE"
    log_message "Initialized state file with 'unknown' status"
fi

while true; do
    current_status=$(get_switch_status)
    last_status=$(cat "$STATE_FILE")
    if [ "$current_status" != "$last_status" ]; then
        log_message "Switch status changed from '$last_status' to '$current_status'"
        echo "$current_status" > "$STATE_FILE"
        log_message "Executing /usr/bin/gl_tailscale restart"
        /usr/bin/gl_tailscale restart
        if [ $? -eq 0 ]; then
            log_message "Successfully executed gl_tailscale restart"
        else
            log_message "Failed to execute gl_tailscale restart"
        fi
    fi
    sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor &' /etc/rc.local && \
/usr/bin/gl_tailscale_switch_monitor &

Great!

Thank you for sharing and testing!

Update:

The command below will use the last exit node that was chosen in the html based console for when the side toggle switch is turned on. Instead of having to edit the script with the specific ip address of the desired exit node, the user instead will just need to pick that ip address once in the html console interface after running the command below. After that the external toggle switch will turn on the exit node to be whatever the most recent exit node chosen in the tailscale settings from the html console screen was.


cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
mkdir -p /etc/tailscale && \
cat << 'EOF' > /usr/bin/gl_tailscale
#!/bin/sh

. /lib/functions/gl_util.sh

action="$1"
TS_POLICY_ROUTE=/tmp/ts_policy_route
TS_FIREWALL_SECTION=gltailscale
TAILSCALE_ROUTE_TABLE=52
TAILSCALE_DNS_SERVER="100.100.100.100"
LAST_EXIT_NODE_FILE="/etc/tailscale/last_exit_node_ip"
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

# Check for required dependencies
for cmd in ubus jsonfilter ipcalc_network tailscale; do
    if ! command -v "$cmd" >/dev/null; then
        log_message "Error: Required command '$cmd' not found"
        exit 1
    fi
done

if [ "$action" = "set_route" ];then
    sleep 5
    ip route add 100.64.0.0/10 dev tailscale0
fi

add_policy_route()
{
    target=$1
    if [ -n "$target" ]; then
        route_param="to $target table main"
        /sbin/ip rule add $route_param
        echo $route_param >> $TS_POLICY_ROUTE
    fi
}

del_policy_route()
{
    /sbin/ip rule del to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50 2>/dev/null
    if [ -f $TS_POLICY_ROUTE ]; then
        while read line; do
            if [ -n "$line" ]; then
                /sbin/ip rule del $line 2>/dev/null
            fi
        done < $TS_POLICY_ROUTE
        rm $TS_POLICY_ROUTE
    fi
}

add_guest_policy_route()
{
    guest_disable=$(uci -q get network.guest.disabled)
    exit_node_ip=$1
    if [ -n "$exit_node_ip" ] && [ "$guest_disable" = "0" ]; then
        guestip=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].address')
        guestmask=$(ubus call network.interface.guest status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$guestip" ] && [ -n "$guestmask" ] ;then
            guest_network=$(ipcalc_network $guestip $guestmask | awk -F= '/NETWORK/{print $2}')
            guest_mask=$(ipcalc_network $guestip $guestmask | awk -F= '/PREFIX/{print $2}')
        fi

        if [ -n "$guest_network" ] && [ -n "$guest_mask" ]; then
            guest_ip="$guest_network/$guest_mask"
            policy_route_param="from $guest_ip table main"
            /sbin/ip rule add $policy_route_param
            echo $policy_route_param >> $TS_POLICY_ROUTE
        fi
    fi
}

add_exit_node_rule()
{
    count=0
    while [ $count -le 5 ]
    do
        rule_exist=$(ip rule | grep "from all fwmark 0x80000/0x80000 lookup")
        ts_prio=$(ip rule | grep "from all lookup 52" | awk -F':' '{print $1}' | head -n 1)
        if [ "$rule_exist" = "" ]; then
            if [ -n "$ts_prio" ]; then
                pre_prio=$(($ts_prio-1))
                policy_route_param="from all fwmark 0x80000/0x80000 lookup main prio $pre_prio"
                /sbin/ip rule add $policy_route_param
                echo $policy_route_param >> $TS_POLICY_ROUTE
            fi
        else
            break
        fi
        count=$(($count+1))
        sleep 1
    done
}

add_ts_fw_rule()
{
    if [ -f /etc/firewall.tailscale.sh ]; then
        /etc/firewall.tailscale.sh &
    fi
}

modify_dns_resolv()
{
    dns_suffix=$(timeout 2 tailscale status -json | jsonfilter -e '@.MagicDNSSuffix' 2>/dev/null)
    if [ $? -ne 0 ]; then
        log_message "Warning: Failed to retrieve MagicDNSSuffix from tailscale status"
        return 1
    fi
    rule_exist=$(grep "ts.net/100.100.100.100" /etc/dnsmasq.conf)
    domain_orig=$(uci get dhcp.@dnsmasq[0].domain)
    domain_orig_clean=$(echo "$domain_orig" | sed "s/.*ts.net //g")

    if [ "$1" = "1" ]; then
        if [ "$rule_exist" = "" ]; then
            [ "$dns_suffix" = "" ] && dns_suffix="ts.net"
            echo "server=/$dns_suffix/100.100.100.100" >> /etc/dnsmasq.conf
            uci set dhcp.@dnsmasq[0].domain="$dns_suffix $domain_orig_clean"
            uci commit dhcp
            /etc/init.d/dnsmasq restart
        fi
    else
        uci set dhcp.@dnsmasq[0].domain="$domain_orig_clean"
        uci commit dhcp
        if [ -n "$rule_exist" ]; then
            sed -i '/ts\.net\/100\.100\.100\.100/d' /etc/dnsmasq.conf
            /etc/init.d/dnsmasq restart
        fi
    fi
}

get_switch_status()
{
    if ! command -v get_switch_button_status >/dev/null; then
        log_message "Error: get_switch_button_status not found in /lib/functions/gl_util.sh"
        echo "no_support"
        return 1
    fi
    status=$(get_switch_button_status)
    if [ "$status" = "no support" ]; then
        echo "no_support"
    elif [ "$status" = "on" ]; then
        echo "on"
    else
        echo "off"
    fi
}

if [ "$action" = "restart" ];then
    for count in `seq 1 20`
    do
        [ $(pgrep -f "tailscaled") ] && break
        sleep 1
    done
    if [ $count -eq 21 ]; then
        log_message "Error: tailscaled not running after 20 seconds"
        exit 1
    fi

    /etc/init.d/tailscale restart

    del_policy_route
    add_ts_fw_rule

    sys_mode=$(uci -q get glconfig.general.mode)
    if [ "$sys_mode" != "router" ]; then
        /etc/init.d/tailscale stop
        modify_dns_resolv 0
        exit 0
    fi

    enabled=$(uci -q get tailscale.settings.enabled)
    if [ "$enabled" = "1" ]; then
        /sbin/ip rule add to $TAILSCALE_DNS_SERVER lookup $TAILSCALE_ROUTE_TABLE pri 50

        # Save UCI exit_node_ip if defined
        uci_exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)
        if [ -n "$uci_exit_node_ip" ]; then
            echo "$uci_exit_node_ip" > "$LAST_EXIT_NODE_FILE"
            log_message "Saved UCI exit_node_ip: $uci_exit_node_ip to $LAST_EXIT_NODE_FILE"
        fi

        wanip=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].address')
        wanmask=$(ubus call network.interface.wan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$wanip" ] && [ -n "$wanmask" ] ;then
            wan_network=$(ipcalc_network $wanip $wanmask | awk -F= '/NETWORK/{print $2}')
            wan_mask=$(ipcalc_network $wanip $wanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$wan_network" -a -n "$wan_mask" ] && wan_ip="$wan_network/$wan_mask"

        secondwanip=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].address')
        secondwanmask=$(ubus call network.interface.secondwan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$secondwanip" ] && [ -n "$secondwanmask" ] ;then
            secondwan_network=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/NETWORK/{print $2}')
            secondwan_mask=$(ipcalc_network $secondwanip $secondwanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$secondwan_network" -a -n "$secondwan_mask" ] && secondwan_ip="$secondwan_network/$secondwan_mask"

        wwanip=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].address')
        wwanmask=$(ubus call network.interface.wwan status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$wwanip" ] && [ -n "$wwanmask" ] ;then
            wwan_network=$(ipcalc_network $wwanip $wwanmask | awk -F= '/NETWORK/{print $2}')
            wwan_mask=$(ipcalc_network $wwanip $wwanmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$wwan_network" -a -n "$wwan_mask" ] && wwan_ip="$wwan_network/$wan_mask"

        tetheringip=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].address')
        tetheringmask=$(ubus call network.interface.tethering status|jsonfilter -e '@["ipv4-address"][0].mask')
        if [ -n "$tetheringip" ] && [ -n "$tetheringmask" ] ;then
            tethering_network=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/NETWORK/{print $2}')
            tethering_mask=$(ipcalc_network $tetheringip $tetheringmask | awk -F= '/PREFIX/{print $2}')
        fi
        [ -n "$tethering_network" -a -n "$tethering_mask" ] && tethering_ip="$tethering_network/$tethering_mask"

        lanip=$(uci -q get network.lan.ipaddr)
        lanmask="$(uci -q get network.lan.netmask)"
        lan_network=$(ipcalc_network $lanip $lanmask | awk -F= '/NETWORK/{print $2}')
        lan_mask=$(ipcalc_network $lanip $lanmask | awk -F= '/PREFIX/{print $2}')
        [ -n "$lan_network" -a -n "$lan_mask" ] && lan_ip="$lan_network/$lan_mask"

        switch_status=$(get_switch_status)
        if [ "$switch_status" = "on" ]; then
            if [ -f "$LAST_EXIT_NODE_FILE" ]; then
                exit_node_ip=$(cat "$LAST_EXIT_NODE_FILE")
                log_message "Using exit_node_ip: $exit_node_ip from $LAST_EXIT_NODE_FILE"
            else
                exit_node_ip=""
                log_message "No exit_node_ip found in $LAST_EXIT_NODE_FILE"
            fi
        else
            exit_node_ip=""
            log_message "Switch off, no exit node set"
        fi
        [ -n "$exit_node_ip" ] && lan_enabled="1" || lan_enabled=$(uci -q get tailscale.settings.lan_enabled)
        [ -n "$exit_node_ip" ] && wan_enabled="1" || wan_enabled=$(uci -q get tailscale.settings.wan_enabled)
        
        if [ "$lan_enabled" = "1" ]; then
            if [ -n "$lan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$lan_ip" || routes="$lan_ip"
            fi
        fi

        if [ "$wan_enabled" = "1" ]; then
            if [ -n "$wan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$wan_ip" || routes="$wan_ip"
            fi

            if [ -n "$wwan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$wwan_ip" || routes="$wwan_ip"
            fi

            if [ -n "$secondwan_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$secondwan_ip" || routes="$secondwan_ip"
            fi

            if [ -n "$tethering_ip" ]; then
                [ -n "$routes" ] && routes="$routes,$tethering_ip" || routes="$tethering_ip"
            fi
        fi

        if [ -n "$routes" ]; then
            param="--advertise-routes=$routes"
        else
            param=""
        fi
        
        if [ -n "$exit_node_ip" ];then
            add_exit_node_rule
            param="$param --exit-node-allow-lan-access --exit-node=$exit_node_ip"
        fi

        [ -n "$lan_ip" ] && add_policy_route $lan_ip

        [ -n "$wan_ip" ] && add_policy_route $wan_ip

        [ -n "$secondwan_ip" ] && add_policy_route $secondwan_ip

        [ -n "$wwan_ip" ] && add_policy_route $wwan_ip

        [ -n "$tethering_ip" ] && add_policy_route $tethering_ip

        add_guest_policy_route "$exit_node_ip"
        
        timeout 10 /usr/sbin/tailscale up --reset --accept-routes $param --timeout 3s --accept-dns=false > /dev/null
        if [ $? -ne 0 ]; then
            log_message "Error: tailscale up command failed"
            exit 1
        fi
    else
        /etc/init.d/tailscale stop
    fi

    modify_dns_resolv $enabled
fi
EOF
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh

. /lib/functions/gl_util.sh

STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

get_switch_status() {
    if ! command -v get_switch_button_status >/dev/null; then
        log_message "Error: get_switch_button_status not found in /lib/functions/gl_util.sh"
        echo "no_support"
        return 1
    fi
    status=$(get_switch_button_status)
    if [ "$status" = "no support" ]; then
        echo "no_support"
    elif [ "$status" = "on" ]; then
        echo "on"
    else
        echo "off"
    fi
}

if [ ! -f "$STATE_FILE" ]; then
    echo "unknown" > "$STATE_FILE"
    log_message "Initialized state file with 'unknown' status"
fi

while true; do
    current_status=$(get_switch_status)
    last_status=$(cat "$STATE_FILE")
    if [ "$current_status" != "$last_status" ]; then
        log_message "Switch status changed from '$last_status' to '$current_status'"
        echo "$current_status" > "$STATE_FILE"
        log_message "Executing /usr/bin/gl_tailscale restart"
        /usr/bin/gl_tailscale restart
        if [ $? -eq 0 ]; then
            log_message "Successfully executed gl_tailscale restart"
        else
            log_message "Failed to execute gl_tailscale restart"
        fi
    fi
    sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor &' /etc/rc.local && \
sleep 2 && /usr/bin/gl_tailscale_switch_monitor &

1 Like

I’ve made an additional update. The newest command will find the location of places needing changed in the /usr/bin/gl_tailscale script and make the appropriate changes without overwriting the entire /usr/bin/gl_tailscale file. The purpose of this change is to make this command more likely to work on other gl-inet devices and continue to work in future firmware updates.

cp /usr/bin/gl_tailscale /usr/bin/gl_tailscale.bak && \
mkdir -p /etc/tailscale && \
sed -i '/^[[:space:]]*exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/ {
    s/.*/exit_node_ip=$(uci -q get tailscale.settings.exit_node_ip)\n    if [ -n "$exit_node_ip" ]; then\n        echo "$exit_node_ip" > "\/etc\/tailscale\/last_exit_node_ip"\n    fi/
}' /usr/bin/gl_tailscale && \
sed -i '/^[[:space:]]*switch_status=$(get_switch_status)/,/^[[:space:]]*exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/ {
    /switch_status=$(get_switch_status)/!d
    /exit_node_ip=$(uci -q get tailscale\.settings\.exit_node_ip)/!d
    s/.*/switch_status=$(get_switch_status)\n    if [ "$switch_status" = "on" ]; then\n        if [ -f "\/etc\/tailscale\/last_exit_node_ip" ]; then\n            exit_node_ip=$(cat "\/etc\/tailscale\/last_exit_node_ip")\n        else\n            exit_node_ip=""\n        fi\n    else\n        exit_node_ip=""\n    fi/
}' /usr/bin/gl_tailscale && \
if ! grep -q "get_switch_status()" /usr/bin/gl_tailscale; then
    sed -i '/action="$1"/a \
get_switch_status() \{\
    status=$(get_switch_button_status 2>/dev/null || echo "no support")\
    if [ "$status" = "no support" ] || [ "$status" = "" ]; then\
        echo "no_support"\
    elif [ "$status" = "on" ]; then\
        echo "on"\
    else\
        echo "off"\
    fi\
\}' /usr/bin/gl_tailscale
fi && \
cat << 'EOF' > /usr/bin/gl_tailscale_switch_monitor
#!/bin/sh

. /lib/functions/gl_util.sh

STATE_FILE="/tmp/gl_tailscale_switch_status"
POLL_INTERVAL=5
LOG_FILE="/tmp/gl_tailscale_switch_monitor.log"

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S'): $1" >> "$LOG_FILE"
}

get_switch_status() {
    status=$(get_switch_button_status 2>/dev/null || echo "no support")
    if [ "$status" = "no support" ] || [ "$status" = "" ]; then
        echo "no_support"
    elif [ "$status" = "on" ]; then
        echo "on"
    else
        echo "off"
    fi
}

if [ ! -f "$STATE_FILE" ]; then
    echo "unknown" > "$STATE_FILE"
    log_message "Initialized state file with 'unknown' status"
fi

while true; do
    current_status=$(get_switch_status)
    last_status=$(cat "$STATE_FILE")
    if [ "$current_status" != "$last_status" ]; then
        log_message "Switch status changed from '$last_status' to '$current_status'"
        echo "$current_status" > "$STATE_FILE"
        log_message "Executing /usr/bin/gl_tailscale restart"
        /usr/bin/gl_tailscale restart
        if [ $? -eq 0 ]; then
            log_message "Successfully executed gl_tailscale restart"
        else
            log_message "Failed to execute gl_tailscale restart"
        fi
    fi
    sleep "$POLL_INTERVAL"
done
EOF
chmod +x /usr/bin/gl_tailscale_switch_monitor && \
grep -q "/usr/bin/gl_tailscale_switch_monitor" /etc/rc.local || \
    sed -i '/exit 0/i \/usr/bin\/gl_tailscale_switch_monitor \&' /etc/rc.local && \
sleep 2 && /usr/bin/gl_tailscale_switch_monitor &

I’ve tested with no issues on Slate 7 running firmware 4.8.1. I’m not able to test on another device. The use case for this for me is to be able to toggle on the exit node when I’m on a hotel’s network without having to log in to the web console. Changing the position of the toggle switch on the side of the router is much simpler.

1 Like