Wednesday, February 3, 2010

Multiple gateways on the same host

Having two gateways on the same host, where some processes send outgoing traffic over one gateway while the rest use the other gateway, requires a virtual network interface to be set up, and have a separate routing table so that all traffic to/from this virtual interface uses the secondary
routing table where the other gateway is in the default route.

First, I'll describe the steps one by one and later I'll explain how to make this setup persistent so that the system boots correctly the next time. Let's assume we have two gateways:

gw1 : 172.26.2.100
gw2 : 172.26.3.100

Create a virtual interface which will be used by processes that need to send traffic to gateway gw2:


$ ifconfig eth0:1 172.26.3.209

Create a definition and give a name to the new routing table (index 1, name 'test'):
$ echo "1 test" >> /etc/iproute2/rt_tables

Show the main routing table:

$ ip route show table main
172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
169.254.0.0/16 dev eth0 scope link metric 1002
default via 172.26.2.100 dev eth0

Clear the secondary routing table:

$ ip route flush table test

Copy all rules from main table to secondary table, but the default gateway

$ ip route show table main | egrep -Ev "^default" | while read route; do
ip route add table test $route
done

Add the gateway for the secondary routing table:

$ ip route add table test default via 172.26.3.100

List the secondary routing table:

$ ip route show table test
172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
169.254.0.0/16 dev eth0 scope link metric 1002
default via 172.26.3.100 dev eth0

Add a rule so that for any packet to/from the virtual interface, the secondary routing table is applied:

$ ip rule add from 172.267.3.209 lookup test
$ ip rule add to 172.267.3.209 lookup test

At this point, traffic originated from interface eth0:1 will use gateway gw2 (172.26.3.100) and traffic from interface eth0 enroutes via the default gateway gw1 (172.26.2.100). Try and compare:

$ traceroute -s 172.26.3.206 www.google.com
$ traceroute -s 172.26.3.209 www.google.com

In order to make the changes above persistent, edit/create the following files:

1. Add a description for the 'test' routing table (already done):

$ echo "1 test" >> /etc/iproute2/rt_tables

2. Create file '/etc/sysconfig/network-scripts/ifcfg-eth0:1' containing the
configuration of the virtual interface:

DEVICE=eth0:1
ONBOOT=yes
SEARCH="mydomain.biz"
DOMAIN="mydomain.biz"
DNS1=172.26.2.200
DNS2=172.26.2.201
BOOTPROTO=none
NETMASK=255.255.0.0
IPADDR=172.26.3.209
TYPE=Ethernet
USERCTL=no
PEERDNS=yes
IPV6INIT=no
NM_CONTROLLED=no

3. Create file '/etc/sysconfig/network-scripts/route-eth0:1' containing the 'test' routing table:

table test 172.26.0.0/16 dev eth0 proto kernel scope link src 172.26.3.206
table test 169.254.0.0/16 dev eth0 scope link metric 1002
table test default via 172.26.3.100

4. Create file '/etc/sysconfig/network-scripts/rule-eth0:1' containing the rules for the virtual interface:

from 172.26.3.209 lookup test
to 172.26.3.209 lookup test

The above explanations have been tested on a Fedora 10 distribution.

Friday, January 15, 2010

Extracting the contents of a ramdisk image

Sometimes it is necessary to examine the contents of a ramdisk image (initrd). An initrd image is basically a gzip-compressed cpio archive.

Here are the steps used to extract the files contained in a ramdisk:

gunzip initrd
mkdir tmp
cd tmp
cpio -i

Similarly, to build an initial ramdisk image from a directory:

cd tmp
find . | cpio -o -H newc | gzip -9 ../initrd.img

Where 'newc' is the name of the format used in the cpio archive.

In a Fedora distribution, the ramdisk init script is a nash shell-script. Nash is a very reduced footprint shell with built-in commands targetted to ramdisk operations such as device node creation, module loading, root device creation, root pivot, etc.

One of the most important nash commands is 'mkrootdev', which creates the root device specified as an argument. After this command is run, the "mount /sysroot" command is executed, which mounts the root filesystem on the root device.

If the root device is not correctly specified here, the following error will show up:

"mount: error mounting /dev/root on /sysroot as ext3: No such file or
directory"

mkinitrd is the responsible for creating the init script contained in a initrd image, and the name of the root device is obtained from the /etc/fstab file, when the kernel rpm is installed (not when it is built!). So, it is important to have a correct fstab file in the filesystem before the kernel is installed, otherwise mkinitrd won't be able to figure out the root device and set the correct arguments to the mkrootdev command.


Thursday, January 14, 2010

Add a new config patch to a Linux kernel RPM

Sometimes it is necessary to patch a kernel and rebuild the original rpm so that the installation of the new kernel and its modules is easier. I will not explain here how to patch a kernel, so I assume that we already have a patch available that has been tested and applies correctly.

We must have the source rpm of the kernel we intend to patch and rebuild. The following explanation has been tested on a kernel rpm from a Fedora 10 distro and, in particular, I will focus on patching the kernel config files rather than patching the source files.

First of all, an explanation of how kernel configuration is achieved by the spec file of a Fedora kernel rpm:

1. Some extra config files are provided along with the kernel source tarball. These files are named 'config-*' and are declared in the spec file using the 'sourceNN:' directives. The config files are hierchical, that is, the definitions add hierchichally one on top the other. For example, the configuration file for the i686 architecture is produced by adding the config-generic, config-x86-generic and config-i686 config files. The merge.pl perl script, also provided as an extra source file to the rpm, is responsible for doing the merge of the config files.

2. The config-* files are copied into the buildroot directory of the RPM, that is under BUILD/kernel- directory.

3. A 'make configs' rule is executed so that the config-* files are merged and produce a set of kernel-*.config files also in the build root directory. All configs are generated, even if they are not intended for the architecture that is going to be built.

4. At this point, all patches are applied using the 'ApplyPatch' macro in the spec file. Note that any patch that changes the kernel configuration must change the kernel*.config files, Patching the config-* files would not work as these are not taken into account for the build process after the patches are applied. Similarly, patching the .config file is not an option since this file will be overwritten by each kernel-*.config file during the build process.

5. The config files not intended for the architecture that is being built are deleted.

6. 'make oldconfig' is run on all the remaining kernel-*.config files, and the resulting config file is saved in the configs/ directory and removed from the root build directory. This is why the kernel-*.config files are not present even if we only run the patch stage of rpmbuild (-bp).

So, to have a patch that changes kernel config we have two options:

A. Edit the SOURCE/config-* files provided by the kernel source RPM and rebuild the source rpm with the new ones.

B. Generate a patch that modifies the kernel*.config files.

There some pros and cons to each option. Option B is more modular, so in case we choose to remove some kernel configuration in the future, all we have to do is remove the patch from the spec file, whereas if we use option A, the original source file is modified and it makes more difficult to revert some config changes.

On the contrary, option A is easier than option B because the kernel*.config files that must be patched are autogenerated during the rpm build process and are not available before then.

In order to apply option B, we should have a pristine kernel build root containing the patched kernel and the kernel*.config files so that these can be patched. Then, make a copy of the entire root and edit the kernel config files for the intended architectures, generate a patch by diff'ing both directories, copy it into the SOURCE directory and declare it in the SPEC file. Here is an example:

1. Generate a pristine build root of the linux kernel rpm

$ rpm -i kernel-2.6.27.5.src.rpm
$ cd ~/rpmbuild

Edit the SPEC file, by adding an 'exit 1' sentence after the last patch is applied (a call to the 'ApplyPatch' macro). This causes the patch process to be interrupted after the kernel*.config files are merged but before they are oldconfig'ed and moved to the configs/ directory. Perhaps there is a more elegant way to do this, but I don't know of it.

Extract the sources and apply the patches:

$ rpmbuild --target=i686 -bp SPECS/kernel.spec

Note the -bp option tells rpmbuild to stop after the source tarball is extracted and all patches are applied. Because we added the 'exit 1' statement, rpmbuild will return an error which of course can be ignored.

2. Make the changes in the kernel config files

$ cd BUILD/kernel-2.6.27.5
$ cp -a linux-2.6.27.5.i686 linux-2.6.27.5.i686.new
$ cd linux-2.6.27.5.i686.new

Edit each kernel-*.config files that applies to the intended architectures.

Changing the config files may be a bit painful, depending on the dependencies of the config items that we change. To make sure that the changes are ok, at the end we must run 'make nonint_oldconfig' with does some sort of check on our config file. Another option is to copy each kernel*.config file as .config and configure the kernel manually using 'make menuconfig', and then copying it back to the original file.

3. Generate the patch

$ cd ..
$ diff -urN linux-2.6.27.5.i686/ linux-2.6.27.5.i686.new/ > newpatch

Edit newpatch and make sure that only the changes to the kernel-*.config files are included. Then, rename and move the new patch to the SOURCES directory:

$ mv newpatch ../../SOURCES/linux-2.6.27.5-newconfig.patch

And declare the new patch in the SPEC file, by adding this line after the last call to the ApplyPatch macro:

ApplyPatch linux-2.6.27.5-newconfig.patch

# END OF PATCH APPLICATIONS

And, of course, remove the 'exit 1' statement.

4. Rebuild the RPM

As usual, run:

$ rpmbuild -ba --target=i686 SPECS/kernel.spec

After these steps, both a binary RPM and a source RPM will be present under the RPMS/i686 and SRPMS directories, respectively.

Of course, if we already have a patch for the kernel, all we have do is copy it to the SOURCES directory with a proper name, add it to the spec file by using the ApplyPatch macro, and rebuild the rpm (rpmbuild -ba).

Note the Fedora kernel spec file builds several flavours of the same kernel architecture, for instance: with or without PAE, debug, SMP, and several combinations of these. So many combinations may take a lot of time to build the rpms. If we are interested in only the base kernel without PAE, debug, etc, all we have to do is add the "--with baseonly" option to the rpmbuild command. Other variants are allowed, --with / --without pae, debug, etc....