Random GNU/Linux notes

Table of contents

Using git to manage /etc

I did the following on many of the various machines which I administrate: as root, go in /etc and run (umask 077 ; git init-db); then periodically (e.g., with a crontab) run the following script (again, as root):

#! /bin/sh

if [ "x$1" = "x" ]; then
  log_message="autocommit"
else
  log_message="$1"
fi

cd /etc

# Keep a list of installed packages (this is Debian-specific)
COLUMNS=160 dpkg -l > 00PACKAGES

# Also keep a list of all files present, with full metadata
find /etc \( -name .git -o -name 00FILES \) -prune -o -print \
 | sort | xargs stat -c '%A %5h %6u %6g %12s %y %n' > 00FILES

# Make sure all files are in index
git ls-files -c -d -o -z | git update-index --add --remove -z --stdin

git commit -m "$log_message" -n

# Not sure the following is really a good idea...
git repack -d

Example uses:

git whatchanged -p X11/xorg.conf
what changes were made to the X config file
git diff @{2007-09-01} master -- 00FILES
show differences in list of files between two dates
git cat-file -p @{2007-09-01}:passwd
show the passwd file as it stood on 2007-09-01

To send to a remote machine for backup, use git init-db on some /directory and then run git push ssh://remote.machine/directory/ refs/heads/master:refs/heads/here (where here is the branch name to be used to identify the local machine). Conversely, one can fetch the local files on the remote machine with git fetch here provided .git/remotes was created containing:

URL: ssh://local.machine/etc/
Pull: refs/heads/master:refs/heads/here

Using make-kpkg to compile a Linux kernel (Debian)

Extract kernel in /usr/src/linux-version-local (e.g., /usr/src/linux-2.6.17.11-machinename with all appropriate patches. Copy some config file to .config (usually starting from the previous version).

YYYYMMDD="`date +%Y%m%d`"
make-kpkg --revision=custom.$YYYYMMDD --append-to-version=-local \
  --config oldconfig configure

(or use menuconfig if preferred)… Edit debian/control (change maintainer) and debian/changelog (wipe and restart with previously compiled version as template; be sure to preserve the first line, however! it contains the version number).

MAKEFLAGS="CC=gcc-4.0" CONCURRENCY_LEVEL="2" make-kpkg \
  --revision=custom.$YYYYMMDD --append-to-version=-local \
  --rootcmd fakeroot kernel_image kernel_headers

(Of course, I use CONCURRENCY_LEVEL="2" because I have a dual-core proc.)

Compiling a single kernel module

Make sure the linux-headers-version package is installed so that /lib/modules/`uname -r`/build points to the right place. Then from the directory containing the module to be built:

make -C /lib/modules/`uname -r`/build CC="gcc-4.0" M=`pwd`

(the gcc version must match that used to compile the kernel itself, of course; if in doubt, use modinfo).

Some random GnuPG notes

To be asked the signature level when signing, add the (undocumented?) --ask-cert-level option along with --edit-key (used to be default, but it was thought to be too confusing).

To set preferences for hash algorithms &c., use --edit-key and then something like

setpref s10 s9 s8 s7 s3 s4 s2 h8 h10 h9 h11 h3 h2 z2 z3 z1 z0

(that's my preference: h8 means sha256 and so on; maybe now they can be entered with more user-friendly names?).

Note on choice of cipher and hash: GnuPG chooses the cipher and hash functions according to the preferences in the recipient's self-signature (see above) and also the personal-cipher-preferences and personal-hash-preferences (on the command line or in .gnupg/options), but not beyond what the recipient authorizes. One can manually override with --cipher-algo and --digest-algo. But it's probably not a good idea. (The same holds for the compression functions.)

Note on DSA2: Currently (version 1.4.5), GnuPG prefers, if it is possible (that is, if the key allows) to use the old DSA standard, which limits DSA keys to 1024 bits and hash functions to 160 bits. The new DSA2 standard, which is not well supported outside GnuPG, must be explicitly requested using enable-dsa2 (on the command line or in the config file). One can also explicitly request a larger hash function, e.g., using --digest-algo SHA256.

Note about GnuPG v2: Currently it has no support for SHA224 (that's a bit odd). It will get it in the libgcrypt stable version 1.4.x (currently it is in the unstable version 1.3.x). The following signature currently cannot be decoded with GnuPG v2:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA224

It would seem that GnuPG2 does not have the SHA224 digest algo!
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
  
iD8DBQFG68GLDZNkJgsnkN4RC4MAAKCsuSnydZSzLQfl/rf6WcoZpUczOgCfXsmC
yiS5H8EOb5BnH2DLmWXuz38=
=cSjt
-----END PGP SIGNATURE-----

Linux double network interface µHOWTO

[Originally published in my blog.]

This took me a while to figure out, so I might as well post it here in case it's of use to anyone. Assume you have two network interfaces, say eth0 and ppp0, with two totally unrelated IP addresses, say 257.42.0.18 for eth0 (yes, I know, 257 is impossible, I'm just choosing this to represent a totally arbitrary address) and 333.64.17.29 for ppp0. You wish to use one interface for certain connections and the other for others (there can be plenty of different reasons for that: maybe one connection is faster but has a stupid firewall so it can't be used always). Now if you can decide which connection goes where in function of the destination host('s IP address or network), then it's easy: just configure your routing tables appropriately. But what happens if you wish, for example, all outbound connections to TCP port 80 to go through eth0 and all others to go through ppp0? We need a little more work there, and a little magic, but thanks to the mutant features of the Linux network stack, using iptables and iproute2, it is possible. Here's a sample of the command lines that might be useful (just a guideline, of course: don't ever copy them blindly, please learn about the programs and understand what each line does), at least if the host is not a router:

# Ordinary route is via ppp0 (this should probably be done as pppd starts):
route add defaut gw 333.64.17.1 dev ppp0
# Routing table 201 (say) is through eth0 (gateway is 257.42.0.1, say):
ip route add 257.42.0.0/24 dev eth0 scope link src 257.42.0.18 table 201
ip route add default via 257.42.0.1 dev eth0 table 201
# Use routing table 201 for marked packets:
ip rule add fwmark 1 table 201
# Now set up iptable rule to mark packets destined for eth0:
iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 80 -j MARK --set-mark 1
# Lastly, we need to do some self-masquerading:
iptables -t nat -A POSTROUTING -s 333.64.17.29 -o eth0 -j SNAT --to-source 257.42.0.18

The last line probably deserves extra comments, because it is not at all obvious: it is needed because otherwise an outbound connection from the local host on port 80 will have local address 333.64.17.29 (as it appears on the ordinary routing table) whereas it is sent through the eth0 device, and this can't work (any router down the stream will reject it as not being meant for this route, or at best the return packets will come through the wrong interface).

If you're also trying to open listening (server) sockets on the eth0 interface, you also need something probably like this:

# Turn off entry route verification on incoming packets:
sysctl -w net.ipv4.conf.eth0.rp_filter=0
# Also mark local packets destined for eth0:
iptables -t mangle -A OUTPUT -s 257.42.0.18 -j MARK --set-mark 1

If your box also acts as a router (through some third interface eth1, say), then at the very least you need to duplicate the OUTPUT rules (for the mangle table) as PREROUTING ones and broaden the source address match on the rules that have one, but more complicated are probably desirable (of course, it all depends on what kind of addresses you have on eth1; I'll leave as an exercise for the interested reader the case where eth1 has private addresses and you wish to masquerade on forwarding…).

Linux TCPMSS clamping module for IPv6 (ip6tables)

No longer needed! There is now an xtables implementation of TCPMSS, which works both with IPv4 and with IPv6: CONFIG_NETFILTER_XT_TARGET_TCPMSS (module name xt_TCPMSS).

2007-01-14: Shamelessly copying Marc Boucher's implementation for IPv4, I wrote a TCPMSS target for ip6tables (Marc's original implementation for iptables is now a standard part of the kernel and iptables). Both the kernel patch (creating the ip6t_TCPMSS.ko module) and the accompanying iptables patch can be downloaded from my FTP site.

Userland ECC verification script for certain Intel (82x) chipsets

ECC memory does its task without any software support (provided it is enabled in the BIOS, something one tends to forget about, and provided the chipset supports it, of course!). However, it's nice to be able to query the status in software so as to learn about which errors have been corrected or detected.

The following simple script checks the ECC status on two Intel northbridges which I happen to have, namely the [82]975X (e.g., on the Asus P5W64 WS Pro motherboard), the 82955X (part of the 955X chipset, e.g., on the Asus P5WD2 Premium motherboard), and the older 82845 (part of the 845E chipset, e.g., on the Asus P4B533 motherboard). If you have a different but similar Intel chipset, you can probably hack a simple patch by comparing the Intel datasheet for your chipset (look for registers with names like ERRSTS and EAP) with that of the aforementioned two (see here for the [82]975X, here for the 82955X and here for the 82845); note that ID 0x277c8086 refers to the [82]975X, 0x27748086 to the 82955X and 0x1a308086 to the 82845.

#! /usr/local/bin/perl -w

# ecccheck.pl -- Check ECC status on the Intel 82845, 82955X and [82]975X.
# David Madore <david.madore@ens.fr> -- 2007-09-19 -- Public Domain
# Updated 2007-10-03 to add support for the 975X.

use strict;
use warnings;
use English '-no_match_vars';
use Getopt::Std;
use Sys::Syslog qw(:standard :macros);

die "must be run as root" if $EFFECTIVE_USER_ID;

my %opts;
# -c (clear): ignore reported ECC errors (use this once at boot, to reset)
# -r (read): read reported ECC errors without clearing them (just test)
# Normal use: once with -c at boot, then periodically without options.
getopts("cr", \%opts);

# Northbridge is supposed to be device 0 function 0 on PCI bus 0.
my $conf_file = "/sys/bus/pci/devices/0000:00:00.0/config";

sub readconf { # Read PCI configuration variable.
    # Force a reopen of the file, so no buffering will be done by Perl/libc.
    open F, "<", $conf_file;
    my $off = shift;
    my $len = shift;
    seek F, $off, 0;
    my $buf;
    read F, $buf, $len;
    close F;
    return $buf;
}

sub writeconf { # Write PCI configuration variable.
    open F, "+<", $conf_file;
    my $off = shift;
    my $data = shift;
    seek F, $off, 0;
    print F $data;
    close F;
}

my $id = unpack ("V", readconf(0, 4));
# 0x27748086 is the 82955X and 0x1a308086 is the 82845
# 0x277c8086 is the 975X
unless ( $id == 0x277c8086 || $id == 0x27748086 || $id == 0x1a308086 ) {
    die "unknown northbridge";
}

if ( $id == 0x1a308086
     && ( unpack("V", readconf(0x7c, 4)) & 0x300000 ) != 0x200000 ) {
    die "ECC disabled (or unknown mode)";
}

my $retcode = 0;
my $type;
my $pos;
my $chan;
my $syn;

while (1) {
    $type = unpack ("C", readconf(0xc8, 1)) & 3;
    exit 0 unless $type;
    if ( $id == 0x277c8086 || $id == 0x27748086 ) {
	my $v = unpack ("V", readconf(0x58, 4));
	# Error address
	$pos = ($v & 0xffffff80)
	    || ((unpack ("C", readconf(0xfc, 1)) & 1) << 32);
	$chan = $v & 1;
	# Error syndrome
	$syn = unpack ("C", readconf(0x5c, 1));
    } elsif ( $id == 0x1a308086 ) {
	# Error address
	$pos = (unpack ("V", readconf(0x8c, 4)) & 0x3ffffffe) << 2;
	# Error syndrome
	$syn = unpack ("C", readconf(0x86, 1));
    }
    # If the type has changed, reread values to prevent race-condition.
    my $type2 = unpack ("C", readconf(0xc8, 1)) & 3;
    last if $type2 == $type;
}

unless ( $opts{"r"} ) {
    # Clear the type bits we have just read (notice that there is no race here!).
    writeconf(0xc8, pack("C", $type));
}

if ( $type && ! $opts{"c"} ) {
    openlog("ecccheck.pl", 0, LOG_LOCAL0);
    sub printf_log {
	my $priority = shift;
	my $format = shift;
	my @args = @_;
	syslog $priority, $format, @args;
	printf $format, @args;
    }
    my $prio = ($type&2)?LOG_CRIT:LOG_ERR;
    printf_log LOG_ERR, "single-bit (corrected) ECC error\n" if $type & 1;
    printf_log LOG_CRIT, "MULTIPLE-bit (uncorrectable) ECC error\n" if $type & 2;
    printf_log $prio, "address: 0x%010x\n", $pos if defined($pos);
    printf_log $prio, "channel: %d\n", $chan if defined($chan);
    printf_log $prio, "syndrome: 0x%02x\n", $syn if defined($syn);
    closelog;
    exit 1;
}

To use this, simply run it—as root—once at boot time with -c as argument (to clear the ECC error flags) and then periodically (every 5 minutes, say) without arguments; the simplest is probably to use a crontab (so any error messages get sent by email). Nothing is printed in case of success (and it returns with error code 0).

Important note: If you use ECC, make sure to disable any quick boot (or similarly named) option in your BIOS. Otherwise your memory will not be initialized at boot and you will get loads of ECC errors (mostly at the top of available RAM, it seems) from unclean ECC bits. I had a hart time figuring this one out.

Creating a FreeDOS bootable USB stick

This is not directly Linux-related, but still useful: sometimes one needs to boot under DOS, typically to flash a firmware or something of the sort. FreeDOS can be used, but installing so as to make a USB stick (thumb drive) bootable isn't easy, and the following instructions are hard to find:

This worked for me. YMMV, of course.