newspaint

Documenting Problems That Were Difficult To Find The Answer To

Getting Statistics from HG612 Revision 3B VDSL Modem Using Node

The HG612 Revision 3B VDSL modem commonly provided by BT OpenReach in the United Kingdom for fibre-to-the-cabinet (FTTC) connected households can be unlocked with alternative firmware.

For those modems that are unlocked they can be connected to on the modem’s default IP address of 192.168.1.1 with a username of admin, password of admin, using web (if enabled), telnet, and ssh.

Such a session entails getting into the shell, then running a Linux command to show the DSL statistics, like the following:

me@home:~$ ssh admin@192.168.1.1
admin@192.168.1.1's password: admin
PTY allocation request failed on channel 0

ATP>sh
sh


BusyBox v1.9.1 (2010-10-15 17:59:06 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

# xdslcmd info --stats
xdslcmd: ADSL driver and PHY status
Status: Showtime
Retrain Reason: 0
Max:    Upstream rate = 30992 Kbps, Downstream rate = 90516 Kbps
Path:   0, Upstream rate = 20000 Kbps, Downstream rate = 79999 Kbps
...
# exit
exit

ATP>exit
exit
exit from configuration console.
[6408] Jan 03 23:45:49 exit after auth (admin): child failed
Connection to 192.168.1.1 closed.

I wanted a way of dumping this information on a regular basis automatically but it wasn’t as simple as merely executing a command from the Linux command line, e.g. ssh admin@192.168.1.1 “sh\nxdslcmd info –stats\nexit\nexit”.

So I wrote the following Node.JS script to open a ssh connection to the modem, and execute the appropriate instructions, and dump the output from the xdslcmd command.

'use strict';

/*

  You will need to run the following in the directory you place this script:
    npm install ssh2
    npm install bluebird

*/

var Promise = require("bluebird");
var ssh2 = require("ssh2");

var modem_ip = "192.168.1.1";
var modem_user = "admin";
var modem_pass = "admin";

var debugging = false;

function debug( msg ) {
  if ( debugging ) {
    console.log( msg );
  }
}

function connect() {
  return new Promise( function( resolve, reject ) {
    var conn = new ssh2();

    conn.setMaxListeners(0);

    conn.on(
      'connect',
      function () {
        debug( "ssh connect" );
      }
    );

    conn.on(
      'ready',
      function () {
        debug( "ssh ready" );

        resolve( conn );
        return;
      }
    );

    conn.on(
      'error',
      function (err) {
        debug( "error: " + err );

        reject();
        return;
      }
    );

    conn.on(
      'end',
      function () {
        debug( "end" );

        //reject();
      }
    );

    conn.on(
      'close',
      function (had_error) {
        debug( "close: " + had_error );

        reject();
        return;
      }
    );

    conn.connect(
      {
        "host": modem_ip,
        "port": 22,
        "username": modem_user,
        "password": modem_pass,
        "debug": function ( msg ) { debug( msg ); },
        "algorithms": {
          "kex": [
            'ecdh-sha2-nistp256',
            'ecdh-sha2-nistp384',
            'ecdh-sha2-nistp521',
            'diffie-hellman-group-exchange-sha256',
            'diffie-hellman-group14-sha1',
            'diffie-hellman-group1-sha1', /* required for modem */
          ],
          "cipher": [
            'aes128-ctr',
            'aes192-ctr',
            'aes256-ctr',
            'aes128-gcm',
            'aes128-gcm@openssh.com',
            'aes256-gcm',
            'aes256-gcm@openssh.com',
            '3des-cbc', /* required for modem */
          ],
        },
      }
    );

    debug( "Starting..." );
  } );
}

function interactive( conn ) {
  return new Promise( function( resolve, reject ) {
    debug( "Starting shell..." );
    conn.exec( '',
      function( err, stream ) {
        if ( err ) {
          reject( err );
          return;
        }

        var data = "";

        stream.on(
          'close',
          function( code, signal ) {
            debug( "close stream " + code + " " + signal );
            conn.end();
            resolve( data );
            return;
          }
        );

        /* use a state machine */
        var stage = "ATPprompt";

        stream.on(
          'data',
          function ( chunk ) {
            debug( "Chunk: " + chunk );
            data += chunk;

            /* determine state we are in and if expected prompt is seen */
            if ( ( stage == "ATPprompt" ) && /^ATP>/m.test( data ) ) {
              debug( "writing sh" );
              data = ""; /* clear buffer */
              stream.write( "sh\n" );
              stage = "shprompt";
              return;
            }

            if ( ( stage == "shprompt" ) && /^#/m.test( data ) ) {
              debug( "writing xdslcmd info --stats" );
              data = ""; /* clear buffer */
              stream.write( "xdslcmd info --stats\n" );
              stage = "shprompt2";
              return;
            }

            if ( ( stage == "shprompt2" ) && /^#/m.test( data ) ) {
              debug( "writing exit" );
              console.log( data );
              data = ""; /* clear buffer */
              stream.write( "exit\n" );
              stage = "ATPprompt2";
              return;
            }

            if ( ( stage == "ATPprompt2" ) && /^ATP>/m.test( data ) ) {
              debug( "writing exit" );
              data = ""; /* clear buffer */
              stream.write( "exit\n" );
              stage = "none";

              /* we now expect connection to close, so resolve when we do */
              conn.on(
                'close',
                function (had_error) {
                  resolve();
                  return;
                }
              );

              return;
            }
          }
        );
      }
    );
  } );
}

/* set out abort timer in case something goes wrong */
setTimeout(
  function () {
    console.error( "ERROR: Timeout" );
    process.exit( 1 );
  },
  5000 /* 5.000 seconds */
);

/* main routine as a chain of promises */
connect().then(
  function ( conn ) {
    return interactive( conn );
  }
).then(
  function () {
    debug( "Done." );
    process.exit();
  }
).catch(
  function ( err ) {
    debug( "Failed to connect." );
    process.exit();
  }
);

Which can be run simply as node xdslcmd.js.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: