Story time

For the past five years I have been waiting for Greek telecoms to put a proper landline at my place in order to enjoy the internet like I used to in my previous homes. To be honest, I had this for granted before moving here just because of the fact that I am living 15 minutes from the capital, well I was wrong. I will spare you from talking too much about this and how annoying it is. I will still rant a bit though.

What I am trying to say is when you have fast and unlimited internet it's extremely awkward to go back to something relatively slow with a limited dataplan. Especially when you work mostly using the internet. In order to be able to work from home when needed I had to find some way to have internet without using a landline, I ended up with a mobile broadband program giving me 30GB/mo at supposedly up to 21Mbps. I say supposedly because the max speed I get is 2.5Mbps, but hey I have internet.

The first couple months I was really careful trying to limit as much as possible what I was downloading and streaming and I would always end up with a lot of unused GB of traffic. Then I slowly stopped paying too much attention and went over my dataplan a couple times. This means no more internet except if I pay an extra package to get 500MB. When you have to send a project to your client it's an amazing feeling.

Looking around

I started to have a closer look at the Huawei E5220 mobile broadband modem I got when signing with this ISP. Each time I wanted to see how much traffic was left from my data plan I had to log inside the web admin panel, and while it's only taking a minute it's still annoying to do it a couple times a day.

After observing traffic on the modem I noticed that the admin panel pages would make API calls to get various data through Ajax requests and to my surprise I realized I could actually make those calls myself without even being logged in:

mrt:~/huawei$ curl http://192.168.1.1/api/wlan/security-settings

<?xml version="1.0" encoding="UTF-8"?>
<response>
<WifiAuthmode>WPA/WPA2-PSK</WifiAuthmode>
<WifiBasicencryptionmodes>WEP</WifiBasicencryptionmodes>
<WifiWpaencryptionmodes>MIX</WifiWpaencryptionmodes>
<WifiWepKey1>462HR</WifiWepKey1>
<WifiWepKey2>462HR</WifiWepKey2>
<WifiWepKey3>462HR</WifiWepKey3>
<WifiWepKey4>462HR</WifiWepKey4>
<WifiWepKeyIndex>1</WifiWepKeyIndex>
<WifiWpapsk>**wifi access password in plaintext here**</WifiWpapsk>
<WifiWpsenbl>1</WifiWpsenbl>
<WifiWpscfg>0</WifiWpscfg>
<WifiRestart>0</WifiRestart>
</response>

While you need to be already on the network to access the modem API you can still get info such as the wifi access password or who is actually connected to it.

mrt:~/huawei$ curl http://192.168.1.1/api/wlan/host-list

<?xml version="1.0" encoding="UTF-8"?>
<response>
<Hosts>
<Host>
<ID>1</ID>
<MacAddress>A8:BB:CF:DA:E1:40</MacAddress>
<IpAddress>192.168.1.11</IpAddress>
<HostName>Mobile-Phone</HostName>
<AssociatedTime>46222</AssociatedTime>
<AssociatedSsid>WIFI_ACCESS</AssociatedSsid>
</Host>
<Host>
<ID>2</ID>
<MacAddress>F8:1E:DF:2F:B8:35</MacAddress>
<IpAddress>192.168.1.199</IpAddress>
<HostName>Computer</HostName>
<AssociatedTime>11535</AssociatedTime>
<AssociatedSsid>WIFI_ACCESS</AssociatedSsid>
</Host>
</Hosts>
</response>

API mapping

For some reason I couldn't properly extract a firmware I found for this specific modem in order to get the whole API but since the admin panel seems to be solely based on javascript I thought I could come pretty close to the full list of commands by grabbing the source files with a recursive wget. While it's highly possible I missed a couple files this is the list I ended up with:

http://192.168.1.1/js/changelang.js
http://192.168.1.1/js/checklogin.js
http://192.168.1.1/js/country.js
http://192.168.1.1/js/deviceinformation.js
http://192.168.1.1/js/dhcp.js
http://192.168.1.1/js/dmzsettings.js
http://192.168.1.1/js/firewallswitch.js
http://192.168.1.1/js/home.js
http://192.168.1.1/js/lanipfilter.js
http://192.168.1.1/js/main.js
http://192.168.1.1/js/messagesetting.js
http://192.168.1.1/js/mobileconnection.js
http://192.168.1.1/js/mobilenetworksettings.js
http://192.168.1.1/js/modifypassword.js
http://192.168.1.1/js/nat.js
http://192.168.1.1/js/pincodemanagement.js
http://192.168.1.1/js/ping.js
http://192.168.1.1/js/profilesmgr.js
http://192.168.1.1/js/quicksetup.js
http://192.168.1.1/js/redirect.js
http://192.168.1.1/js/sipalgsettings.js
http://192.168.1.1/js/sms.js
http://192.168.1.1/js/specialapplication.js
http://192.168.1.1/js/statistic.js
http://192.168.1.1/js/systemsettings.js
http://192.168.1.1/js/table.js
http://192.168.1.1/js/update.js
http://192.168.1.1/js/upnp.js
http://192.168.1.1/js/validation.js
http://192.168.1.1/js/virtualserver.js
http://192.168.1.1/js/wlanadvanced.js
http://192.168.1.1/js/wlanbasicsettings.js
http://192.168.1.1/js/wlanmacfilter.js

And I made a small bash script to download them all:

#!/bin/bash

js=( changelang.js checklogin.js country.js deviceinformation.js dhcp.js dmzsettings.js 
firewallswitch.js home.js lanipfilter.js main.js messagesetting.js mobileconnection.js 
mobilenetworksettings.js modifypassword.js nat.js pincodemanagement.js ping.js 
profilesmgr.js quicksetup.js redirect.js sipalgsettings.js sms.js specialapplication.js 
statistic.js systemsettings.js table.js update.js upnp.js validation.js virtualserver.js 
wlanadvanced.js wlanbasicsettings.js wlanmacfilter.js )

url="http://192.168.1.1/js/"
folder="./js/"

for file in "${js[@]}"
do
        wget "$url$file" -O $folder$file.gz; gunzip $folder$file.gz
done

It saved all javascript files found on the modem in a folder named js, from there I could grep quickly most of the api calls without digging too much into the source of each files:

mrt:~/huawei/mirror/js$ egrep -oh "(api/.\S*?)" *.js | egrep -o '^api/[a-z/_-]*' | sort | uniq

api/cradle/basic-info
api/cradle/status-info
api/device/autorun-version
api/device/fastbootswitch
api/device/infomation
api/device/information
api/device/powersaveswitch
api/dhcp/settings
api/dialup/auto-apn
api/dialup/connection
api/dialup/dial
api/dialup/mobile-dataswitch
api/dialup/profiles
api/filemanager/upload
api/global/module-switch
api/host/info
api/language/current-language
api/monitoring/check-notifications
api/monitoring/clear-traffic
api/monitoring/converged-status
api/monitoring/month_statistics
api/monitoring/month_statistics_wlan
api/monitoring/start_date
api/monitoring/start_date_wlan
api/monitoring/status
api/monitoring/traffic-statistics
api/net/current-plmn
api/net/net-mode
api/net/net-mode-list
api/net/network
api/net/plmn-list
api/net/register
api/online-update/ack-newversion
api/online-update/cancel-downloading
api/online-update/check-new-version
api/online-update/status
api/online-update/url-list
api/ota/status
api/pb/pb-match
api/pin/operate
api/pin/simlock
api/pin/status
api/redirection/homepage
api/security/dmz
api/security/firewall-switch
api/security/lan-ip-filter
api/security/nat
api/security/sip
api/security/special-applications
api/security/upnp
api/security/virtual-servers
api/sms/backup-sim
api/sms/cancel-send
api/sms/cofig
api/sms/config
api/sms/delete-sms
api/sms/save-sms
api/sms/send-sms
api/sms/send-status
api/sms/set-read
api/sms/sms-count
api/sms/sms-list
api/sntp/sntpswitch
api/user/login
api/user/logout
api/user/password
api/user/remind
api/user/session
api/user/state-login
api/ussd/get
api/wlan/basic-settings
api/wlan/handover-setting
api/wlan/host-list
api/wlan/mac-filter
api/wlan/multi-basic-settings
api/wlan/multi-security-settings
api/wlan/multi-switch-settings
api/wlan/oled-showpassword
api/wlan/security-settings
api/wlan/station-information
api/wlan/wifi-dataswitch

This is the full list containing both GET and POST Ajax requests, POST requests should normally check if the user is logged in before processing the request. The reason I wanted both was to map the full API, if I only needed most of the calls not requiring authentification I guess using this command would have been more accurate:

mrt:~/huawei/mirror/js$ grep 'getAjax' *.js | egrep -oh "(api/.\S*?)" | egrep -o '^api/[a-z/_-]*' | sort | uniq

They apparently made a couple typos in messagesettings.js and mobilenetworksettings.js, requesting cofig instead of config and infomation instead of information during the log.error so you can ignore these API calls from the list.

Along with the API we can also access XML files:

mrt:~/huawei/mirror/js$ egrep -oh "(config/.\S*?)" *.js | egrep -o '^config/[a-zA-Z/_-.]*' | sort | uniq

config/deviceinformation/config.xml
config/dialup/config.xml
config/dialup/connectmode.xml
config/firewall/config.xml
config/global/config.xml
config/global/languagelist.xml
config/global/net-type.xml
config/network/net-mode.xml
config/network/networkband_
config/network/networkmode.xml
config/pcassistant/config.xml
config/pincode/config.xml
config/sms/config.xml
config/update/config.xml
config/wifi/configure.xml
config/wifi/countryChannel.xml

The missing part from the url link to networkband should be followed by the device name found in api/device/information.

Using the API

Now that we have a pretty good list of API calls and XML configuration files we can start coding our monitoring tool, or even make our own admin panel with a subset of functions we are only interested to use. For example you can login sending a POST request with the following XML data:

mrt:~/huawei$ curl "http://192.168.1.1/api/user/login"\
 --data "<?xml version=""1.0"" encoding=""UTF-8""?><request>\
<Username>admin</Username>\
<Password>$(echo -n 'admin password' | base64)</Password>\
</request>" --compressed

<?xml version="1.0" encoding="UTF-8"?><response>OK</response>

Just make sure you send your password encoded in base64. After that you will be able to run all API commands requiring authentification until your session timeout or you log out:

mrt:~/huawei$ curl "http://192.168.1.1/api/user/logout"\
 --data "<?xml version=""1.0"" encoding=""UTF-8""?><request>\
<Logout>1</Logout>\
</request>" --compressed

<?xml version="1.0" encoding="UTF-8"?><response>OK</response>

The API calls to make a data plan usage monitor don't need any authentification and are the following:

mrt:~/huawei$ curl "http://192.168.1.1/api/monitoring/month_statistics"

<?xml version="1.0" encoding="UTF-8"?>
<response>
<CurrentMonthDownload>21078592898</CurrentMonthDownload>
<CurrentMonthUpload>1413669553</CurrentMonthUpload>
<MonthDuration>2142739</MonthDuration>
<MonthLastClearTime>2015-4-28</MonthLastClearTime>
</response>

mrt:~/huawei$ curl "http://192.168.1.1/api/monitoring/start_date"

<?xml version="1.0" encoding="UTF-8"?>
<response>
<StartDay>28</StartDay>
<DataLimit>30GB</DataLimit>
<MonthThreshold>90</MonthThreshold>
<SetMonthData>1</SetMonthData>
</response>

Note: you need to setup your data plan in the modem admin panel in the statistics section by clicking the edit button. The monthly data plan value is what we will be using to calculate the usage percentage. This value will be called DataLimit in api/monitoring/start_date. The only thing you need to be careful is how DataLimit is set and convert all values to megabytes when doing calculation.

Calculating this percentage is just a matter of doing: ( (CurrentMonthDownload + CurrentMonthUpload) / DataLimit ) * 100

Here is a small python script to illustrate the procedure:

#!/usr/bin/env python
import urllib2, xml.etree.ElementTree as ET

MODEM_IP = '192.168.1.1'
API_PATH = '/api/'

# retrieve xml and if successful return root
def get_xml_root(api_call):
        try:
                xmlobj = urllib2.urlopen('http://'+MODEM_IP+API_PATH+api_call)
        except:
                print '[!] Error while making GET request for', api_call
                exit(1)
        return ET.parse(xmlobj).getroot()

# return value in specified xml tag
def get_value(root, tag):
        value = root.find(tag)
        if value is not None:
                return value.text
        else:
                print '[!] Error while retrieving value of', tag
                exit(1)

# return data plan usage percentage
def get_usage():
        # modem will not allow API calls if someone is logged in
        root = get_xml_root('user/state-login')
        state = int(get_value(root, 'State'))
        user = get_value(root, 'Username')
        # if state is -1 and username is not empty we cannot make the API call
        if (state == -1) and (user is not None):
                print '[!] Cannot make API calls: %s is logged in' % user
                exit(1)

        # retrieve download and upload traffic and convert them to MB
        root = get_xml_root('monitoring/month_statistics')
        month_download = int(int(get_value(root, 'CurrentMonthDownload'))/1048576)
        month_upload = int(int(get_value(root, 'CurrentMonthUpload'))/1048576)

        # retrieve data plan limit and convert to MB
        root = get_xml_root('monitoring/start_date')
        data_limit = get_value(root, 'DataLimit')

        # convert DataLimit suffix GB to MB
        if data_limit[2:] == 'GB':
                data_limit = int(data_limit[:-2])*1024
        else:
                data_limit = int(data_limit[:-2])

        # return percentage
        return int(round((float(month_upload + month_download) / data_limit) * 100))


print 'DATA PLAN USAGE: {0}%'.format(get_usage())

And here is the output when calling the script:

mrt:~/huawei/mirror/example$ ./get_usage.py

DATA PLAN USAGE: 71%

Here is another version showing signal strength, battery percentage, number of current wifi users and data plan usage percentage:

#!/usr/bin/env python
import urllib2, xml.etree.ElementTree as ET

MODEM_IP = '192.168.1.1'
API_PATH = '/api/'
PREV_API = ''
PREV_OBJ = None

# retrieve xml and if successful return root
def get_xml_root(api_call):
        # check previous call to avoid making multiple identical GET requests
        global PREV_API
        global PREV_OBJ
        if api_call == PREV_API:
                return PREV_OBJ
        try:
                xmlobj = urllib2.urlopen('http://'+MODEM_IP+API_PATH+api_call)
        except:
                print '[!] Error while making GET request for', api_call
                exit(1)
        # save current API call and XML obj
        PREV_API = api_call
        PREV_OBJ = ET.parse(xmlobj).getroot()
        return PREV_OBJ

# return value in specified xml tag
def get_value(root, tag):
        value = root.find(tag)
        if value is not None:
                return value.text
        else:
                print '[!] Error while retrieving value of', tag
                exit(1)

# return data plan usage percentage
def get_usage():
        # modem will not allow API calls if someone is logged in
        root = get_xml_root('user/state-login')
        state = int(get_value(root, 'State'))
        user = get_value(root, 'Username')
        # if state is -1 and username is not empty we cannot make the API call
        if (state == -1) and (user is not None):
                print '[!] Cannot make API calls: %s is logged in' % user
                exit(1)

        # retrieve download and upload traffic and convert them to MB
        root = get_xml_root('monitoring/month_statistics')
        month_download = int(int(get_value(root, 'CurrentMonthDownload'))/1048576)
        month_upload = int(int(get_value(root, 'CurrentMonthUpload'))/1048576)

        # retrieve data plan limit and convert to MB
        root = get_xml_root('monitoring/start_date')
        data_limit = get_value(root, 'DataLimit')

        # convert DataLimit suffix GB to MB
        if data_limit[2:] == 'GB':
                data_limit = int(data_limit[:-2])*1024
        else:
                data_limit = int(data_limit[:-2])

        # return percentage
        return int(round((float(month_upload + month_download) / data_limit) * 100))

# return signal strength percentage
def get_signal():
        root = get_xml_root('monitoring/status')
        return int(get_value(root, 'SignalStrength'))

# return battery percentage
def get_battery():
        root = get_xml_root('monitoring/status')
        return int(get_value(root, 'BatteryPercent'))

# return number of current wifi users
def get_wifi_users():
        root = get_xml_root('monitoring/status')
        return int(get_value(root, 'CurrentWifiUser'))

print 'SIGNAL: {0}% | BATTERY: {1}% | WIFI USERS: {2}/10 | DATA PLAN USAGE: {3}%'.format(get_signal(),
                                                                                         get_battery(),
                                                                                         get_wifi_users(),
                                                                                         get_usage())

And the output of the script:

mrt:~/huawei/mirror/example$ ./get_stats.py

SIGNAL: 90% | BATTERY: 100% | WIFI USERS: 1/10 | DATA PLAN USAGE: 71%

If you are often in a terminal you could setup a cron job to run the script every 5 minutes and save the string in a file to finally include it in your PS1 prompt for example.

Making a dynamic system tray icon for Windows

I often have to use Windows for work related projects and I thought it would be nice to have a small icon in the system tray and dynamically show the value of the data plan usage percentage. I was also looking for a small project to code in assembly and thought it would be the perfect candidate for that since the logic behind it is not really complicated.

The color of the icon changes from green to red depending on the amount of traffic usage.

When you hover the icon you can see further statistics such as download and upload traffic usage along with the total data plan amount.

If something went wrong an error notification will popup, this is to prevent the user from thinking his data plan is still at the same value since the icon value will only change with a successful communication with the modem. The data plan usage will update every five minutes.

I used NASM and GoLink, you can find the documented source on github: https://github.com/mrt-prodz/Huawei-Data-Plan-Monitor

The only reason I'm not providing a binary is because of two Anti-Virus false flagging the file as malicious, not sure why exactly.
The final binary is around 6KB and use 1.2KB of memory.