October 2007 Archives

One of the most powerful features in the Linux kernel is usage of multple routing tables and policy routing.  In normal routing, decisions are made simply based on the source and destination of the packet.  In certain instances, this might not be enough flexibility - we may need to make a different decision based upon what protocol we are using, for example.  Take this example to have SMTP routed differently than the rest of the traffic coming out of the box.

Good news is that the Linux routing code makes this fairly easy.  First, you have to add the table definition to /etc/iproute2/rt_tables, so that it looks something like this:

[root@centos4 ~]# cat /etc/iproute2/rt_tables
# reserved values
#255 local
#254 main
#253 default
#0 unspec
# local
#1 inr.ruhep
100 smtp

Then, you need to setup an IPTables rule to mark the traffic that you are interested in using the alternative gateway for:

iptables -t mangle -A OUTPUT -p tcp --dport 25 -j MARK --set-mark 0x2

Then you need to setup the ip routing rule and the default route in the alternative table:

ip rule add fwmark 0x2 table smtp.
ip route add default via table smtp

Now all SMTP traffic generated locally by the box will route out the gateway  Note, however, that a regular 'ip route' command doesn't show you what's going on here:

[root@centos4 ~]# ip route dev eth0 proto kernel scope link src dev eth0 scope link
default via dev eth0

Instead, we need to specify which table to look in for the route.  Remember adding the table to /etc/iproute2/rt_tables before?  That's for human conveinence in mapping the table name to a number

[root@centos4 ~]# ip route show table smtp
default via dev eth0

You can tell which rules and tables are in use via 'ip rule list'

[root@centos4 ~]# ip rule list
0: from all lookup local
32765: from all fwmark 0x2 lookup smtp
32766: from all lookup main
32767: from all lookup default

And finally, you can see what's in the mangle table by looking at iptables:

[root@centos4 ~]# iptables -t mangle -nvL
Chain PREROUTING (policy ACCEPT 2210 packets, 172K bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 2210 packets, 172K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 1351 packets, 153K bytes)
pkts bytes target prot opt in out source destination
0 0 MARK tcp -- * * tcp dpt:25 MARK set 0x2
Chain POSTROUTING (policy ACCEPT 1351 packets, 153K bytes)
pkts bytes target prot opt in out source destination

There's a brief introduction to policy routing in RHEL4! Special thanks to merlinthp over in #rhel for pointing me in the right direction here, and to the iproute2 documentation on policyrouting.org

Here's an interesting problem.

Apache, when used on a VxFS filesystem, will only serve files that are smaller than 256 bytes. You can manipulate said files any way you would like to using the operating system, however, Apache refuses to serve them, instead serving up zero-byte files. An strace will reveal that the read() syscall is returning EAGAIN.

Well, it seems from the VxFS release notes that the sendfile() syscall is not supported on VxFS filesystems. The simple solution is to add 'EnableSendfile Off' to the httpd.conf, and everything will be working fine again.

UPDATE - I have received word from Symantec support that this is scheduled to be corrected in 5.0MP2, but no timelines yet.

In a VMWare ESX guest, if you try and go configure VxVM (VERITAS Volume Manager), you'll get a rather cryptic error when you try to add disks:

Error: VxVM vxdisksetup ERROR V-5-2-1814 sde: Invalid disk device for 'cdsdisk' format

Some googling brought me here, where I found that VxVM only support cdsdisk on SCSI disks - wait a minute, VMWare only supports SCSI disks, right? Right, but fortunately there's a command to determine if the disks are to VxVM's liking for a CDS disk:

[root@host diag.d]# /etc/vx/diag.d/vxscsi -g sdb
Cannot get disk geometry on /dev/vx/rdmp/sdb !

Gack, it doesn't like me! Good thing is that you can go ahead and set it up manually like so:

[root@host ~]# /etc/vx/bin/vxdisksetup -i sdb format=sliced
[root@host ~]# /etc/vx/bin/vxdisksetup -i sdc format=sliced
[root@host ~]# /etc/vx/bin/vxdisksetup -i sdd format=sliced
[root@host ~]# /etc/vx/bin/vxdisksetup -i sde format=sliced
[root@host ~]# vxdg init datadg sdb sdc sdd sde cds=off

For reference, CDS is cross-platform data sharing. What this does is overcome endianness limitations in sharing disks from say a SPARC host to a Linux host. You have to use the fscdsconv utility to convert the filesystem on it, though.

In this mini-tutorial, I'm going to describe how to rebuild a RPM from a source RPM, or SRPM. Unlike some other tutorials out there, I'm going to demonstrate how to get the SRPM that you require, set up your build environment, edit the spec file to suit your requirements, and finally, build and install the resulting RPM. The specific example here demonstrates how to change the compile time options that Red Hat has shipped with the Apache web server.

Let's get some simple definitions out of the way first. RPM, contrary to popular belief, does NOT stand for Red Hat Package Manager. Instead, it is a recursive acronym for RPM Package Manager. Minor trivia out of the way, a RPM is a package produced via rpmbuild, and that can be installed with the rpm command. A SRPM, or source RPM, is the file that contains the source code, patches (more on those later), and the spec file. The spec file can be thought of as the recipe to build the package. It contains instructions on how to prepare the package for building, build it, and install it. The BuildRoot (you'll see me refer to it as $RPM_BUILD_ROOT, since that's the environment variable where the value is stored) is where the package is installed during the build process. Note that this should NOT be the final resting place of the package, but rather a clean directory (i.e. Empty prior to installing the package to it). Macros are also an important topic, since you will find them used extensively. They are used for variable expansion. You will see things in a spec file about %{_sysconfdir} for example. This macro expands to the system specific sysconf directory (/etc on Unix-derived systems). This is a very powerful facility, since the packager need not know the specifics of every system that the package may be compiled on. This macros are expanded at RPM build time, NOT install time.

It should be noted that following these instructions will invalidate the support that you may (or may not) have for these packages. In this example, we are going to be modifying the httpd RPM that is shipped with Red Hat Enterprise Linux. If the resulting Apache crashes, blows up your computer, or even eats all of the cheese in your house, don't call Red Hat for help – you're on your own here. Of course, if you're using a community distribution such as Fedora, this warning doesn't apply, as you're on your own all the way there :-).

First and most importantly, do NOT follow these instructions as root. Use a normal user, preferably one that is dedicated to building packages. The reason for this is simple. While RPM's that are provided by reputable software vendors such as Red Hat (who we are going to use in this example) provide high-quality spec files, you cannot depend on that to be the case. As the first thing that most well written RPM's do is to do a 'rm -rf $RPM_BUILD_ROOT - if that is not defined, it could result in removing everything on the system if executed as root! Certainly not the desired outcome.

Next, a brief introduction to RPM philosophy is in order, which helps to understand the components of a spec file. First is the concept of pristine sources. What this refers to is the fact that the upstream source must be exactly as you received it from the upstream project. For example, the Linux kernel for RHEL4 is 2.6.9-. In each of those builds, you will find the base 2.6.9 kernel.org source, plus literally thousands of patches to it.

The second basic tenet of RPM is reproducible builds. Someone else, given nothing other than RPM and your SRPM, should be capable of reproducing the same binary RPM that you did. Whatever beyond the standard C compiler is required to produce the RPM should be listed in the spec file as a BuildRequires. You should also not depend on how a specific build environment is set up in order to produce a build.
OK. enough with the theory and talk about RPM. Now let's actually get our hands dirty with it. The first thing that we have to do is set up our build environment. Remember how I said that you should not build as root? Unfortunately, the default RPM installation in RHEL doesn't really make it easy not to. (I understand that there is a script that's shipped with Fedora that will do all of this for you. The name of it eludes me, since I'm used to doing it all manually) The basic requirement is for a build tree underneath your home directory. Make a directory inside either your or your dedicated build user's home directory called build (or whatever you'd like, for the purpose of this example I'm going to assume that you used build, and that your home directory is /home/builder). Inside the build directory that you just created, make directories SRPMS, RPMS. SOURCES, BUILD, and SPECS. These directories must be capitalized. You also require a temporary directory for use during the process, it defaults to /var/tmp I think. Since lots of space may be required in this directory, and /var is typically small, I place it under the build directory. Following is a quick little one-liner that you can paste into the shell to do this.

for dir in SPECS SOURCES BUILD SRPMS RPMS tmp; do mkdir $dir ; done

Now that we have all of the directories created, we have to tell RPM that they exist and to use them. In fact, we are going to define some of the previously described macros here, %_topdir and %_tmppath. these macros are used internally by the RPM build process. In order to set them, you have to create the file ~/.rpmmacros, and put in it two lines:

%_topdir /home/builder/build
%_tmppath /home/builder/build/tmp

Now that we have our build environment set up, we need to find an SRPM to get. After all, you can't build from an SRPM unless you already have one, right? For the distribution that we are talking about here, Red Hat Enterprise Linux, the SRPM's can be found at ftp.redhat.com. Where to find them there is a little convoluted sometimes, so here's a mini-directory. Also, with the release of RHEL5, the directory structure has changed for that release. The source for updates (errata updates) to RHEL4 can be found at ftp://ftp.redhat.com/pub/redhat/linux/updates/enterprise/4AS/en/os/SRPMS, and the original OS distribution can be found at ftp://ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os/i386/SRPMS. Confused yet? I sure am. The good news is that Red Hat seemed to be confused as well, so the arrangement with RHEL5 is a little more sane. Rather than separating out updates from the base OS, they've lumped it all together. Makes a lot more sense to me and makes things easier to find. They're all at ftp://ftp.redhat.com/pub/redhat/linux/enterprise/5Server/en/os/SRPMS. You can replace 5Server with 5Client in that URL if you want. The packaging for RHEL5 has changed, but that's beyond the scope of this RPM tutorial.

For Fedora, they've changed their packaging for Fedora 7 as well, but they separate out the source from the updates still, just in different locations than they did in the past. Drives me crazy. The base OS for F7 can be found at ftp://download.fedora.redhat.com/pub/fedora/linux/releases/7/Everything/source/SRPMS or mirrors, and the updates can be found at ftp://download.fedora.redhat.com/pub/fedora/linux/updates/7/SRPMS.
For other distributions or repositories, there should be some documentation on where to find the source RPM files.

Now that we have a SRPM file, we need to extract it. This is done by simply installing the RPM. However. instead of installing as root as you would normally do, simply install as a normal user that has the setup mentioned above. There may be some error messages about users not existing (RHEL RPM's you will see errors about brewbuilder, and in Fedora RPM's, you'll see errors about kojibuilder). So where does all of this stuff go to? Why that directory structure that you so carefully created before!
The spec file will go in the SPECS directory, and the source files and patches will go in the SOURCES directory. You don't want to manually touch anything in the SOURCES directory.

Next, let's examine the format of a spec file in a little more detail. There are multiple sections to a spec file, some used more often than others. For the purpose of this example/tutorial, I'm only going to look at the required or most commonly used sections.

First, there's the preamble to the file. which contains various package metadata, such as the sources for the package, and the patches to all. All of these have to exist in the SOURCES directory, or the build will fail. When you update a package for any reason, you should update the Release field, and I always add some descriptive text to it so that I and other people know that this is not the package that came from the vendor.
Then you specify the license and group for the package. The license is fairly obvious (Apache Software License in this case, could be GPL, or in the instance of the kernel, a specific version of the GPL, GPLv2). The group is fairly obvious, and can technically be anything. However, the canonical list of groups for the distribution resides in /usr/share/doc/rpm-/GROUPS.

Then we have some of the magic of RPM working here. Provides, Requires, Obsoletes, BuildRequires, Conflicts are all as the name implies. Here you can specify versions of the packages to require, for example, Requires: initscripts >= 7.93.26.EL-1.. Earlier versions of the packages may not be compatible with this version of the software, for example.

Subpackages are also defined in the preamble, however, their use is beyond the scope of this quick tutorial. I may go back to them in a later post.

Then below that we have the %prep section, which specifies how to prepare for the build, i.e. unpack the source distribution, patch it, and make it ready for building. Inside here, you will notice both RPM macros applying patches, etc. as well as stuff that looks like shell script. That's because it is! Macros are expanded into executable shell scripts, and the whole thing is executed via the shell. The most important macro that you see here is %setup, which takes care of making the build directory and unpacking the sources.

After that, there's the %build section, which is where the actual compilation is performed. That is ALL that is performed here – building the source inside of a temporary directory. Again, this is a shell script that is executed by RPM. This is where configuration changes would be made

As a side note on these shell scripts, you can actually find them in %_tmppath as rpm-tmp.. This is useful if the build fails and you need to look to see exactly what was executed. The message that the build has failed will indicate the name of the script that failed, which is left behind rather than being automatically removed as is normal.

So after you have compiled code in your build directory, it comes time to install it. That's what the %install section is for. The tricky part is you have to 'install' into $RPM_BUILD_ROOT, which as I mentioned before, should not be on the live system. Most packages simply use GNU install, rather than calling the 'make install' that is provided with the source. This is because most do not support installing into an alternate location from what they were configured with.

An important piece about the installation is that you cannot have unpackaged files in the $RPM_BUILD_ROOT. The httpd RPM, for example, installs several files which are not packaged, which are removed by the spec file in the %install section, or else the next section will fail:

# Remove unpackaged files
rm -f $RPM_BUILD_ROOT%{_libdir}/*.exp \
$RPM_BUILD_ROOT/etc/httpd/conf/mime.types \
$RPM_BUILD_ROOT%{_libdir}/httpd/modules/*.exp \
$RPM_BUILD_ROOT%{_libdir}/httpd/build/config.nice \
$RPM_BUILD_ROOT%{_bindir}/ap?-config \
$RPM_BUILD_ROOT%{_sbindir}/{checkgid,dbmmanage,envvars*} \
$RPM_BUILD_ROOT%{contentdir}/htdocs/* \
$RPM_BUILD_ROOT%{_mandir}/man1/dbmmanage.* \

The next section that we will talk about (but not the next section in the spec file, generally) is the %files section. This specifies the files that are to be placed in the packages. At the top of the %files section, you will notice a %defattr(-,root,root). What this means is that the installed files are to have the permissions that they do right now in the $RPM_BUILD_ROOT, however, be owned by user and group root. Another thing is is %config files. These are files that are designed to be modified by the user. The behavior of RPM here varies on upgrades:

If the file has not been modified from the distributed version, then replace it silently. If the file has been modified, then there are two options. If the file has been designated as simply %config, then replace the configuration with the new one, and save the old one with a .rpmsave extension. If the file was designated as %config(noreplace), then the original file will not be replaced, but the new file will be installed as .rpmnew.

There are also the %pre, %preun, %post, and %postun sections of the spec file. These are shell scripts which are executed on the client machine during the specified time in the install.