newspaint

Documenting Problems That Were Difficult To Find The Answer To

Monthly Archives: April 2016

Adding an Ubuntu 16.04 Xenial LXC Container to Ubuntu 14.04 Trusty

Introduction

So you have an Ubuntu 14.04 Trusty Tahr installation and you want to try out Ubuntu 16.04 Xenial Xerus in a LXC container.

There are two things you have to know about Xenial Xerus running in LXC on Trusty:

  • Trusty’s LXC did not support systemd, the start-up process on which Xenial relies
  • Xenial is not going to work in LXC until you backport a later version of LXC into Trusty and install the lxcfs package

This article documents the problems you might encounter if you do not take the two steps above first.

To obtain the container you can try:

~$ sudo lxc-create -n xtest -t download -- --dist ubuntu --release xenial --arch amd64

Which, if successful, prints out the following message:

You just created an Ubuntu container (release=xenial, arch=amd64, variant=default)

To enable sshd, run: apt-get install openssh-server

For security reason, container images ship without user accounts
and without a root password.

Use lxc-attach or chroot directly into the rootfs to set a root password
or create user accounts.

At this point you should skip to the end to:

  • add the backported LXC support because Ubuntu Trusty LXC did not support containers with systemd
  • install the lxcfs package which is also required for systemd to start

We can start this container but it does not have networking or user accounts:

~$ sudo lxc-start -d -n xtest
~$ sudo lxc-ls -f
NAME           STATE    IPV4        IPV6  AUTOSTART  
---------------------------------------------------
xtest          RUNNING  -           -     NO         

~$ sudo lxc-attach -n xtest
root@xtest:/# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 16.04 LTS
Release:        16.04
Codename:       xenial
root@xtest:/# exit
exit
~$

It is possible to shutdown the container but it takes a full minute:

~$ time sudo lxc-stop -n xtest

real    1m0.118s
user    0m0.003s
sys     0m0.009s

You can get a list of installed services:

root@xtest:/# service --status-all
 [ - ]  bootmisc.sh
 [ - ]  checkfs.sh
 [ - ]  checkroot-bootclean.sh
 [ - ]  checkroot.sh
 [ - ]  console-setup
 [ - ]  cron
 [ - ]  hostname.sh
 [ ? ]  hwclock.sh
 [ - ]  killprocs
 [ ? ]  kmod
 [ - ]  mountall-bootclean.sh
 [ - ]  mountall.sh
 [ - ]  mountdevsubfs.sh
 [ - ]  mountkernfs.sh
 [ - ]  mountnfs-bootclean.sh
 [ - ]  mountnfs.sh
 [ ? ]  networking
 [ ? ]  ondemand
 [ - ]  procps
 [ - ]  rc.local
 [ + ]  resolvconf
 [ - ]  rsyslog
 [ - ]  sendsigs
 [ + ]  udev
 [ - ]  umountfs
 [ - ]  umountnfs.sh
 [ - ]  umountroot
 [ - ]  urandom

You can get networking started (but name resolution will not work) by running:

root@xtest:/# service networking start
 * Configuring network interfaces...

… but this does not apparently configure a nameserver even though it will obtain a DHCP IP address for the eth0 interface.

Next you need to start the resolvconf service so that DNS can function given the nameserver provided by the DHCP configuration:

root@xtest:/# service resolvconf start
 * Setting up resolvconf...
   ...done.

root@xtest:/# ping www.google.com
PING www.google.com (216.58.210.36) 56(84) bytes of data.
64 bytes from lhr25s11-in-f4.1e100.net (216.58.210.36): icmp_seq=1 ttl=53 time=6.32 ms

At this point you should be able to run apt-get and install whatever packages you want.

You can restart the container using the /etc/init.d/reboot script:

root@xtest:/# /etc/init.d/reboot stop
 * Will now restart
Rebooting.

~$

You can try creating an /etc/inittab to give a runlevel to start into (3):

root@xtest:/# echo id:3:initdefault >/etc/inittab
root@xtest:/# /etc/init.d/reboot stop
 * Will now restart
Rebooting.

… but this will throw the following error on restart:

Failed to mount cgroup at /sys/fs/cgroup/systemd: Permission denied
[!!!!!!] Failed to mount API filesystems, freezing.
Freezing execution.

In the end you don’t want this /etc/inittab file. It wasn’t there when you obtained the container, you don’t want it there once you read the solution below.

Backporting LXC

Turns out this is because LXC on Trusty does not support systemd in containers. The solution is to install a backport of LXC:

  • add “deb http://archive.ubuntu.com/ubuntu/ trusty-backports main restricted multiverse universe” to /etc/apt/sources.list
  • sudo apt-get update
  • sudo apt-get -t trusty-backports install lxc

Now if you run lxc-start in non-daemon mode you get the following:

~$ sudo lxc-start -n xtest
lxc-start: utils.c: setproctitle: 1455 Invalid argument - setting cmdline failed

This setproctitle issue is harmless. The container will run anyway.

Installing the lxcfs Package

But we still have the issue of no console and no processes starting – not even networking or DNS – on boot.

You must install the lxcfs package according to this report quoting “systemd will refuse to boot without access to cgroups”.

~$ sudo apt-get install lxcfs
~$ sudo lxc-start -d -n xtest
~$ sudo lxc-stop -n xtest

Now, when you run lxc-start -n xtest you will have the networking and DNS started as expected.

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.