pfSense 2.0 Cookbook


Nice to see a copy of the pfSense 2.0 Cookbook land on my desk today. Packt Publishing asked me to do a pass of technical review for this a while ago, which was good fun. Matt Williamson has done a good job of creating a useful set of enumerated examples that should help anyone wanting to roll out 2.0, which has recently achieved Release Candidate 1 status.

Tags:

Faking single-factor authentication — ssh keys for HTTP Auth?


For customers of one of my services I provide ssh keys, that allow restricted access to the server environment. Now I want to run a web service in there as well, that will give them various status reports on the server; but I don’t want to manage another username/password pair for them. So the intention was to try to re-use the current ssh keys for authentication on the web service.

This was more difficult than I initially expected. Although both ssh and SSL have a similar key structure internally, there are a lot of differences in the implementation. It turns out to be possible to reverse the problem — I could create SSL keys and then patch OpenSSH to use them instead (see http://roumenpetrov.info/openssh/ for details), but I’d have to patch the client OpenSSH instances as well as the server, and that’s just painful.

Eventually I hit on an alternative scheme, to fake things. I will be running the web service on a localhost port, so only valid customers could access the app via ssh tunneling. The app doesn’t really need to do authentication, as the customer has already proved who they are by being able to use ssh. But identification is required — I don’t want the app to leak data between customers, of course. It turns out to be quite difficult to work out who a user is by looking backwards from a web app, when all you have is a port number that they connected from. And by ‘difficult’ I mean ‘not easily supported in most web app frameworks’.

So I wrote a small forward proxy that the customer will run and tunnel into. Because the customer is running it, I have access to their username and environment. I use these to create a fake HTTP Auth request. The real back-end web app just allows any basic auth request to succeed, and runs from there. Remember, the back end service doesn’t need to enforce authentication because I’ve done that via ssh already.

Here’s the Fake Auth Forward Proxy code — a short job for perl, HTTP::Daemon and LWP::UserAgent. I did try writing this with python’s Twisted first, but to be honest the CPAN documentation was so much better when it came to working out details … I spent ages trying to figure out Twisted, asking questions on IRC … as opposed to just reading CPAN cross-references and understanding things more quickly.

#!/usr/bin/env perl

# Fake Auth Forward Proxy
#
# This HTTP proxy listens to client requests, and gets the answer from
# another HTTP server. Before making the forward request, we construct
# a new HTTP Authentication header, based on the OS username running
# this program, and the SSH_CONNECTION environment variable, if available.
#
# Usage: fafp [listenport] [server URL]
# Defaults: fafp 8080 http://127.0.0.1:3000
#
# http://inode.co.nz/
# Copyright © Jim Cheetham  2011
# Version 1.0

use strict;
use HTTP::Daemon;
use LWP::UserAgent;

my $listenport = $ARGV[0] || 8080;
# Set the forward server, and the authentication that will be used
my $fserver = $ARGV[1] || “http://127.0.0.1:3000″;
my $fauser = getlogin || getpwuid($<) || "Kilroy";
my $fapass = $ENV{'SSH_CONNECTION'} || "localconnection";
# Set up the user agent that will be making onward requests
my $ua = LWP::UserAgent->new;
$ua->timeout(10);

# Set Reuse => 1 to avoid a long TIME_WAIT state on the socket when we finish
my $d = new HTTP::Daemon ( LocalPort => $listenport, Reuse => 1) or die “Failed to start HTTP Daemon: $@\n”;
print STDERR “Fake Auth Forward Proxy listening on “, $d->url, “\nSending queries to $fserver, auth=’$fauser:$fapass’\n”;

# Loop, accepting client communication
while (my $clientconn = $d->accept) {
            # Extract the HTTP::Request object
            while (my $req = $clientconn->get_request) {
                        # Alter the request target, and add fake authentication
                        $req->authorization_basic($fauser,$fapass);
                        $req->url($fserver.$req->url);
                        # Make the forward request (via $ua) and return it to our client.
                        $clientconn->send_response($ua->request($req));
                }
        $clientconn->close;
        undef($clientconn);
}

The end user sshs into the server to contact the web applications :-

$ ssh server -L 8080:localhost:8080 /usr/local/bin/fafp

and then just contacts http://localhost:8080 to get access to the app. Job done, with only a single authentication token, the ssh key.

Tags: ,

Dropping connections to a postgresql db


Sometimes, especially in development environments, you want to drop a whole database & then re-establish it. Unfortunately, if there are any current connections to the postgresql db, you can’t. Postgresql doesn’t give you a way to selectively drop client connections — you can stop the whole db server, but that will affect every database in there, not just the one you are working on.

Luckily, postgres will report on who has current connections, through the pg_stat_activity table. Our task is to use this table effectively to grab the process IDs of connections, and then to ask the OS to kill those connections for us. And to do this without granting too much power on the way; this will not “run as root with no password” via sudo, for example.

For those impatient people out there, here’s the script that does the job, the sudoers line needed to grant a reasonable minimum privilege, and how to use it. Then I can get on with explaining it …

Step 0 - The Finished Product

Put this script somewhere useful, like /usr/local/bin/kill-db-connections or $HOME/bin/kill-db-connections :-

#!/bin/sh
psql -U $1 -t -c "select procpid from pg_stat_activity \
  where datname='$2' and current_query not like \
  'select procpid from pg_stat_activity%';" \
| sudo -u postgres xargs kill

Now decide which user will be executing this - in a development box I have nearby, this is the user jenkins, related to the Jenkins Continuous Integration system (previously called Hudson). So in sudoers I add the following restricted ability :-

jenkins ALL=(postgres) NOPASSWD: /usr/bin/xargs kill

Finally, we invoke this command with the username and database name as command arguments, and any user password as an environment variable …

PGPASSWORD="userpw" kill-db-connection testuser testdb

And that’s all we need. Now read on for the detail of each step …

Step 1 - Identify the connections

The pg_stat_activity table in postgresql stores a lot of detail about the connections to the database and what they are doing. When we look at this table we are interested in the datname column, which shows the database a client is connected to (we are assuming that a busy system has multiple databases under postgresql, which is why we are not just shutting down the whole server to get rid of unwanted connections), the procpid column which gives us the OS process ID of the connection, and the current_query column, which I’ll explain in a moment.

Lets just query those three and see what we get (my database name is ‘dev’ in this example) :-

dev=> select datname,procpid,current_query
dev-> from pg_stat_activity;
 datname  | procpid |                        current_query
----------+---------+-------------------------------------------------------------
 dev |    4865 | select datname,procpid,current_query from pg_stat_activity;
 dev |    2200 |
 dev |    4538 |
 dev |    2257 |
 dev |    2270 |
 dev |    3926 |
 dev |    5394 |
prod |    4528 |
(8 rows)

Ok, so we can see 8 connections — and one of those is actually our current query! More worryingly, one of them is a connection to a different database, and we should leave this one alone. We need to refine our query a little — select only the ‘dev’ database, and exclude our own query.

dev=> select datname,procpid,current_query
dev-> from pg_stat_activity where datname = 'dev'
dev-> and current_query not like 'select datname,procpid,current_query from pg_stat_activity%';
 datname  | procpid | current_query
----------+---------+---------------
 dev |    2200 |
 dev |    4538 |
 dev |    2257 |
 dev |    2270 |
 dev |    3926 |
 dev |    5394 |
(6 rows)

That’s better! I excluded our own query by specifying enough of our own actual query using LIKE to avoid any normal db user. I couldn’t specify the whole query, because that would end up including the LIKE clause itself … and that way lies recursive madness.

So now we have the list of connections that we are interested in. We can now forget about the datname and current_query colums on output, because we have confidence in our main query. When we drop these from the main query we need to also modify the LIKE clause to match …

dev=> select procpid from pg_stat_activity
dev-> where datname = 'dev'
dev-> and current_query not like 'select procpid from pg_stat_activity%';
 procpid
---------
    2200
    4538
    2257
    2270
    3926
    5394
(6 rows)

Step 2 - Just the bare minimum data please

We need to step outside the postgresql prompt now, in order to process this output from the shell. The first step is to work out what this needs to look like to be a ’single command’ rather than an interactive session.

We can call the psql program with the option ‘-c SQL_COMMAND’, but we also need to specify which user to log in as with ‘-U username’. Lets try this …

$ psql -U devuser \
> -c "select procpid from pg_stat_activity where datname='dev' \
> and current_query not like 'select procpid from pg_stat_activity%';"
Password for user devuser:

Oh, that’s not very helpful. Automating this won’t work if I have to type in a password every time it gets used. We have two approaches — allow this user to log in to postgresql without providing a password, or store the actual password in the script.

The correct approach is to set up a user that is allowed to run this query, and only this query, and to do so without a password. After all, we can get most of this data from the OS as any user anyway. But I’m guessing that if you knew how to do that properly you wouldn’t need to read this page! And I don’t want to start describing how to modify postgresl authentication to allow this, we have enough to cover anyway. So providing the password from the script is the way we’re going. Just make sure that the script file is stored securely, with restricted permissions to stop just anyone from reading it.

So, we can provide the password by populating the PGPASSWORD environment variable when we call the psql command :-

$ PGPASSWORD="userpw" psql -U devuser \
> -c "select procpid from pg_stat_activity where datname='dev' \
> and current_query not like 'select procpid from pg_stat_activity%';"
 procpid
---------
    2200
    4538
    2257
    2270
    3926
    5394
(6 rows)

That’s good. Now we need to ask psql to stop printing the column name at the top and the number of matching rows at the bottom, using the ‘-t’ option.

$ PGPASSWORD="userpw" psql -U devuser -t \
> -c "select procpid from pg_stat_activity where datname='dev' \
> and current_query not like 'select procpid from pg_stat_activity%';"
    2200
    4538
    2257
    2270
    3926
    5394

So now we have a simple and clean list of the connections that we want to terminate. Time to move on.

Step 3 - Terminate the connections

Lets have a look at one of those process IDs, and see what we can see.

$ ps -fp 2200
UID        PID  PPID  C STIME TTY          TIME CMD
postgres  2200 17444  0 09:22 ?        00:00:00 postgres: dev dev 203.97.121.211(44619) idle

It is owned by the system user ‘postgres’. If we want to kill that process, we need to either have the same user ID, or to be root. Being root is not good, it is too easy to make mistakes and do bad things on a system. We want to be the user ‘postgres’ when we are killing the connections. But at the beginning of all this, we mentioned running this under the Jenkins CI system, and that means that we are probably running as the user ‘jenkins’. We will need to use the sudo command to allow us to switch users for this. And we have already exposed one password to our script, can we avoid doing this again?

Yes, we can. sudo will allow us to run a specified command, as another user, without asking for a password. This is a potential security hole, but if you manage things well it becomes a powerful automation tool.

So, to allow ‘jenkins’ to run the command ‘kill’ as the user ‘postgres’, we add the following line to the sudoers configuration using visudo :-

jenkins ALL=(postgres) NOPASSWD: /usr/bin/kill

So we can now issue kill commands to these process IDs. We can also kill the whole postgresql server too, if we are not careful. How careful are you when writing new Jenkins tasks for your development server? How careful do you need to be?

I’m not going to answer those questions for you. What I am going to do it help you work your way through the list of process IDs from the pg_stat_activity table and get rid of them all quickly!

We could ask the shell to step through each line of the output, and send the kill signal to each one. That would look like this :-

$ for pid in $(PGPASSWORD="userpw" psql -U devuser ... blah blah)
> do
> sudo -u postgres kill $pid
> done

Yes, I really did say “blah blah” in there, there was too much to type and it all looks so very messy. That’s one reason to not use this approach — the code ends up messy, and messy code is bad code.

The other way is to use the xargs command, which is written specifically to make this kind of thing easy. xargs basically handles the whole ‘for;do;done’ loop for you in one go.

Lets test xargs with a simple printf call :-

$ PGPASSWORD="userpw" psql -U devuser -t \
> -c "select procpid from pg_stat_activity where datname='dev' \
> and current_query not like 'select procpid from pg_stat_activity%';" \
> | xargs printf 'pid=%s\n'
pid=2200
pid=4538
pid=2257
pid=2270
pid=3926
pid=5394

We can use xargs to call the kill command for us, too. Now we face another choice, to do with the use of the ’sudo -u postgres’ that we need — do we do this inside the xargs loop, or outside it? Both will work …

I’ve chosen to invoke the sudo first, and to run the xargs command as the postgres user. It is a little arbitrary, but in my way the jenkins user can no longer simply call ’sudo -u postgres kill …’. This might stop an accidental kill of the postgres server perhaps. It isn’t going to be difficult to work around, but it isn’t obvious. I’m not trying to utterly prevent the jenkins user from abusing their rights to kill postgres processes, but I am trying to avoid user mistakes.

So I will call sudo -u postgres xargs kill at the end of our long command line (and of course I have to change the sudoers config we used earlier).

$ PGPASSWORD="userpw" psql -U devuser -t \
> -c "select procpid from pg_stat_activity where datname='dev' \
> and current_query not like 'select procpid from pg_stat_activity%';" \
> | sudo -u postgres xargs kill

sudoers:

jenkins ALL=(postgres) NOPASSWD: /usr/bin/xargs kill

Step 4 - Generalising the solution

So now we need to take our huge command line, and pop it into a script for later use. While we’re doing that, we can clean up some of the fixed names we have used and make the solution more flexible.

First off, we will leave out the users password; this will be needed in scripts called directly by Jenkins, but our single-purpose ‘kill-db-connections’ script doesn’t need to handle it — after all, perhaps you set up postgresql acces permissions to allow a user to run this query with no password (tell me how you did it, please!)

Secondly, we won’t make a fixed assumption about the name of the database itself, or the username to connect as. We’ll grab these from the command-line when you run the script.

Thirdly, we won’t put in error checking and stuff like that. You won’t be using this script from the command-line, you will be integrating it into other scripts that, once tested, won’t be changed. So we can save time and effort here — and that’s another tradeoff between doing things ‘right’ and doing them ‘now’.

So, our script becomes :-

#!/bin/sh
psql -U $1 -t -c "select procpid from pg_stat_activity \
  where datname='$2' and current_query not like \
  'select procpid from pg_stat_activity%';" \
| sudo -u postgres xargs kill

In order for the sudo to work, we need ’sudoers’ to contain :-

jenkins ALL=(postgres) NOPASSWD: /usr/bin/xargs kill

And when we call this, we need to provide the user- and database-names as command arguments, and the user password as an environment variable :-

PGPASSWORD="userpw" kill-db-connection testuser testdb

Last thoughts

Is there a better way for all this? Well, perhaps if the postgresql server itself supported a command like ‘pg_ctl kill-db-clients datname’ we would be happy. Who wants to implement that, and submit the code to the postgresql project?

A better way to implement this specific solution, however, would be to mandate that the actual ‘kill-db-connection’ script were run as the postgres user. That way we avoid the granting of arbitrary ‘kill’ ability to the jenkins user, and as long as the script were protected by filesystem permissions to prevent ‘jenkins’ from altering it (and we spent more time being very defensive about the command-line argument inputs), we could be more restrictive in sudoers, with something like ‘jenkins ALL=(postgres) NOPASSWD: /usr/local/bin/kill-db-connections‘. I didn’t do that here, because I’m not that paranoid about the development server in question, and didn’t need to spend the extra time to make the script more defensive. However, in a larger environment you may not want to extend too much ‘trust’ to your automation systems.

Another improvement would be around the handling of the user password. I’m keeping this out of the command-line arguments so that it doesn’t show up in a standard process list; however putting it in an environment variable isn’t much better as it can still be extracted by any user — it’s just a little more obscured. The psql command will read data from $HOME/.pgpass so we could put data in there — however when you are running from a system user account like ‘jenkins’ it isn’t obvious just where $HOME actually is, and you may be switching between an arbitrary number of different database user accounts anyway. If you really need to exercise more care over passwords (and you should!) then you need to do a little more research. Talk to a real postgresql administrator, not me!

Tags: ,

sshfs options in fstab


When I configure something on a server, I like to be able to control all the options explicitly. I’m not a fan of implicit configuration, it might work fine for single-user workstations and look far prettier, but things on a server should be completely unambiguous and have no hidden dependencies.

So, I wanted to set up an sshfs mount in /etc/fstab, but I needed to specify the private key to be used explicitly. Google results are full of people giving up and using scripts, or relying on $HOME/.ssh/id.dsa, or perhaps if they get carried away $HOME/.ssh/config (which is of course better).

Where command-line sshfs uses ‘-o IdentityFile=…‘, you can put that option directly into the fs_mntops column in /etc/fstab :-

sshfs#username@server:directory  mountdir  fuse  IdentityFile=/path/to/privatekey  0 0

You probably want to add a few other options to that column, such as ‘user’ and ‘noauto’. While you are at it, try adding this simple sanity check — have a file called ‘STOP’ in your unmounted mountpoint directory, and one called ‘GO’ in the mounted one; this makes a simple sanity check in scripts using the mount before they use up all your disk space … you may have to add the ‘nonempty’ option to get away with this one.

Tags: , , ,

Automating bzr over ssh without passwords


Well this job took me far far longer than should have been necessary. I wanted to automate the job of updating a bzr branch from a remote server, and the bzr+ssh:// method involved the least change to authentication at the far end.

What I thought was a slight complication was that I need to use an identity file to connect, not a password; and because this is going to come from a script invoked from cron I don’t have a specific user in mind, and no $HOME.

Unfortunately all the examples I can find for bzr talk about changing configs in $HOME, either for bzr itself or for ssh. And only for managing passwords; for keys the only comment was to use an SSH key agent, which isn’t ideal for me. That’s odd, I thought, well I’ll just have to specify things on the command-line, like we can with rsync & friends.

Well, no. There is no space in bzr for specifying options to the underlying ssh (which by default is paramiko). If you look hard in the documentation you might discover the BZR_SSH environment variable though … so perhaps you can set that to point to your own pre-loader script?

Again, no. You can only set BZR_SSH to a pre-defined set of words, and you cannot specify a specific command.

So the only working option that I could find was the nasty nasty method of creating a replacement command called ’ssh’, and to run bzr with this script at the front of PATH. Make sure that you don’t accidentally invoke the real ssh as just “ssh” within that script — use absolute filenames to prevent a forkbombing of your own machine.

This replacement ssh sets the correct username and identity file for the onward connection, and then does just whatever bzr wanted anyway. In order to invoke bzr correctly you need to specify a new PATH and the BZR_SSH setting …

The fake ssh script :-

#!/bin/sh
/usr/bin/ssh -l username \
    -i /full/path/to/identity_file \
    -o IdentitiesOnly=yes $*

The bzr invocation :-

PATH=/full/path/to/fake BZR_SSH=ssh \
    /usr/bin/bzr pull bzr+ssh://remote.host/repo

Tags: ,

Editing OpenOffice.org documents from the commandline


I’ve just put together a reasonably big document with a load of content copy/pasted from a web page (in this case, a firewall configuration GUI). Everything looked fine. Then I closed the VPN to the remote firewall … and all the icon images I’d pasted broke! When they were pasted in, by default only links to the graphics were added, instead of copies …

Luckily for me, I was using OpenOffice.org, a wordprocessor with a sensible internal file storage.

To fix my document all I needed to do was to unpack my .odt file into a temporary location, grab local copies of the icons (I populated ~/tmp/fwicons/ from a different firewall, as it happens) into the Pictures/ directory, change all the old image URLs into filenames (just by changing their prefix from http://something/... to Pictures/), and repack the changes into the original document.

OpenOffice.org detected my monkeying around internally, and insisted on running a quick “repair” over the file; when that was done I had all my lovely icons back again!

Here’s the complete command-line session …

jim@hex:~/ORIG$ mkdir ~/tmp/fixdoc
jim@hex:~/ORIG$ cp Network\ Configuration\ 1.0.odt /home/jim/tmp/fixdoc/
jim@hex:~/ORIG$ cd /home/jim/tmp/fixdoc/
jim@hex:~/tmp/fixdoc$ ls -l
total 80
-rw-r--r-- 1 jim jim 73421 2011-01-20 18:29 Network Configuration 1.0.odt
jim@hex:~/tmp/fixdoc$ mkdir unpack
jim@hex:~/tmp/fixdoc$ cd unpack

jim@hex:~/tmp/fixdoc/unpack$ unzip ../Network\ Configuration\ 1.0.odt
Archive:  ../Network Configuration 1.0.odt
 extracting: mimetype
 extracting: Pictures/10000201000001ED000001882B8674A7.png
  inflating: content.xml
  inflating: layout-cache
  inflating: manifest.rdf
  inflating: styles.xml
 extracting: meta.xml
  inflating: Thumbnails/thumbnail.png
  inflating: Configurations2/accelerator/current.xml
   creating: Configurations2/progressbar/
   creating: Configurations2/floater/
   creating: Configurations2/popupmenu/
   creating: Configurations2/menubar/
   creating: Configurations2/toolbar/
   creating: Configurations2/images/Bitmaps/
   creating: Configurations2/statusbar/
  inflating: settings.xml
  inflating: META-INF/manifest.xml   

jim@hex:~/tmp/fixdoc/unpack$ cp ~/tmp/fwicons/*gif Pictures

jim@hex:~/tmp/fixdoc/unpack$ perl -pi -e 's|http://firewall/themes/pfsense_ng/images/icons/|Pictures/|g' content.xml

jim@hex:~/tmp/fixdoc/unpack$ zip -r ../Network\ Configuration\ 1.0.odt Pictures/*
  adding: Pictures/icon_block_d.gif (deflated 15%)
  adding: Pictures/icon_block.gif (deflated 17%)
  adding: Pictures/icon_log_d.gif (deflated 28%)
  adding: Pictures/icon_log.gif (deflated 30%)
  adding: Pictures/icon_log_s.gif (deflated 29%)
  adding: Pictures/icon_pass_d.gif (deflated 7%)
  adding: Pictures/icon_pass.gif (deflated 9%)
  adding: Pictures/icon_reject_d.gif (deflated 13%)
  adding: Pictures/icon_reject.gif (deflated 15%)
jim@hex:~/tmp/fixdoc/unpack$ zip -r ../Network\ Configuration\ 1.0.odt content.xml
updating: content.xml (deflated 93%)

Tags: ,

The dangers of rsync into a full filesystem


I have an overly-complex and brittle chain of storage between my MythTV server and the machine that does playback onto the TV screen; one of the steps between them is a regular rsync of files from one machine to another — unfortunately disk usage on “the other” is not well controlled.

This means that I often end up with a full filesystem at one end, and I’m trying to stuff more files into it. And because I’m invoking rsync from cron instead of doing it properly and invoking it from a ‘while true; do …; sleep; done’ loop script, I end up with multiple rsync’s running.

I haven’t dissected the behaviour yet, but I’ve found a whole load of quite sizeable rsync temporary files (”.<filename>.randomsuffix”). I’m guessing that these exist because the filesystem keeps on filling during these rsync runs, and somehow rsync isn’t managing to delete the temp files when this happens.

I’d be happy to accept that rsync would normally try to delete temp files when things go wrong, but with potentially multiple simultaneous rsyncs banging on, over an NFS link to an embedded machine with low disk performance, I guess that there are some timeout issues; added to which occasionally I have to kill rsyncs at the top end of the chain.

So just a warning; if you rsync badly (i.e. over cron) into a filesystem that is prone to filling up, please remember to look out for temp files while you are cleaning up!

XMonad is fun


The longer I spend with XMonad as a window manager, the more I like it. Now I’m running on multiple screens, the time saved by not manually moving windows around between screens, maximising and un-obscuring them is very noticeable.

I do still rely on the mouse a little too much though, and now I have 4 screens in a square arrangement its easy to lose sight of the pointer — expecially when using XMonad to open/switch/rearrange windows from the keyboard. I asked Ubuntu for a larger more visible pointer which helped, but now I’ve found an XMonad action that drags the mouse in to the focused window …
http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Actions-UpdatePointer.html

xmonad.hs :-

import XMonad.Actions.UpdatePointer
...
logHook = updatePointer (Relative 0.5 0.5)

Sorted. What shall I change next? …

4 screens with Ubuntu 10.10


After success in getting my 4 screen setup under Ubuntu 10.04 Lucid Lynx, I upgraded to Ubuntu 10.10 Maverick Meerkat. Quite a mistake — there is a Xinerama bug was an X.org bug that crashes X when running Qt and some other applications.

https://bugs.launchpad.net/ubuntu/+source/qt4-x11/+bug/650539

Despite being deprecated in favour of RandR, Ximerama remains the only way to properly multiscreen multiple graphics cards. Sadly it looks like a typo crept in during a recent code clean-up in Xorg. The Arch Linux community found the fault and talked to upstream (https://bbs.archlinux.org/viewtopic.php?pid=841628) which resulted in an accepted patch to Xorg (http://lists.x.org/archives/xorg-devel/2010-October/014150.html)

Now we just have to wait for an update to the Ubuntu packages. Until that lands, I can’t run multiscreen on Ubuntu 10.10, because I need many of the affected apps in my workflow. And now I’ve invested in a multi-arm monitor stand system (see http://integinternational.com/modular-monitor-arms/, a New Zealand manufactured item) I really don’t want to go back to just two screens …

Update

As of 3 December, Ubuntu have released the fix to the xorg-server package in 10.10.

Tags: , ,

4 screens with ATI Radeon on Ubuntu 10.04


I now have a desktop PC with two ATI Radeon 4670 video cards (both dual-port output), driving 4 physical screens under Ubuntu 10.04 LTS.

Sadly, the Ubuntu standard tools only detect one video card (despite the CrossFireX connection between them), so all I can have is dual screen.

However, this can be fixed by just manually specifying the X.org configuration — and given that X.org has very well developed discovery tools these days I don’t have to get in to too much detail.

There is a very capable Open Source driver for the ATI Radeon cards, known as “radeon“. This lets me set up an X “Screen” for each separate output of the cards using the “ZaphodHeads” option — the CrossFireX connection means that although there are two different PCI addresses for the cards, the DVI ports are numbered as if there were only one. Then I use the Xinerama extension to glue the four screens together into one big desktop.

So, in my X.org config, I describe the 4 “Devices”, add 4 “Screens” by connecting each one to a Device, and finally provide a “ServerLayout” to place them in order (this is now dependent on which physical monitors are physically cabled to which output port). The relationship between DVI port numbers, physical output ports and PCI Bus IDs was discovered by experimentation, and documented by sticky labels on the back of the PC.

/etc/X11/xorg.conf

Section "ServerLayout"
    Identifier  "SL0"
    Option      "Xinerama" "on"
    Screen      "S0" 0 0
    Screen      "S1" RightOf "S0"
    Screen      "S2" LeftOf "S0"
    Screen      "S3" LeftOf "S2"
EndSection

Section "Device"
    # Radeon DVI-0 is top left (viewed from front of case, looking back)
    Identifier  "D0"
    Driver      "radeon"
    BusID       "PCI:9:0:0"
    Option      "ZaphodHeads" "DVI-0"
    Screen      0
EndSection
Section "Device"
    # Radeon DVI-1 is top right
    Identifier  "D1"
    Driver      "radeon"
    BusID       "PCI:9:0:0"
    Option      "ZaphodHeads" "DVI-1"
    Screen      1
EndSection
Section "Device"
    # Radeon DVI-2 is bottom left
    Identifier  "D2"
    Driver      "radeon"
    BusID       "PCI:8:0:0"
    Option      "ZaphodHeads" "DVI-2"
    Screen      0
EndSection
Section "Device"
    # Radeon DVI-3 is bottom right
    Identifier  "D3"
    Driver      "radeon"
    BusID       "PCI:8:0:0"
    Option      "ZaphodHeads" "DVI-3"
    Screen      1
EndSection

Section "Screen"
    Identifier  "S0"
    Device      "D0"
EndSection
Section "Screen"
    Identifier  "S1"
    Device      "D1"
EndSection
Section "Screen"
    Identifier  "S2"
    Device      "D2"
EndSection
Section "Screen"
    Identifier  "S3"
    Device      "D3"
EndSection

Thanks are due to Daniel Reurich of Centurion Computer Technology, who chose the hardware based on my loose spec of “open source friendly, 4 screens for 2D desktop work”, and crafted the first xorg.conf to prove that everything would work.

Tags: , ,