ADAM'S WEB PRESENCE

31 May 2006

Network boot with Debian

Filed under: Nerd Notes — adam @ 12:29 pm

These are my notes on setting up diskless booting of a Via EDEN computer from an Ubuntu server. This should also work on a Debian server. The machines of relevance to this article are:

  • DHCP Server (203.35.65.1) an existing server running an old copy of RedHat which is currently providing DHCP services to the existing local network
  • Boot Server (203.35.65.33) A new machine running Kubuntu 5.10 “Hoary Hedgehog” which will contain the boot image and files
  • Client – The Via EDEN box which has no hard disk

Setting up the EDEN box to network boot

You’ll need to go into the BIOS settings by pressing the DEL key when you see the Via splash screen.

1. Go into Advanced BIOS Features and change the First boot device setting to LAN.

2. Go into the Integrated Peripherals section and enable the Onboard Lan Boot ROM.

3. Save your changes and reboot. It won’t work yet because we haven’t set up the server but you should see it try and fail to network boot.

Setting up DHCP for network booting

Since there is already a DHCP system installed here, I need to work with that since I do not think it would be a good idea to have two DHCP servers on the same network. So we will begin by adding the following lines to /etc/dhcpd.conf – don’t forget to change the IP and MAC numbers to ones appropriate for your own network:

allow booting;
allow bootp;

group
{
   next-server 203.35.65.33;
   filename "pxelinux.0";

   host stb00001;
   {
      hardware Ethernet 00:40:63:E1:F9:76;
      fixed-address 203.35.65.232;
   }
}

After modifying the file, restart the daemon. That should be all the changes I need to do on the RedHat box and I have not messed up normal DHCP operation for the other network users.

# service dhcpd restart

Setting up the boot environment

The Via box uses a network boot standard known as PXE. Debian has some packages to get a PXE boot going. Install the following packages:

# apt-get install syslinux tftpd-hpa nfs-user-server

Choose no when it asks if you want to launch tftpd from inetd.

Next set up the file /etc/default/tftpd-hpa to look like this:

#Defaults for tftpd-hpa
RUN_DAEMON="yes"
OPTIONS="-l -s /tftpboot"

Then set up a boot directory:

# mkdir /tftpboot

# chmod 755 /tftpboot

# cd /tftpboot

# cp /usr/lib/syslinux/pxelinux.0 ./

# mkdir pxelinux.cfg

Next set up the PXE configuration file. The configuration file name must be the IP of the client in HEX. The hex letters must be in uppercase too!

So for our test box at 203.35.65.232, the file would be /tftpboot/pxelinux.cfg/CB2341E8. Apparently you can also call this file /tftpboot/pxelinux.cfg/default but I haven’t tried that. Here are the contents of the file:

# PXE Configuration file for the DVC set top box.
# by Adam Pierce adam@commandsystems.com.au
# Command Systems Pty. Ltd. Australia

default dvc

label dvc
kernel dvc-kernel
append ip=dhcp root=/dev/nfs rw nfsroot=203.35.65.29:/export/dvc/stbroot

At this point, you should be able to reboot the Via box and it should connect to the server and obtain its network settings. It should get as far as loading the OS and then fail with “Could not find kernel image”.

Set up the root file system

The operating system for the client will be stored in a directory on the server. Let’s create that directory now.

# mkdir /export

# mkdir /export/dvc

# mkdir /export/dvc/stbroot

# mkdir /export/dvc/stb00001

Fill this new directory with your root file system. In my case, I intend to run multiple clients at some point in the future so I have created two subdirectories called stbroot and stb00001. These will be mounted read-only and read-write respectively. The stb00001 directory will contain folders such as /tmp and /home which the client will need to write to and I can have a separate directory for each client on the network.

The root file system for the client can now be placed in stbroot:

# cd /export/dvc/stbroot

# tar zxvf MyRootFSWhichIPreparedEarlier.tar.gz

Set up the kernel

The client will need to load the kernel before it can mount the shared directories so the kernel will need to go in the tftpboot area. Just copy the kernel from your root file system (this is easy if you are using GRUB):

# cp /export/dvc/stbroot/boot/<name of kernel> /tftpboot/dvc-kernel

At this point, the client will be able to boot and load the kernel. You should see screenfuls of kernel messages and then it will say “Kernel panic: unable to mount root fs”.

Configure NFS

This is the final step before the client can be booted. The client will mount it’s drives over the network using NFS. Set this up in the /etc/exports file. It should look like this:

# /etc/exports: the access control list for filesystems which may be exported
#                 to NFS clients. See exports(5).
/export/dvc/stbroot	203.35.65.0/255.255.255.0(rw,no_root_squash)
/export/dvc/stb00001	203.35.65.0/255.255.255.0(rw,no_root_squash)

Then finally you can restart the NFS daemon and you should be good to go.

# /etc/init.d/nfs-user-server restart

Note that I have not discussed how to set up the root file system for the client. That can be a topic for another day.


26 May 2006

Dead Easy NAT on Debian Sarge

Filed under: Nerd Notes — adam @ 12:29 pm

The problem: The boss wants me to set up a server and two workstations which can plug into various customer’s networks and connect to the internet as a kind of mobile demo of our client/server product. I have one hour to do it. The workstations run Windows XP and the server is Debian 3.1 “Sarge”.

The solution: Make the server a DCHP client and provide NAT functionality. Put the workstations onto a private subnet with static IPs.

So how to do that ? Read on…

DHCP

Fortunately, this is pretty easy in Debian. Just edit /etc/network/interfaces. Here is what I ended up with:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
auto eth0
iface eth0 inet dhcp

# Our private subnet
auto eth0:1
iface eth0:1 inet static
address 192.168.166.1
netmask 255.255.255.0

eth0 will be our connection to the internet (via DHCP) and eth0:1 is a secondary interface which will connect to our private subnet.

Get these new settings running by typing the following commands. Do it from the local console, not a telnet session or you will be in trouble :). Rebooting would also work.

# ifdown eth0
# ifup eth0
# ifup eth0:1

Next I set the two Windows workstations to IP addresses 192.168.166.5 and 192.168.166.6 and set their gateway and DNS settings to 192.168.166.1. I chose 192.168.166.x as a kind of unusual subnet so there would be little chance of it clashing with any other private subnets the client might have.

I can now use ping to check that it is all working

DNS

The workstations need to see the internet so they will need DNS. This is not a problem since the default install of BIND is set up to relay DNS requests. All I need to do is install it and it just works:

# apt-get install bind

And people say Linux is complicated and difficult, I just set up DNS server with one command!

NAT

This was the most difficult part. The first thing I need was for the server to relay traffic from the workstations out to the internet. To do this, edit the file /etc/network/options and change one line thus:

ip_forward=yes

UPDATE – I recently had to do this on Debian Etch. This step is a little different. Instead of editing /etc/network/options, you need to edit /etc/sysctl.conf and uncomment the line which reads net.ipv4.ip_forward = 1

Then we need to fiddle with IPTABLES. Once again it is only one line to do this but it is not a trivial one, actually, before we do that, make sure the iptables package is installed.

# apt-get install iptables

Now we can type our magical incantation…

# iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

The final stage is to set it up so that IPTABLES line gets run each time the system is booted. I wrote a little script which must be placed in the /etc/network/if-up.d directory. Any script placed here will be automatically run whenever the network is started up. I called my script /etc/network/if-up.d/iptables and here it is:

#!/bin/sh

# Set up firewall rules.
/sbin/iptables-restore /etc/network/iptables.rules

Next make the script executable.

# chmod 755 /etc/network/if-up.d/iptables

and finally, create the configuration file for the script to use:

# iptables-save > /etc/network/iptables.rules

Testing and conclusion

Once that is done, it should all be working. I rebooted to make sure it all came up in a working state. After that I yanked the power cord and then started up from cold just to test the kind of abuse it will experience in the field (thank God for journalling filesystems!).

All was done in twenty minutes flat which included time spent Googling for that magical IPTABLES command.

Be warned that I have not set up any kind of firewall here. IPTABLES can do that but was not required for this job. I’ll leave that up to you if you want to do it.


24 May 2006

KidType v1.0 released

Filed under: Products — adam @ 12:12 pm

KidType is a simple Windows application I whipped up last night so my kids can have fun banging away on the keyboard without messing up the files on my PC.

CLICK HERE to check it out.


23 May 2006

How to create a maximized window which the user cannot restore

Filed under: Nerd Notes — adam @ 12:24 pm

For my KidType application, I needed to create a full-screen window which a child could not accidentally close or otherwise interfere with by random poking of the keyboard and mouse.

This proved more difficult than I thought. I initially tried to create a window (using MFC) which had no WS_SYSMENU style and was initially maximized. This worked although the window could still be moved by dragging the titlebar even though it was maximized. How odd.

After fiddling with window styles for an hour or so, I realised that it is not possible to create a window which is maximized but has no system menu.

My solution is to create the window with no system menu (ie. remove the WM_SYSMENU style which also removes the minimize, maximize and close buttons) and then intercept messages so the user cannot move it.

First create a window with no system menu. This is done in the PreCreateWindow function like this:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;

// Remove system menu, minimize, maximize and close buttons.
    cs.style &= ~WS_SYSMENU;

// Remove menu bar.
    cs.hMenu = 0;

    return TRUE;
}

Next, I open the window initially maximized. This is done by changing the argument of the ShowWindow command in CMyApp::InitInstance(). The new line should look like this:

m_pMainWnd->ShowWindow(SW_SHOWMAXIMIZED);

Then I wrote a message handler for the WM_SYSCOMMAND message to prevent the window from being moved or restored:

void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam)
{
    UINT cmd = nID & 0xFFF0;
    if(cmd == SC_RESTORE || cmd == SC_MOVE)
        return;

    CFrameWnd::OnSysCommand(nID, lParam);
}

Note that I check for SC_RESTORE as well as SC_MOVE because even though the restore button is gone, the user can still restore by double-clicking the title bar.


22 May 2006

Very Quick Intoduction to the STL

Filed under: Nerd Notes — adam @ 12:26 pm

The STL is a very useful addition to the C++ language and it is really not that hard to learn and it will make your life easier I promise.

This article shows how to use a very useful STL class, the list. There are lots of occasions when you might need to make a variable-length list or queue or stack, the list container can be used for all of those things.

I am not going to waste your time with theory, lets just get straight onto some practical examples.

First include the header file. Note that STL headers do not have .h after them.

#include <list>

Now to create a list. You can create a list of any type of object (they do get tricky with polymorphic objects but ordinary primitives and classes are dead easy). To create a list of integers for example, just do this:

std::list<int> MyList;

But that example is too trivial. In real life, you will most likely want to make lists of your own classes so lets make up a class called Person to store someone’s name:

class Person
{
public:
    Person(const char *firstName, const char *lastName)
        : _first(firstName)
        , _last(lastName)
    { }

    void printName() const { std::cout << _first << " " << _last << std::endl; }

private:
    std::string _first;
    std::string _last;
};

Now we can define a list of them. You don’t have to do it using typedef but in my experience it will save you some pain. Trust me on this one.

typedef std::list<Person> PERSONLIST;

Then create an instance of the list:

PERSONLIST myFriends;

Next you need to know how to insert items into the list. A good way is to use the push_back function:

myFriends.push_back(Person("Adam", "Pierce"));

Finally you will need to know how to extract items from the list. The most common type of access would be to create a loop which iterates over the whole list. STL provides an iterator which you can place into a for loop. This example uses an iterator to print out every item in the list:

    for(PERSONLIST::const_iterator it = myFriends.begin(); it != myFriends.end(); it++)
    {
        (*it).printName();
    }

And that is pretty much it for STL lists. How easy was that! Or more correctly, how many lines of code would you have to write and debug to create your own linked list and how much time would that take ?

There is a lot more to the STL of course and even a lot more to lists (for example, I have not covered how to remove items from a list), but the intention of this article was to give you a small taste of how STL containers work. Even if you only use the list container in this simple way you will find it speeds up your development times.

To finish up, I will put together the previous examples into a working program that prints a list of my friends (hmm, I don’t seem to have very many do I).

// *********************************************************************
// ** Simple STL container example by Adam Pierce <adam@doctort.org>
// **

#include <list> // We will be using the STL list class.
#include <string> // We will be using C++ strings.
#include <iostream> // We will be using cout to print to the standard output.

using namespace std; // This is optional but it does make the code look neater.

// This class can store the name of one person.
class Person
{
public:
    Person(const char *firstName, const char *lastName)
        : _first(firstName)
        , _last(lastName)
    { }

    void print() const { cout << _first << " " << _last << endl; }

private:
    string _first;
    string _last;
};

// Now we define PERSONLIST which is a list of Persons.
typedef list<Person> PERSONLIST;

main()
{
// Create an empty list.
    PERSONLIST myFriends;

// Populate the list.
    myFriends.push_back(Person("Jasper","Russell"));
    myFriends.push_back(Person("Mike","Cornelius"));
    myFriends.push_back(Person("James","McParlane"));

// Print the list.
    cout << "A list of my friends:" << endl;
    for(PERSONLIST::const_iterator it = myFriends.begin(); it != myFriends.end(); it++)
        (*it).print();

    return 0;
}

18 May 2006

Bitten by Critical Sections

Filed under: Nerd Notes — adam @ 12:26 pm

I love Critical Section objects in Windows, they are very useful for lightweight thread locking. But this week, I’ve found an unexpected behaviour.

Normally I use critical sections to make sure multithreaded code does not re-enter, especially if I have operations which must be atomic. Here is some typical code to prevent CallFunction1() ever being called while in the middle of running CallFunction2():

// This is called by a WM_TIMER event.
void onTimer()
{
    EnterCriticalSection(crit1);
    CallFunction1();
    LeaveCriticalSection(crit1);
}

// This is called from a worker thread.
void OtherFunction()
{
    EnterCriticalSection(crit1);
    CallFunction2();
    LeaveCriticalSection(crit1);
}

Now I found that if CallFunction2() happens to do something like launch a message box, the critical section does not lock out CallFunction1() as you would expect. The messagebox will of course run the message pump, the WM_TIMER message will occur and the onTimer() function will run even though the critical section object is locked.

This is because a critical section will only lock out other threads. It will not lock out reentrant calls made from the same thread.

This is mentioned in the MSDN documentation if you dig deep enough but it sure caught me out this week!


Powered by WordPress