Cover V14, i14

Article

sep2005.tar

Conserver: A Flexible, Mature Console Management System

Bryan Stansell

How would you like the flexibility to troubleshoot system problems from anywhere? How would you like the ability to have others watch what you are doing, and even help? And how would you like to have everything logged, so you can "go back in time" and review past events?

These are just some of the things conserver can do for you. Conserver is a daemon that runs on a Unix-like system to facilitate remote management of devices. The only prerequisite to taking advantage of conserver is the ability to connect to the "console" of the device you wish to manage. This "connection" can be via a direct serial connection, a TCP socket, or any command conserver can invoke. A TCP socket is traditionally used to connect to a terminal server, whereas random commands allow you to do anything -- from connecting to proprietary systems to integrating into an IPMI environment. The definition of "console" is really left up to your imagination.

Once connected, conserver logs everything displayed on the console of each device. This allows administrators, for example, to go back and see any error messages sent to a console immediately before a system crash, which is an enormous value when debugging problems remotely. Conserver also manages administrators accessing the consoles, entering commands, sending break signals, etc. It even supports multiple administrators accessing the same console simultaneously.

A Little History

Conserver was first publicly announced at LISA IV in 1990 [1]. Back then, it could only attach to local serial ports, had no concept of distributed conserver hosts, and had only the most basic command set. This simple, yet powerful, concept attracted the attention and help of others, and conserver grew into a fully distributed, command-rich, flexible piece of software by the end of 1993 [2].

It wasn't until 1995 that I actually used conserver for the first time. I was immediately hooked. Once I had the ability to go through console logs, share console connections with others (to mentor as well as be mentored), and respond to issues without running to the data center or driving in from home, I could never go back.

Like many pieces of software at that time, there was no central distribution of conserver. Conserver itself was actually becoming stagnant. The code we were running was a combination of the original work, patches posted to Usenet, and our own bug fixes and enhancements. Because of my interest in the code, I became the in-house maintainer of conserver. I took that responsibility to my next job and continued distributing the code to my circle of friends.

In 1999 I registered conserver.com, not only to make it easier for the folks I knew to get the software but also to offer it to the world. There had been a lot of work done on the code and since it didn't look like anyone else was actively maintaining it, I happily took on the task. Additionally, I created mailing lists for announcements and user support, which have drawn a great community of helpful and creative individuals.

The "original" (but heavily modified) version of the code continued until the 7.2.7 release in April, 2003. Then the "big rewrite" happened and, in September of 2003, version 8.0.0 was released.

It wasn't a total rewrite of code, but major portions were reworked. The configuration file parser was replaced, a generalized I/O subsystem was created (specifically to support SSL and buffer failed writes), the POSIX standard was adopted, and the client-server protocol extended, just to name a few. The result was what everyone had wanted for years: a tool that was flexible, reliable, and able to grow with the future.

What Can Conserver Do?

For me, any console management system should be able to do two basic things: log the console stream and allow multi-user access to the console. Anything else is just bells and whistles (although many things will become a personal requirement once you use them). Conserver is full of bells and whistles. After 15 years, it should be.

There are three major aspects of conserver to look at. The first is building the software with optional capabilities, since that determines how conserver will behave in your environment. The second thing is the configuration file, since most capabilities are enabled or disabled at runtime based on that file. The third, and perhaps the most important aspect, is the end-user experience -- using the conserver client.

Building Conserver

Conserver has been run successfully in a variety of Unix-like environments. This includes the Cygwin environment for Windows. My goal is to be able to run in any Unix-like environment that the GNU configure utility supports. I'm not sure if that will ever happen, but I do know all the common vendors work just fine.

The first step (aside from reading the documentation) is to run configure. You can use all the standard GNU configure options as well conserver-specific ones. Aside from overriding file locations, you can tell conserver to compile in support for PAM (Pluggable Authentication Module), tcp_wrappers (for further access restrictions), OpenSSL (to encrypt and authenticate client-server communication), and Unix domain sockets (to have all client-server communication contained on a single host).

There are also a couple of compile-time tuning options. The first (--with-maxmemb) tells conserver how many consoles to manage per-process. Because you could have thousands of consoles managed by conserver, it splits up the workload into groups and hands each group to a sub-process. This was mainly done to prevent a single process from running out of file descriptors, but it also allows for a certain level of parallelism. The second tunable option is the TCP connection timeout value (--with-timeout). If a console is defined as a direct TCP connection and the remote host is not responding, the system default timeout can be much longer than you would like. Conserver will abort any attempt that reaches the timeout and mark the console as "down". This happens without blocking, so all other consoles managed by that process can still be used.

The Configuration File

Up through version 7.2.7 the configuration file is very basic. It is similar to a standard password file -- a colon-separated list of items, one per console, with common information replicated throughout the file.

With version 8.0.0, the configuration file format became very powerful, especially for such a simple concept as a console server. It has been formatted into blocks of information, with an "include" facility to allow for inheritance of information and elimination of redundant data. These blocks of information are console definitions (of course), sets of named defaults, runtime settings, access lists, groups of users, and break strings (similar to macros). All blocks of information are of the form:

type name {
  key value;
  key value;
  ...
}
To give you an idea of what some of this looks like, here is a very simple, completely valid, configuration file (which could even be on a single line, if you like):

console simple {
  master localhost;
  type exec;
  rw *;
}
This creates a console named simple that is managed by the local conserver host. The console type is exec, which means it will run a command (in this case the default of /bin/sh -i) and all valid users have read-write access. Console items take a variety of key-value pairs, the most important of which tell conserver which host should manage the console (see the Distributed Conserver Hosts section), how to connect to the console, where to log the console data, and who can access the console.

Another console option is initcmd, which specifies which command should be attached to the console when it first comes "up" (i.e., when conserver successfully connects to the console). This allows you to authenticate to a terminal server or do any other activity required to get to the point where you are fully connected to the console. There is also a timer (initspintimer) and counter (initspinmax) to catch rapid successive failures of initcmd and stop conserver from thrashing.

Aside from protecting itself, conserver can also protect you by doing port calculations internally. This allows you to do things like specify the physical port number in the console definition and let conserver calculate the TCP port number or even local serial port name (using the devicesubst feature, which is described below). That can be quite handy when you are on the phone with a data center technician and want to say what physical port a machine is plugged into. Conserver uses the portbase, portinc, and port options to do the calculation.

Terminal servers use various TCP port number sequences for exposing their serial ports. Cisco devices typically use the formula 2000 + port, while old Xyplex devices use 2000 + 100 * port. Vendors can also vary this depending on whether you are connecting to telnet-protocol ports, ssh-protocol ports, or "raw" ports. Conserver uses the formula portbase + portinc * port to determine the final port number, where portbase defaults to 0 and portinc defaults to 1. For example, say you wanted to connect to port #4 of a Xyplex terminal server. If you set portbase to 2000 and portinc to 100, conserver would calculate the appropriate port when you used port 4; in the console definition.

Console items, when fully specified, end up having a lot of common information. This is where sets of defaults come in handy. They allow you to take all the common information and combine it into logical groups, which you can then use in a console definition (or other groups). You can group defaults in default items, which take the same key-value pairs as console items.

All default items are given a name. The default named * is special and automatically applied to all further console definitions. Consoles (or other default items) can explicitly reference a default by using the include option. Here's an example of a named default (ts-ssh) being included in the console definition for foo:

default ts-ssh {
  type exec; portbase 2000; portinc 1;
  exec /usr/local/bin/ssh -p P -l tsuser H;
  execsubst H=hs,P=Pd;
}
console foo { include ts-ssh; host ts1.conserver.com; port 4; }
This may look a bit complicated because I haven't explained the execsubst, devicesubst, and initsubst options. They perform substitutions on the exec (command-based consoles), device (local serial port), and initcmd options. The substitution allows you to replace characters in the option with new strings, formatted the way you prefer, based on the value of the host option, console name, port option, and calculated port value.

The above example substitutes all H characters with the host option (which is set in the console definition), and all P characters with decimal representation of the calculated port value. Thus you can define an abstract connection in a named default and only put console-specific information in the console definition. If the default was named * instead of ts-ssh, the include ts-ssh; could have been removed since the * default is automatically included in the console definition.

You can also change the runtime behavior of conserver via config items. These match the command-line options you can give conserver. Some things you can do are set file locations, change SSL settings (if you compiled in SSL support), disable the client redirection feature, and set the default access type.

With managed consoles, you must also think about access restrictions. Access to a console is governed by two levels of restrictions: server-specific and console-specific. A client must first be allowed to connect to the server, and then it must be allowed to connect to the particular console.

The first server-specific access restriction is governed by tcp_wrappers (if built into the code). The next access check is against the lists built with access configuration items. If no access list matches, the default access type (defined with the defaultaccess option in the config item) is used.

To build an access list, the name of the item should match the conserver host it applies to or * for all hosts. There are three types of access checks, as shown in this access list for conserver01.conserver.com:

access conserver01.conserver.com {
  trusted 127.0.0.1;
  allowed 192.168.0.0/16, 172.0.0.0/8;
  denied 192.0.0.0/8;
}
The first match is used, so connections from 127.0.0.1 are allowed in without a password; connections from 192.168.0.0/16 and 172.0.0.0/8 are allowed if a password check succeeds; and connections from the rest of 192.0.0.0/8 are denied. You can specify hostnames, specific IP addresses, or netblocks in the access lists.

Once past the server-level access list, the client must then be given access to the console via the rw and ro console options. As I showed previously, the rw option specifies who is allowed access to a console in read-write mode. ro specifies who is granted read-only access. Both options take a list of users, which can be user names, conserver group names, or system group names (/etc/group, etc).

Conserver group names are created with the group item. You give it a list of users, just like the rw and ro items, and can then use them wherever a list of users is needed. Here's an example:

group admin {
  users bryan, todd, @sysadmin;
}
This defines a group named admin, which consists of the users bryan, todd, and everyone in the system group sysadmin. To use the group, you just specify it like rw admin;.

If a client is to have access and a password check is necessary, conserver looks for a conserver.passwd file. That file should have user names and encrypted passwords of the form username:passwd. If the user exists and the password matches, access is granted. Two special keywords are allowed in the file. For username, you can use "*any*", to match all users. And for passwd you can use "*passwd*", which tells conserver to look up the password via PAM (if compiled in) or the standard system library. This way you can hard-code passwords for some users, allow others to use their system password, restrict access by locking an account, or do a variety of other combinations. If the conserver.passwd file doesn't exist, conserver will just query PAM or the system library.

One final aspect of the configuration file is its ability to be split into parts. This allows you to edit pieces individually, generate a portion based on external data, or organize things however you like. You simply specify a file that you'd like to include via the #include feature:

#include /etc/conserver-globals.cf
console one { include ts3.conserver.com; port 4; }
The conserver-globals.cf file should define the ts3.conserver.com default for the above to work. The #include directive must not be preceded by whitespace, otherwise it will be treated as a comment.

These are just some of the ways in which you can define defaults, consoles, users, and access lists. There are more examples of basic to complex configurations in the conserver.cf/samples directory of the distribution.

Distributed Conserver Hosts

Conserver also supports the idea of a distributed conserver configuration. The idea is that you would have multiple hosts running conserver, sharing a single configuration file. Each server would then redirect the client requests to the appropriate conserver host. That is what the master console option states -- which host should manage the console. One way to set this up would be to override the automatic default (*) as you define consoles:

default global { ... } # set generic defaults here

default * { include global; master conserver1.conserver.com; }

console foo { include ts1.conserver.com; port 2; }
console bar { include ts2.conserver.com; port 3; }

default * { include global; master conserver2.conserver.com; }

console zip { include ts1.conserver.com; port 4; }
console zap { include ts2.conserver.com; port 5; }
The first two consoles are managed by the conserver host conserver1, while the second two are managed by conserver2. Both conserver hosts know where all consoles are managed and can properly redirect clients to the proper conserver host.

There really is no difficulty in setting up a distributed configuration. You could even take independent conserver.cf files from multiple conserver hosts, concatenate them, and distribute them (assuming your master values were unique hostnames and not localhost). But using things such as named defaults and sharing the information between console definitions would, of course, make things much cleaner.

The Conserver Client

The conserver client can be used in two ways: to do global operations across all consoles, and to connect to a specific console. All global operations return you to your shell, whereas connecting to a console virtually links your terminal to the console.

Most of the global operations you can do are querying functions. You can ask who is attached to each console, if the consoles are up, how the consoles are connected (host and port numbers, local devices, etc.), and even get a formatted data dump of the state of the consoles for scripting purposes.

Aside from the querying functions, you can also do "bulk" operations. You can send a broadcast message to all users, send a message to specific users, disconnect users, and tell the conserver processes to shut down. The destructive functions require administrator rights (which are set with the admin option in config items), but the rest are available to all valid users.

When connecting directly to a console, the client does nothing but send all key sequences to the server and display all characters the server returns. There is an escape sequence, processed on the server, that allows you do other things, like bring the console up or down, send messages to other users on the console, take over read-write mode (only one user is allowed read-write access at a time), send break sequences, etc. To give you an idea of all the things you can do, here's the help screen you get when connected in read-write mode:

$ console simple
[Enter '^Ec?' for help]
[help]
 .    disconnect                      ;    move to another console
 a    attach read/write               b    send broadcast message
 c    toggle flow control             d    down a console
 e    change escape sequence          f    force attach read/write
 g    group info                      i    information dump
 L    toggle logging on/off           l?   break sequence list
 l0   send break per config file      l1-9 send specific break sequence
 m    display the message of the day  o    (re)open the tty and log file
 p    replay the last 60 lines        r    replay the last 20 lines
 s    spy read only                   u    show host status
 v    show version info               w    who is on this console
 x    show console baud info          z    suspend the connection
 |    attach local command            ?    print this message
 <cr> ignore/abort command            ^R   replay the last line
 \ooo send character by octal code
Most of the options should be relatively obvious as to what they do. Because consoles are organized into groups by the server, and because each process manages a group, many of the informational options (g, i, u, etc) show information about the entire group. Others operate on the console only (b, d, o, etc).

The break sequences deserve a little explanation. You can think of them as macros, with the ability to specify delays and serial breaks (which was the original need, hence the name). They are defined in the configuration file and available to all consoles. One of them is then defined to be the primary break sequence for the console and accessed via the "^Ecl0" escape sequence. This allows you to have different types of hardware, with different definitions of a "break" and all accessible via a single escape sequence. To illustrate these concepts, here are a couple of examples:

break 1 { string "\z"; }
break 2 { string "\r\d~\d^b"; delay 600; }
The break sequence for 1 is set to "\z", which means "send a serial break". The second break sequence has its delay set to 600 milliseconds with an action of: carriage return, delay, "~", delay, control-b.

As you can see, using the client is straightforward and provides a wide array of capabilities and information, which is exactly the way it should be.

Conclusion

By no means was this a complete description of everything conserver can do. I hope, however, that I've provided a good overview of why conserver is a robust, mature console management system.

The latest version of conserver can be found at http://www.conserver.com/ or one of its mirrors. The mailing lists and archives are available to help you make conserver a successful part of your environment.

Notes

1. Fine, Thomas A. and Romig, Steven M. "A Console Server". Ohio State University -- http://hea-www.harvard.edu/~fine/Tech/cs1.1/paper.asc

2. Kevin Braunsdorf of Purdue University and Robert Olson of Argonne National Laboratory were the significant contributors.

Bryan Stansell has been part of the system administration community in the San Francisco bay area for more than 10 years. When not dealing with pesky computers or hacking on open source software, he likes visiting strange, exotic lands, like Canada! Bryan can be contacted at: bryan@conserver.com.