Monthly Archives: March 2012

Sending Custom Data with DHCP Option Tags … and Then Doing Something with the Data

Over the past week I’ve been playing with DHCP option tags, and using them to send data to DHCP client systems, and then do something with the data. I didn’t know much about DHCP option tags before doing this, I figure DHCP just gave you an IP address and maybe a nameserver. In reality, DHCP can be customized to send a couple hundred different settings, including where the IRC or SMTP servers are, VOIP settings, and lots more. What makes it really powerful is that you can use the unassigned options to create your own settings, and then do actions on the client when they get set. In this simple example, I’m going to send and set a new “message of the day” via dhcp. The message of the day is a (usually) short text message shown when you login via the terminal and is stored in /etc/motd. If you are trying this on a production system, you should back-up your /etc/motd first.

Server Setup

On the server, after installing dhcpd, edit /etc/dhcp/dhcpd.conf, create a valid subnet and then in the global space, add your new option. Since options 151-174 are unassigned, I will use option 151. You could also use a ‘site-local’ option between 224-254.

Add the following outside any subnet sections:
option motd code 151 = text;
option motd "Happy Easter from your DHCP server team";

Client Setup

On the client, manually run dhclient -d and make sure you’re getting a valid IP from the server. Once you know that is working, edit /etc/dhcp/dhclient.conf and add two parts, first you need to name the new option and second you need to request it:

Add this to the top, you may see another option code there, add it near that:
option motd code 151 = text;

Then look for the big request block, add your new option into that block:
request subnet-mask, broadcast-address, time-offset, routers,
domain-name, domain-name-servers, domain-search, host-name,
netbios-name-servers, netbios-scope, interface-mtu,
rfc3442-classless-static-routes, ntp-servers,
dhcp6.domain-search, dhcp6.fqdn,
dhcp6.name-servers, dhcp6.sntp-servers,
motd;

Now, let’s see if we get it. Edit /etc/dhcp/dhclient-exit-hooks.d/debug and switch RUN=”no” to RUN=”yes”. Then edit the large block of variables and add your new option, motd.

Re-run dhclient -d and then check /tmp/dhclient-script.debug, do you see your motd option? If so, your setup is correct. It should look liks this:

new_motd='Happy Easter from your DHCP server team'

Doing Something When You Get an Option

dhclient defines exit-hooks, one of which is the debug script you edited above. You can create your own shell script in /etc/dhcp/dhclient-exit-hooks.d/ and follow some of the other examples in there. DHCP options come in as variables, so in this case, the script would use ${new_motd}. From what I can tell everything in here has to be a shell script, a python script I tried did not work. Here is my script to set motd:

if [ -n "${new_motd+x} ]; then
echo ${new_motd} > /etc/motd
fi

Re-run dhclient -d and you should see that the script has run and your message of the day is now a Happy Easter message.

What About NetworkManager?

If you are running NetworkManager on your system, and you probably are, then NetworkManager starts dhclient during boot and subverts the running of dhclient’s exit hook scripts. You can use NetworkManager’s own version of “exit hooks” which live in /etc/NetworkManager/dispatcher.d to respond to DHCP events. Scripts in that directory can be written directly in python. The scripts run from NetworkManager’s dispatcher use slightly different environment variables for DHCP options, they’re in all caps and are pre-pended with DHCP4_ (and presumably DHCP6_ if you’re serving IPv6 addresses).

if [ -n "${DHCP4_MOTD+x} ]; then
echo ${DHCP4_MOTD} > /etc/motd
fi

See the man NetworkManager(8) for more details on actions that your scripts can respond to.

Options as Expressions

There are more advanced things you can do. For example, you set a setting on the server based on an expression, for example:

if option dhcp-user-class = "fortcollins-office" {
option smtp-server "smtp.ftc-office.net";
}

You then configure the client to send up a dhcp-user-class of “fortcollins-office” for all your PCs in Fort Collins.

Using my example, you could change the holiday in the message based on the date that the lease was given out.

See man dhcp-eval(5) for more info on expressions.

Encapsulated Options

Another thing you can do is vendor encapsulated options. Vendor encapsulated options take a bunch of vendor specific info and encapsulate it into one option. This means that you can pack a bunch of data into one option. I am not sure if all dhcpd servers support this construct however.

Testing This
If you want to try DHCP options without messing up your network, the quickest way is to create two virtual machines, a client and a server, and give them each two NICs. One NIC on each machine should be on a private network, and the server should have a static IP on this private network NIC and serve DHCP on that interface. The other non-private NIC is used by you to copy files to and from your virtual machines, but is otherwise not specifically required. I did this setup using Ubuntu 12.04 precise and qemu and it works great.

Conclusion
Customized DHCP options let you transmit data to your DHCP clients and then write scripts to act on that data. Although my example is contrived, you could use DHCP to transmit useful system settings and then configure them via script.

References

Tagged , , ,

Easily Create a Default Settings Package with Ubuntu Defaults Builder

If you’re rolling your own Ubuntu edition, usually a localized one, then you typically need to configure a bunch of settings. Previously this was done by creating a config package and overwriting settings files manually. This was a pain and also required knowledge of which settings to modify and where and how they were stored. Ubuntu-defaults-builder solves most of these issues for anyone rolling their own Ubuntu by abstracting the way settings are saved for you, you edit a simple text file and let the tools generate the settings you need.

Here’s How it Works

After installing ubuntu-defaults-builder, you create your config area by running ubuntu-defaults-template [package-name]. Package-name should be something like ubuntu-defaults-germany or ubuntu-defaults-mydistro. This creates a directory named [package-name]. Inside that directory are simple config files that you can modify:

drwxr-xr-x 12 1000 1000 4096 Mar 23 21:08 .
drwxrwxr-x 4 1000 1000 4096 Mar 23 21:05 ..
drwxr-xr-x 2 1000 1000 4096 Mar 25 04:11 debian
-rw-r--r-- 1 1000 1000 799 Mar 22 21:38 depends.txt
drwxr-xr-x 2 1000 1000 4096 Mar 23 02:34 desktop
drwxr-xr-x 2 1000 1000 4096 Mar 23 02:44 hooks
drwxr-xr-x 2 1000 1000 4096 Mar 22 21:38 i18n
drwxr-xr-x 4 1000 1000 4096 Mar 22 21:41 icons
drwxr-xr-x 2 1000 1000 4096 Mar 22 21:41 multimedia
-rw-r--r-- 1 1000 1000 871 Mar 22 21:38 recommends.txt
drwxr-xr-x 2 1000 1000 4096 Mar 23 20:53 unity
drwxr-xr-x 2 1000 1000 4096 Mar 22 21:40 webbrowser

The files are not “real” conf files, but are used by ubuntu-defaults-builder to create gconf and gsettings overrides. Most of the conf files are simple and self-documenting. Look at /unity/launchers.txt for example, in that file you specify what default launchers you want in Unity.

root@caprica:~/Projects/# cat ubuntu-defaults-fred/unity/launchers.txt
@clear
nautilus-home
firefox

Now, just modify all these files how you would like them. Finally you can build your defaults package with dpkg-buildpackage. After you do that, take a look at the .deb with dpkg -c and you’ll see that your settings have become overrides for anything that uses gconf or dconf/gsettings:

cat /usr/share/glib-2.0/schemas/ubuntu-defaults-fred.gschema.override
[com.canonical.Unity.Launcher]
favorites=['nautilus-home.desktop', 'firefox.desktop']

What Can You Configure?

ubuntu-defaults-builder, as of 0.27, lets you modify the following settings:

  • depends.txt – a list of packages to make dependencies of the defaults package, this is helpful when building the image.
  • recommends.txt – a list of packages to make recommends of the defaults package, this is helpful when building the image.
  • desktop/ directory – set the background, default MIME handlers, default user session, and adding custom desktop files.
  • hooks/chroot – extra hooks to be run during image build (advanced)
  • i18n/ directory – set default keyboard layout, language, and which language packs to include in the image
  • icons/ directory – where to place custom icons, for general use or for any custom .desktop files
  • multimedia/radiostations.txt – customize the radio stations list for rhythmbox and banshee
  • unity/launchers.txt – set the default list of launchers
  • webbrowser/ directory – append bookmarks to the bookmarks menu or toolbar, set the default search engine or startpage. Currently only Firefox is supported.

Testing

At this point you probably want to test it in a VM. However, due to the way that dconf settings work, the simplest method to ensure your changes will be seen is to test like this:

1) Copy the defaults package to the VM.
2) Install the defaults package
3) Create a new user
4) Logout
5) Login as the new user. This will ensure that all the default settings take precedence.

You could also use your VM’s snapshot model and save yourself from ending up with 50 user accounts if you do a lot of testing.

Once your defaults package is good, you can build an image with it using the command ubuntu-defaults-image. This tool allows you to specify a locale, add packages or sources, etc and will generate an install image for you to do final testing on.

Limitations

Ubuntu-defaults-builder is a great tool, but it has limitations. First and foremost is that you cannot change everything you might want to using it. Unless you’re going to write a patch for ubuntu-defaults-builder, you are limited to what the tool knows about. The tool has specialized routines for almost every setting it knows how to to. If you want to set other settings, for example, some advanced lightdm.conf settings (such as disabling guest or hiding the user list), you will have to override it using the old way of dropping in a new lightdm.conf file (or using /usr/lib/lightdm/lightdm-set-defaults if that will do what you want). For settings stored in gsettings/dconf you will have to write your own gsettings override files and use the override_dh_installgsettings rule in debian/rules.

When you are making your own overrides, be careful that you’re not interacting with the overrides that are built by ubuntu-defaults-builder. For example, in my case, I get two overrides, the one starting with 50 is from the dh_installgsettings step during the build:

-rw-r–r– 1 root root 68 Mar 23 20:18 50_ubuntu-defaults-fred.gschema.override
-rw-r–r– 1 root root 146 Mar 23 21:06 ubuntu-defaults-fred.gschema.override

I do not know the order the two files will be handled in, so I just try not to let them overlap settings. The easiest way to do this is to use ubuntu-defaults-builder’s own mechanisms whenever possible and then to double-check your output .deb file.

More Info

The spec for ubuntu-defaults-builder, which has additional details on what it does, how, and why, is available here.

Tagged , , ,

Locales and Building Packages

A couple weeks ago I participated in the Ubuntu Global Jam/Bug Fix session. I started by looking at all the “fails to build from source” aka ftbfs list. I read through the list and found a few interesting packages, but ended up working on live-manual, because I use live-build and also it seems odd that we can’t build a user-manual.

At first I was confused, because I pulled the source and ran dpkg-buildpackage on it and it worked fine on my amd64 box. So I tried an i386 VM that I had running, works fine there too! Next, the litmus test, a build in a pbuilder chroot. Guess what, that one failed. I traced the errors back to this one error when building the German user manual:

ERROR occurred, message:”invalid byte sequence in US-ASCII”

It was odd, why are we processing a German user manual file using a US ASCII locale?

I compared the environment between my system, where it built, and the pbuilder chroot, where it failed and found that inside a pbuilder chroot, the LC_ALL variable is set to “C”. C is equivalent of US-ASCII. After trying in vain to change the locale to something that understands utf-8, I realized that I needed to install a locale that could handle UTF-8 inside my pbuilder chroot.

debian/control:
-Build-Depends: debhelper (>= 8), ruby, libnokogiri-ruby
+Build-Depends: debhelper (>= 8), ruby, libnokogiri-ruby, language-pack-en

debian/rules:

%:
– dh ${@}
+ LC_ALL=en_US.UTF-8 dh ${@}

Now the build was working, so I submitted my merge proposal.

My fix was accepted but discussions revealed that the fix wouldn’t work in debian because their locale packages are different. With that in mind, I worked on a more generic fix, based on what python-djvulibre does. After some tweaking (not shown in the merge proposal), I settled on this fix:

debian/rules:

+override_dh_auto_build:
+ mkdir -p debian/tmp/locale/
+ localedef -f UTF-8 -i en_US ./debian/tmp/locale/en_US.UTF-8/
+ export LOCPATH=$(CURDIR)/debian/tmp/locale/ && \
+ export LC_ALL=en_US.UTF-8 && \
+ dh_auto_build

Note: During the development of that fix, I had trouble finding all the different steps in the debhelper build and what I could override. A debian developer pointed me to this great presentation which answered a lot of questions: Joey Hess’ Not Your Grandpa’s Debhelper.

So there are two ways to solve a locale issue during a build. Do you know of any others? If so I’d love to hear them in the comments.

Tagged , , ,