Last active
June 4, 2018 05:52
-
-
Save mwpastore/3178412c9a21dd1411cab7152b016baf to your computer and use it in GitHub Desktop.
Automated VLAN flop for AT&T RG workaround
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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