Table of Contents

About

The ZTE routers cheap piece of shit routers™ usually dished out by ISPs (such as the ones in Indonesia) that have an astonishing lack of features: no SNMP, no bridge mode, bizarre wireless coupled with hardware, etc… Since SNMP did not fit the plastic allure of router in general, there is no way to programatically extract the external IP address.

Instead, Selenium and PhantomJS are going to be used in order to retrieve the IP address and update the CloudFlare zone.

Requirements

Setup

Download the script in the code section, configure the script by editing the parameters in the configuration section at the top and then place it in, say /etc/cron/cron.hourly. The script is created such that it will not return any output in case the CloudFlare name already resolves to the ZTE external address. However, the script will announce when the IP has been updated or in case there are any errors in attempting to do so.

Code

zte-f660rv1-cloudflare.sh
#!/bin/sh
###########################################################################
##  Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3      ##
##  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  ##
##  rights of fair usage, the disclaimer and warranty conditions.        ##
###########################################################################
 
###########################################################################
##                            CONFIGURATION                              ##
###########################################################################
 
# ZTE router IP or hostname
ZTE_LOCAL_ADDRESS='192.168.0.1'
# ZTE router username
ZTE_USERNAME='sm5lty'
# ZTE router password
ZTE_PASSWORD='goM0k1'
# Set this to the cloudflare zone name.
DOMAIN='remoz.org'
# Set this to the A record name.
RECORD_NAME='forward.remoz.org'
# Set this to your account e-mail address.
EMAIL='vic@remoz.org'
# Set this to your CloudFlare API key.
API_KEY='f5fdb672c54bb7d72dbeec35e822d90ce14e1'
 
###########################################################################
##                               INTERNALS                               ##
###########################################################################
 
LOCK_FILE='/var/lock/zte-cloudflare'
 
# Acquire a lock.
if mkdir $LOCK_FILE 2>/dev/null; then
    trap '{ rm -rf $LOCK_FILE; }' KILL QUIT TERM EXIT INT HUP
else
    exit 0
fi
 
# Retrieve the external address.
ZTE_EXTERNAL_ADDRRESS=`cat << EOF | /usr/bin/env python - $ZTE_LOCAL_ADDRESS $ZTE_USERNAME $ZTE_PASSWORD
# -*- coding: utf-8 -*-
 
# imports and packages
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support import expected_conditions as EC
from contextlib import contextmanager
import unittest, time, re, time, os, sys, random, signal
 
# the tool takes as parameter:
# - the IP address of the ZTE router
# - the username
# - the password
if len(sys.argv) != 4:
    print "Syntax: " + sys.argv[0] + " " + "<IP>" + " " + "<username>" + " " + "<password>"
    sys.exit(1)
zte = sys.argv[1]
usr = sys.argv[2]
pwd = sys.argv[3]
 
try:
    # initialize PhantomJS
    wd = webdriver.PhantomJS(service_log_path='/var/ghostdriver.log')
    wd.get("http://" + zte)
    wd.find_element_by_id("Frm_Username").click()
    wd.find_element_by_id("Frm_Username").clear()
    wd.find_element_by_id("Frm_Username").send_keys(usr)
    wd.find_element_by_id("Frm_Password").click()
    wd.find_element_by_id("Frm_Password").clear()
    wd.find_element_by_id("Frm_Password").send_keys(pwd)
    wd.find_element_by_id("LoginId").click()
    wd.switch_to_frame("mainFrame")
    wd.find_element_by_xpath("//div[3]/div[1]/div[1]/table/tbody/tr/td/table/tbody/tr[1]/td[2]/font").click()
    print wd.find_element_by_xpath("/html/body/div[3]/div[1]/div[3]/table/tbody/tr[5]/td[2]").text
except:
    # Tough luck!
    #print "Unexpected error:", sys.exc_info()[0]
    pass
finally:
    wd.close()
    wd.service.process.send_signal(signal.SIGTERM)
    wd.quit()
 
EOF
`
 
#echo $ZTE_EXTERNAL_ADDRRESS
#exit 0
 
if [ -z $ZTE_EXTERNAL_ADDRRESS ]; then
    echo "Unable to retrieve Airport external address."
    echo "Please check that the Airport has SNMP enabled."
    exit 1
fi
 
FLARE=$(curl -s https://www.cloudflare.com/api_json.html \
    -d 'a=rec_load_all' \
    -d "tkn=$API_KEY" \
    -d "email=$EMAIL" \
    -d "z=$DOMAIN")
 
if [ -z $FLARE ]; then
    echo "Unable to retrieve CloudFlare zone."
    echo "Please check the API key."
    exit 1
fi
 
ZONE_ADDRESS=`echo $FLARE | sed -E "s/.*,\"name\":\"$RECORD_NAME\",[^}]*?,\"type\":\"A\",[^}]*?,\"content\":\"([0-9\.]+?)\",.*/\1/"`
if [ -z $ZONE_ADDRESS ]; then
    echo "Unable to retrieve the CloudFlare zone IP address."
    exit 1
fi
 
# if the ZTE external address matches the zone address terminate
if [ $ZTE_EXTERNAL_ADDRRESS = $ZONE_ADDRESS ]; then
    exit 0
fi
 
RECORD_ID=`echo $FLARE | sed -E "s/.*\"rec_id\":\"([0-9]+?)\",[^}]*?,\"name\":\"$RECORD_NAME\",[^}]*?,\"type\":\"A\",.*/\1/"`
if [ -z $RECORD_ID ]; then
    echo "Unable to retrieve CloudFlare record ID for the zone."
    exit 1
fi
 
FLARE=$(curl -s https://www.cloudflare.com/api_json.html \
    -d 'a=rec_edit' \
    -d "tkn=$API_KEY" \
    -d "id=$RECORD_ID" \
    -d "email=$EMAIL" \
    -d "z=$DOMAIN" \
    -d 'type=A' \
    -d "name=$RECORD_NAME" \
    -d "content=$ZTE_EXTERNAL_ADDRRESS" \
    -d 'service_mode=0' \
    -d 'ttl=1')
 
RESULT=`echo $FLARE | sed -E "s/.*,\"result\":\"([^\"]*)\",.*/\1/"`
if [ $RESULT = "success" ]; then
    echo "$RECORD_NAME now points to $ZTE_EXTERNAL_ADDRRESS"
    exit 0
fi
exit 1