Tracking LAN Uptime on My ASUS/Merlin Router – Router Script

The introduction and architecture to the LAN uptime system that I have added to my ASUS/Merlin router has been described in a previous post.  This post will focus on the script that runs on the router via a cron job that executes the script every 5 minutes.  A copy of the script is provided at the bottom of this post.

The first part of the script performs the checks on the networks using ping.  While ping works, I have noticed a couple of issues over time.

  1. Roku boxes, at least the ones I have, use WiFi for the remotes.  And due to the fact that the box only has one antenna, the wireless and the remote both have to use the same frequency.  So, the remotes do a great job interfering with the connection to the internet.  So, the best way to connect them to the internet when you can’t run a wire to them is to get a media router for the wireless connection and then you can use Ethernet from the media router to the Roku box.  This is not widely known and there are a lot of people that just seem to have a hard time getting a good wireless signal to their Roku boxes.
  2. I’ve noticed that pinging the media router doesn’t always work, but there is still signal flowing to the Roku, so for a true check of Roku connection I ping the media router and if that fails I also check the Roku box.  That network is considered down if they both fail.

After the pings is a section of initializing variables and helper functions for signing the request.  Then there are a few steps to creating the request and bundling it up into a variable called stringToSign.  Note, all data is in the query string so the payload is empty.

The next section of the script is about building a signed request.  I’ve talked about this before, but I will do it again without referencing the original in hopes that the two descriptions provide more information than just one.  A signed request is comprised of 5 components.

  1. A secret key used to encrypt
  2. A set of instructions for creating a signature
  3. A message
  4. Data relevant to the message (Sent date and time, etc)
  5. The signature

The sender of the request uses the first 4 components to create the 5th component (the signature).  Then the sender sends the receiver the last 4 components (not the secret key).  The reason this is a secure message is that the secret key is known on both sides and not communicated as part of the message. Since the receiver has the secret key already and received the last 4 components, it can recreate the signature.  If the sender’s signature matches the receiver’s signature, then the message is considered valid and it is processed.

So, why are there instructions.  Hashing once with a secret key is not that secure.  Hashing multiple times is much more secure.  The instructions are sent in headers called Authorization and Credential.  The Authorization header describes the type of hash function.  The Credential header describes the steps.  The value of the Credential header is:

  • ${ACCESSKEY}/${SDATE}/${REGION}/${SERVICE}/aws4_request

So the steps are:

  1. Obtain the secret key.
  2. The secret key is used to create a hash value of the short date (SDATE).
  3. The short date hash value is used to create a hash value of the region (REGION)
  4. The region hash value is used to create a hash value of the service (SERVICE)
  5. The service hash value is used to create a hash value of the signing version (aws4_service)
  6. The signing version hash value is used to create a hash value of the message (stringToSign)

The message hash value is the signature.  The receiver knows to use the ACCESSKEY to lookup it’s stored copy of the SECRETKEY.

The last nugget of information is the header X-Amz-Date.  The value of this must be the date and time the message was sent and if it is older than 15 minutes when the receiver gets it, the message is invalid and not processed.  This is a safety feature to prevent malicious attempts to intercept requests and resend them later.

Once the 5 components are compiled, the script uses curl to send the request to API Gateway.

Below is the script.


#!/bin/sh

WIREDHOST=<hostname to ping that is connected to the router via ethernet cable>
WIRELESS24HOST=<hostname to ping that is connected to the router via 2.4Ghz wireless>
WIRELESS5HOST1=<hostname of media router to Roku box>
WIRELESS5HOST2=<hostname of Roku box>

/bin/ping -c 3 ${WIREDHOST} > /dev/null 2>&1
if [ $? -eq 1 ]
then
   WIREDALIVE=0
else
   WIREDALIVE=1
fi

/bin/ping -c 3 ${WIRELESS24HOST} > /dev/null 2>&1
if [ $? -eq 1 ]
then
   WIRELESS24ALIVE=0
else
   WIRELESS24ALIVE=1
fi

/bin/ping -c 3 ${WIRELESS5HOST1} > /dev/null 2>&1
if [ $? -eq 1 ]
then
   /bin/ping -c 3 ${WIRELESS5HOST2} > /dev/null 2>&1
   if [ $? -eq 1 ]
   then
      WIRELESS5ALIVE=0
   else
      WIRELESS5ALIVE=1
   fi
   WIRELESS5HOST=${WIRELESS5HOST2}
else
   WIRELESS5ALIVE=1
   WIRELESS5HOST=${WIRELESS5HOST1}
fi


QUERY="wiredalive=${WIREDALIVE}&wiredhost=${WIREDHOST}&wireless24alive=${WIRELESS24ALIVE}&wireless24host=${WIRELESS24HOST}&wireless5alive=${WIRELESS5ALIVE}&wireless5host=${WIRELESS5HOST}"
SDATE=$(date -u +"%Y%m%d")
LDATE=$(date -u +"%Y%m%dT%H%M%SZ")
HOST=<domain name of the API Gateway>
URI=<Path to the API method>
ACCESSKEY=<AWS Access Key>
SECRETKEY=<AWS Secret Key>
REGION=<Region>
SERVICE=execute-api

hash () {
  /bin/echo -e -n "$1" | /usr/sbin/openssl dgst -sha256 -hex | /bin/sed 's/^.* //'
}

keyhash () {
  /bin/echo -e -n "$1" | /usr/sbin/openssl dgst -sha256 -hex -mac HMAC -macopt "key:AWS4${SECRETKEY}" | /bin/sed 's/^.* //'
}

rehash () {
  /bin/echo -e -n "$2" | /usr/sbin/openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:$1" | /bin/sed 's/^.* //'
}

payloadHash=$(hash "")

canonicalRequest="GET\n${URI}\n${QUERY}\nhost:${HOST}\nx-amz-date:${LDATE}\n\nhost;x-amz-date\n${payloadHash}"

canonicalRequestHash=$(hash "${canonicalRequest}")

stringToSign="AWS4-HMAC-SHA256\n${LDATE}\n${SDATE}/${REGION}/${SERVICE}/aws4_request\n${canonicalRequestHash}"

kDate=$(keyhash "${SDATE}")
kRegion=$(rehash "${kDate}" "${REGION}")
kService=$(rehash "${kRegion}" "${SERVICE}")
kSigning=$(rehash "${kService}" "aws4_request")
signature=$(rehash "${kSigning}" "${stringToSign}")

curl -s -X GET \
  -H "Host:${HOST}" \
  -H "X-Amz-Date:${LDATE}" \
  -H "Authorization:AWS4-HMAC-SHA256 Credential=${ACCESSKEY}/${SDATE}/${REGION}/${SERVICE}/aws4_request, SignedHeaders=host;x-amz-date, Signature=${signature}" \
  https://${HOST}${URI}?${QUERY} > /dev/null

exit 0