Skip to content

Instantly share code, notes, and snippets.

@mwpastore
Last active June 4, 2018 05:52
Show Gist options
  • Save mwpastore/3178412c9a21dd1411cab7152b016baf to your computer and use it in GitHub Desktop.
Save mwpastore/3178412c9a21dd1411cab7152b016baf to your computer and use it in GitHub Desktop.
Automated VLAN flop for AT&T RG workaround
#!/bin/bash
###
# PURPOSE
#
# Execute a VLAN "flop" to allow the AT&T Residential Gateway (RG) to perform
# 802.1x authentication via the Optical Network Terminal (ONT) before falling
# back to the subscriber's "own" router/gateway hardware. The RG is toggled off
# and on as needed via a remote-controlled power outlet (see below).
#
# For more information about this procedure, please see brianlan's original
# post[1] on DSLReports.
###
# REQUIREMENTS
#
# * A Netgear gigabit-speed "Smart Managed Plus" switch[2]. The following
# models are known to work:
# * GS108E
# * GS105PE
# * A MQTT broker[3]
# * An outlet that can be remotely controlled (i.e. set "on" or "off") via a
# published MQTT message. For example:
# * SmartThings Hub, Zigbee or Z-Wave outlet, and MQTT bridge[4]
# * Z-Wave "stick" and outlet and Home Assistant[5]
# * Z-Wave "stick" and outlet and MQTT bridge[6][7]
# * Sonoff outlet flashed w/ Tasmota firmware[8]
# * A host (for this script) that can access both the Netgear management
# console via HTTP *and* the MQTT broker via a command-line client[9]
#
# N.B. With a SmartThings-based solution, an Internet connection is required to
# perform the VLAN flop, so if the operation fails (or is attempted after your
# router has already lost its DHCP lease), your network may get stuck in an
# inconsistent state. To recover, just run this script and toggle the RG power
# manually as indicated.
###
# INSTALLATION
#
# Follow the guide[1] to establish the initial network environment, which
# includes copying information from the RG to your own router.
#
# In the configuration section below, define the address of the switch
# management console, ONT switch port, and VLAN IDs for the RG and your own
# router; assign static switch port VLANs; and supply the MQTT command-line,
# topic, and messages.
#
# Finally, schedule this script to run once a week or so, during off-hours,
# and/or when your router loses its DHCP lease.
###
# REFERENCES
#
# 1. http://www.dslreports.com/forum/r29903721-AT-T-Residential-Gateway-Bypass-True-bridge-mode
# 2. https://www.netgear.com/business/products/switches/web-managed/gigabit-web-managed-switch.aspx
# 3. https://github.com/mqtt/mqtt.github.io/wiki/servers
# 4. https://github.com/stjohnjohnson/smartthings-mqtt-bridge#readme
# 5. https://www.home-assistant.io/components/mqtt/
# 6. https://github.com/adpeace/zwave-mqtt-bridge#readme
# 7. https://github.com/ltoinel/ZWave2MQTT#readme
# 8. https://github.com/arendst/Sonoff-Tasmota/wiki/MQTT-Overview
# 9. https://github.com/mqtt/mqtt.github.io/wiki/libraries
###
# The MIT License (MIT)
#
# Copyright (c) 2018 Mike Pastore <mike@oobak.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
###
set -eou pipefail
IFS=$'\n\t'
# inspect execution environment
for command in curl perl ; do
hash $command
done
###
# BEGIN CONFIGURATION
###
RG_VLAN=2
OWN_VLAN=3
NG_BASE_URL=http://a.b.c.d
NG_PASSWORD=password
# switch ports are numbered from zero, so e.g. port #4 is port3
NG_ONT_PORT=port0
declare -A NG_STATIC_PORTS=(
[port1]=$RG_VLAN
[port2]=$OWN_VLAN
[port3]=4
[port4]=4
[port5]=4
[port6]=4
[port7]=4
)
MQTT_CLIENT=$(which mosquitto_pub)
declare -a MQTT_ARGS=()
MQTT_TOPIC=smartthings/RG/switch
MQTT_ON_MESSAGE=on
MQTT_OFF_MESSAGE=off
###
# END CONFIGURATION
###
###
# BEGIN GLOBAL VARIABLES
###
COOKIE_JAR=$(mktemp)
read -r -d '' NGGETVAL <<-'PERL' || true
END {
die "Unable to determine hash value!\n" unless $match;
print $match;
}
if (/<input.+name=['"]?$key['"]?/ && /value=['"]?([^ '"]+)['"]?/) {
$match = $1;
last;
}
PERL
###
# END GLOBAL VARIABLES
###
###
# BEGIN FUNCTION LIBRARY
###
function URLENCODE {
local url
declare -a args
for kv in "$@" ; do
args+=("--data-urlencode" "$kv")
done
set +e
url=$(curl -qs -o /dev/null -w '%{url_effective}' -G "${args[@]}" "")
local rc=$?
set -e
if [[ $rc -ne 3 ]] ; then
echo "ERROR: Unable to encode URL" >&2
return 1
fi
echo ${url##/?}
}
function NGPOST {
local URL="$NG_BASE_URL/$1"
local DATA=$(URLENCODE "${@:2}")
local STDOUT=$(mktemp)
curl -qsS -o $STDOUT -b $COOKIE_JAR -c $COOKIE_JAR -d "$DATA" -X POST -H "Content-Length: ${#DATA}" $URL
local rc
if grep -qs -e RedirectToLoginPage -e 'Maximum sessions reached' $STDOUT ; then
echo "ERROR: Unable to POST $URL" >&2
rc=1
else
rc=0
fi
rm -f $STDOUT
return $rc
}
function NGGET {
local URL=$NG_BASE_URL/$1
local STDOUT=$(mktemp)
curl -qsS -o $STDOUT -b $COOKIE_JAR $URL
local rc
if grep -qs RedirectToLoginPage $STDOUT ; then
echo "ERROR: Unable to GET $URL" >&2
rc=1
else
cat $STDOUT
rc=0
fi
rm -f $STDOUT
return $rc
}
function NGLOGOFF {
NGGET logout.cgi >/dev/null
rm -f $COOKIE_JAR
}
function NGGETVAL {
perl -sne "$NGGETVAL" -- -key="$1"
}
function NGPORTSTR {
local port=$1
local vlan=$2
echo "$port=$vlan"
for port in "${!NG_STATIC_PORTS[@]}" ; do
vlan=${NG_STATIC_PORTS[$port]}
echo "$port=$vlan"
done
}
function RGPOWER {
local message
if $1 ; then
message=$MQTT_ON_MESSAGE
else
message=$MQTT_OFF_MESSAGE
fi
$MQTT_CLIENT "${MQTT_ARGS[@]:+${MQTT_ARGS[@]}}" -t "$MQTT_TOPIC" -m "$message"
}
function log {
echo $1
}
function rollback {
set +eu
log 'ROLLBACK: Attempting to power off RG...'
RGPOWER false
sleep 60
log 'ROLLBACK: Attempting to log out of Netgear switch...'
NGLOGOFF
log 'Completed with errors!'
}
###
# END FUNCTION LIBRARY
###
log 'Powering off RG...'
RGPOWER false
sleep 15
log 'Logging into Netgear switch...'
NGPOST login.cgi "password=$NG_PASSWORD"
# Set up a trap to ensure we always recover
trap rollback ERR
log 'Powering on RG...'
RGPOWER true
log 'Moving ONT to same VLAN as RG...'
NGPOST 8021qBasic.cgi status=Enable \
hash=$(NGGET 8021qBasic.cgi | NGGETVAL hash) \
$(NGPORTSTR $NG_ONT_PORT $RG_VLAN)
sleep 15
# Verify results of previous operation in case of silent error
if [[ $RG_VLAN -ne $(NGGET 8021qBasic.cgi | NGGETVAL $NG_ONT_PORT) ]] ; then
echo "ERROR: Unable to set ONT VLAN" >&2
exit 1
fi
# N.B. If the above VLAN flop fails, there's nothing to roll back.
echo '*** Manually power on your RG *NOW*, if necessary! ***'
log 'Waiting for RG to auth...'
sleep 240
log 'Moving ONT back to its original VLAN...'
NGPOST 8021qBasic.cgi status=Enable \
hash=$(NGGET 8021qBasic.cgi | NGGETVAL hash) \
$(NGPORTSTR $NG_ONT_PORT $OWN_VLAN)
sleep 15
# Verify results of previous operation in case of silent error
if [[ $OWN_VLAN -ne $(NGGET 8021qBasic.cgi | NGGETVAL $NG_ONT_PORT) ]] ; then
echo "ERROR: Unable to set ONT VLAN" >&2
exit 1
fi
# N.B. If the above VLAN flop fails, there's no way to roll back the previous flop.
log 'Logging out of Netgear switch...'
NGLOGOFF
log 'Powering off RG...'
RGPOWER false
sleep 15
# TODO: Tell our router to release and renew its WAN interface?
log 'Completed successfully'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment