Recently in System Administration Applications Category

Build a Wireless Gateway with Perl

You have set up and configured various wireless access devices, but could not find one that included all of the features you needed. You could wait for a firmware upgrade from your manufacturer, hoping that they will include the features you want. However, your chances of finding all of your issues addressed by a new firmware package, if it ever comes out, are slim to none. Now is the time to roll up your sleeves and build your own wireless access gateway from scratch. Don't let this idea scare you; this is all possible thanks to the open source world.

This article introduces an open source project called AWLP (Alptekin's Wireless Linux Project), which turns a PC with an appropriate wireless LAN card (Prism2/2.5/3) into a full-featured, web-managed wireless access gateway. That old Pentium 120 machine in your basement might march back up the stairs shortly.

Building your own wireless access device is nothing new. Around three years ago, Jouni Malinen released HostAP, a Linux driver for wireless LAN cards with Intersil's Prism2/2.5/3 chipset. When operated in a special mode called Managed, the host computer acts as a wireless access point. HostAP does its job and does it well, but it is command line only, and that's not suitable for everyone. A complete solution that potentially competes with off-the-shelf devices needs more features, including DHCP, firewall, DNS, and NTP, along with a web interface for configuration. This is the de facto standard nowadays.

To use a car analogy, this is like building a custom car; you want to be able to put in a new clutch, change the suspension, or try a new braking system and see how it performs. In the case of the gateway, you might want to implement an outgoing port-based filter in addition to the incoming one in the firewall. You may want to try a different DNS server or develop a special module that will block MAC addresses through ACLs after exceeding a certain amount of bandwidth in a short period of time.

For AWLP, the chassis is GNU/Linux Slackware, the engine is Host AP Driver, the transmission is Host AP Utilities, and so on. AWLP code, written in Perl, is the part that makes all these components work together in harmony as a wireless access gateway. After you set up AWLP, you will have a functioning, preconfigured, wireless access gateway to start with. Then you can start modifying the Perl code and configuration files to test and implement the extra capabilities.

Hardware Requirements

You need a dedicated machine for this task. Running with less than 32 MB of RAM will be painful; the system is more RAM-intensive than it is CPU-intensive so you can run it even on a 486 processor--but be aware that most 486 machines have only ISA slots. You might have problems finding ISA 10/100 Mbps Ethernet cards and ISA wireless LAN cards on the market. An old Pentium machine with at least 1GB HDD is ideal for this task.

If you plan on running your wireless device on a 24/7 basis, and if availability and portability are major concerns, you might consider dedicated hardware. Check out OpenBrick Community for compact hardware platforms with HDD support.

In addition to the computer, you also need an Ethernet card compatible with Linux, a PCI or PCMCIA wireless LAN card that has a Prism2/2.5/3 chipset, and a PCI-to-PCMCIA converter, depending on your choice of wireless LAN card and the slots on your board. Refer to the AWLP Hardware Compatibility section for in-depth information on choosing these three components.

Installing AWLP

There are three phases of installation:

  1. Custom installing Slackware with tagfiles
  2. Upgrading packages in Slackware
  3. Installing AWLP codes and configuration files

The third step is the easiest one. The first step, installing Slackware with tagfiles provided by the AWLP package, will take most of your time and effort. You must have Slackware installation discs on hand. Consult the Slackware Linux Project site to obtain them. In order to keep this article readable, I again refer you to the related site, the step-by-step AWLP Installation Instructions. After completing the first and second phase of the installation instructions, you can install AWLP, the code, and the configuration files that will make everything work together.

I highly recommend that you take a disk image after successfully installing AWLP and before you start to modify the code if you have installed it on a slow machine. Installing AWLP code and configuration files take no more than couple of minutes, but in order to reach this step, you must have custom-installed the Slackware with the tagfiles provided, and this might take a couple of hours on an old (<200MHz) Pentium machine. Taking a disk image and restoring from that image when needed will be a lot easier and quicker than starting afresh from Step 1.

Under the Hood

AWLP uses several configuration files:

  • DHCP, the Dynamic Host Configuration Protocol: /etc/dhcpd.conf
  • Apache Web Server: /etc/apache/httpd.conf
  • DNS, the Domain Name System: /etc/named.conf, /var/named/caching-example/localhost.zone, /var/named/caching-example/named.ca, and /var/named/caching-example/named.local
  • NTP, the Network Time Protocol: /etc/ntp.conf
  • Firewalling: /etc/rc.d/rc.firewall

In addition to these standard configuration files, there are some custom configuration files such as /etc/awlp/oui_filtered.txt. This file contains the filtered version of oui.txt, representing the IEEE Organizationally Unique Identifiers. It makes it possible to find the company manufacturing a specific Ethernet card, wired or wireless, using the first 24 bits of MAC address. In order to have an in-depth knowledge of all the configuration files and their layouts, I encourage you to examine installer.sh from the AWLP package.

AWLP code resides in /var/www/cgi-bin/awlp. The core of the AWLP code is index.pl. It does all of the error checking, manipulation, and modification. The web server, Apache 1.3.33, runs as the user and group apache. In order to manipulate configuration files through the web browser, the related configuration files have suid and guid permissions set. This strategy is definitely more secure than running the web server as root.

The part that interacts with HostAP command line tools and utilities, along with standard Linux tools and utilities, is engines1.pl. It serves as an include file and contains various subroutines to do the work. engines2.pl also contains various subroutines, but these usually do sorting, searching, and conversion. radar.pl provides status checking and monitoring functionality. It's not crucial to the operating of the wireless access gateway, but it definitely does add value because watching and monitoring how your device is performing is the key factor to the success of your implementation.

extras.pl provides ASCII to HEX conversion, DHCP lease table display, and several other non-core functions. As the name implies, error_messages.pl has all the error messages and their descriptions. global_configuration.pl has pretty much all the configuration variables ranging from critical to non-critical. You must understand the inner workings of the system in order to change the configuration variables up to $MAIN_TITLE. $MAIN_TITLE and the following variables are also necessary, but for mostly cosmetic purposes, so you can customize them without worrying about accidentally disabling needed features.

Sample Modification

Now you know what AWLP is and what it can do. What about all these modifications and customizations? Now, it is time to show you a step-by-step instruction for adding a feature to AWLP. As of AWLP 1.0, you can configure the DHCP server only by manually changing /etc/dhcpd.conf. The feature I want to demonstrate will simply show the contents of the file. Once you have familiarity with the code, you can improve it to modify the contents of dhcpd.conf.

The Apache web server runs as the user and group apache. The result of ls -l on /etc/dhcpd.conf shows:

# ls -l /etc/dhcpd.conf
-rwxrwx---  1 root apache 618 Mar 5 12:41 /etc/dhcpd.conf*

The script will be able to read and modify the /etc/dhcpd.conf file. Open the index.pl file. At the top, there are two configuration variables; @MainPageLinksAction and @MainPageLinksName. These two control the links on the left side. To add an additional link for DHCP, add DHCP to both arrays.

Next, find the line that says:

elsif ($FORM_Action1 eq "Administration") {

Just before this line, add the following lines of code, then save and close the file.

elsif ($FORM_Action1 eq "DHCP") {

    my $DHCPConfigFileContent;

    if (open(FILE, "/etc/dhcpd.conf")) {
        local $/;
        $DHCPConfigFileContent = <FILE>;
        close(FILE);
    }

    unless ($DHCPConfigFileContent) {
        $DHCPConfigFileContent = "/etc/dhcpd.conf could not be read or it is empty!";
        $DHCPLeaseRangeStart = "N/A";
        $DHCPLeaseRangeEnd   = "N/A";
    }

    my ($DHCPLeaseRangeStart, $DHCPLeaseRangeEnd);

    if ($DHCPConfigFileContent =~ m/Range\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)/i) {
        $DHCPLeaseRangeStart = $1;
        $DHCPLeaseRangeEnd   = $2;
    }

    $Right_Plane_Output .=<<HTMLCODE
    <TABLE ALIGN=CENTER CELLSPACING=3 CELLPADDING=3 BGCOLOR="#000000">
    <TR BGCOLOR="#FFFFFF">
            <TD ALIGN=LEFT>
            <font face="Helvetica, Arial, Sans-serif, Verdena" size="2">
            <TABLE CELLSPACING=0 CELLPADDING=0>
            <TR>
                    <TD>
                    <font face="Helvetica, Arial, Sans-serif, Verdena" size="2">
                    <PRE><CODE>
                    ${DHCPConfigFileContent}
                    </CODE></PRE>

                    <BR><BR><BR>
                    <font color="#FF0000">Lease Range:</font> 
                    <B>${DHCPLeaseRangeStart} to ${DHCPLeaseRangeEnd}</B>
                    <BR><BR><BR>
                    </TD>
            </TR>
            </TABLE>
            </TD>
    </TR>
    </TABLE>
HTMLCODE

}

Once you do this modification, there will be a new menu section on the left side with the name DHCP. Clicking the DHCP link on the left will show you the contents of /etc/dhcpd.conf and Lease Range values, which come from the contents of the file through a simple regular expression construct.

Conclusion

You will have a functioning wireless access gateway once you install AWLP. The above illustration proves how easy it is to add features to this software-based wireless access gateway, provided that you are familiar with Perl. However, to accomplish more useful modifications tailored to your needs, you should examine index.pl and the other core, and include scripts and configuration files.

Related Links

Automating Windows (DNS) with Perl

Driving Windows DNS Server

If you happen to manage a DNS server running on Windows 2000 or Windows 2003 with more than just a couple of dozen resource records on it, you've probably already hit the limits of the MMC DNS plugin, the Windows administrative GUI for the DNS server implementation. Doing mass operations like creating 20 new records at once, moving a bunch of A records from one zone to another, or just searching for the next free IP in a reverse zone, can challenge your patience. To change the name part of an A record, you have to delete the entire record and re-create it from scratch using the new name. You've probably thought to yourself, "There MUST be another way to do this." There is!

Silently, almost shyly, behind the scenes and without the usual bells and whistles, Microsoft has arrived at the power of the command shell. For the DNS services[1], the command line utility dnscmd is available as part of the AdminPac for the server operating systems. dnscmd is a very solid command line utility, with lots of options and subcommands, that allows you to do almost every possible operation on your DNS server. These include starting and stopping the server, adding and deleting zones and resource records, and controlling a lot of its behavior.

This article explores how to run dnscmd from Perl. In that respect, it is a classic "Perl-as-a-driver" script, invoking dnscmd with various options and working on its outputs.

DNSCMD

Invoke dnscmd /? to see a top-level list of available subcommands and type dnscmd /subcommand /? for more specific help for this subcommand. dnscmd /? shows that there is a subcommand RecordDelete, and dnscmd /recorddelete /? (case not significant) explains that you need a zone name (like "my.dom.ain"), a node name within this zone (like "host1"), a record type (like "A"), and the data part of the resource record to delete (like "10.20.40.5").

The first argument to dnscmd, if you actually want to do something with it, is the name of the DNS server to use. A full working command looks something like:

dnscmd dnssrv.my.dom.ain /RecordDelete my.dom.ain host1 A 10.20.40.5

This opens the very welcome opportunity to run dnscmd remotely—from your workstation, for example—which frees you from the need to log in to your DNS server.

WDNS.PL (The Script)

The script in the current version only handles A and PTR records. There is no handling of CNAME records, for example. Within this limitation, it is also very A record-oriented: you can add or delete A records, change the IP or name of an A record, or move the A record to a different zone. It keeps PTR records in sync with these changes, creating or deleting a corresponding PTR record with its A record. This is mostly what you want.

The most important thing to understand with this script is the format and the meaning of the input data it takes ("Show me your data ...")[2]. The format is simple, just:

    <name1>      <target1>
    <name2>      ...
    ...          ...

Separate name and target by whitespace. A name is a relative or fully- qualified domain name. A target can be one of these:

  • another domain name, meaning to rename to that name;
  • an IP, meaning to change to this IP;
  • nothing or undef, meaning to delete this name.

This mirrors the basic functions of the script. To add some extra candy, the target parameter has two other possibilities which have proven very useful in my environment. A target can also be:

  • a C net, given as a triple of IP buckets (like "10.20.40");
  • a net segment identifier ("v1", "v2", and so on, in my example).

In both cases, the script will give the name a free IP (if possible) from either the C net or the net segment specified by its identifier. I'll return to this idea soon.

To pull all of the various possibilities together, here is a list of sample input lines, each representing one of the mentioned possibilities for the target:

          pcthe                10.20.90.53     -- new ip
          pcthe.my.dom.ain     10.20.90.53     -- new ip full qualified
          pcthe                10.20.90        -- search free ip in range
          pcthe                @v8             -- search free ip in net segment v8
          pcthe                pcthe2          -- rename host
          pcthe                pcthe.oth.dom   -- rename host full qualified
          pcthe                                -- delete host (with -r option)

Pass this data to the script through either an input file or STDIN[3].

init()

Now to the code. At startup, wdns.pl pulls in the list of primary zones from the given DNS server, both forward (names as lookup keys) and reverse (IPs as lookup keys) zones. This is handy, because it will use this list again and again.

mv_ip()

The main worker routine is the sub mv_ip. (Don't think too much about the name; it's from the time when the only function of the script was to change the IP of a given name). For any given name/target pair, it does the following: First it tries to find a FQDN for the name. If it finds a host for the given name, it uses the FQDN as a basis to construct the name part of the targeted record. If it cannot find a name, it assumes that it should create an entirely new record. If the options permit (-c), it constructs one.

Then it inspects the target. Depending on its type, the program prepares to assign a new IP to the name, rename an existing A record while retaining the IP, search for a free IP in a certain range, or just delete existing records. When everything settles, the actual changes take place, using dnscmd to delete and add A and PTR records as appropriate. (There is no UpdateRecord function in dnscmd, so updating is in fact a combination of delete and create).

That's it! The rest of the code is lower-level functions that help to achieve this.

create_* and delete_*

The four subs create_A, create_PTR, delete_A, and delete_PTR are wrapper functions around the respective invocations of dnscmd. An additional issue of interest is that Windows DNS will delete a PTR record once you delete the corresponding A record, so you don't have to do so explicitly.

get_rev_zone() and get_fwd_zone()

One of the major issues when manipulating DNS resource records is picking the right zone to do the change in. If you have just one forward and one reverse zone, this is simple. However, if you are maintaining a lot of zones with domains and nested subdomains, while other subdomains of the same parent have their own zones, this might be tedious.

wdns.pl can offload this task for you. The subs get_rev_zone and get_fwd_zone use the initially retrieved list of primary zones from your server. They take an IP or a fully qualified domain name respectively, and split it into the node part and the zone part. So the IP 10.20.40.5 might split into 10.20.40 and 5 (if the proper zone of this IP is 40.20.10.in-addr.arpa) or 10 and 20.40.5 (if 10.in-addr.arpa happens to be the enclosing zone), depending on your zone settings. The same applies for domain names. Other routines use this information to add or delete resource records in their appropriate zones.

IP Lookup Functions

There is a set of subs I called "IP lookup functions". They all help to find a free IP in an appropriate range. Depending on the target specification, they will search a certain C net or a whole net segment of unused address. This searching breaks down to finding the appropriate zone, the appropriate node ("subdomain"), and then listing the already existing leaf nodes in this range. Once it has the list of used nodes, it starts scanning for gaps or unused nodes off the end of the list.

An additional feature of these routines is that they honor certain reservations in the ranges, either through fixed directives ("leave the first 50 addresses free at the beginning of each net segment") or through inspecting dedicated TXT records on the DNS server that contain the RESERVED keyword. (The actual format of these records is RESERVED:<range-spec>:<free text>, where range-spec is a colon-separated list of IPs or IP ranges. An example is RESERVED:1,3,5,10-20,34:IPs reserved for the VPN switches). This helps avoid re-using reserved IPs accidentally through the automatic script, and also helps avoid messing things up when time is short.

In the case of these TXT records, I used dnscmd to retrieve them, not nslookup, which would have been equally possible.

A Word About the Net Segments

If your IPs reside in a segmented network, which is likely to be the case for most sites, make sure that your hosts have addresses for the segments to which they attach. For this script I have chosen a poor man's approach to represent the segments just by the list of their respective C nets in the script itself (see the hash %netsegs in the "Config section"). There might be a more clever way to do this. If you are going to run the script in your environment, edit this hash to reflect your network topology.

The dns_lookup sub looks up the current DNS entries. It runs the extern command nslookup and parses its output. If you need more sophisticated DNS lookups (and nslookup's options just won't do), you might want to resort to dig (which has a Windows version) or Net::DNS (which runs on Windows in any case). This simple way of doing it was just enough for my needs.

Footnotes

  1. In the Windows world, server processes are usually referred to as "services"; I tend to mix this term with "server" every now and then.
  2. "Show me your functions, and I will be confused. Show me your data, and your functions will be obvious", to re-coin a famous quote from Frederick Brooks' The Mythical Man-month.
  3. Depending on your Windows command shell, you might have to tinker a bit to get the STDIN input to work as desired. Cygwin's bash works like a breeze and takes Ctrl-Z<RET> as the EOF sequence.

Perl and Mandrakelinux

Perl programmers have a special reason for choosing Mandrakelinux as their desktop operating system. Mandrakelinux uses Perl for dozens of the graphical "value added" utilities included with the distribution, including much of the Mandrakelinux Control Center. I asked Mandrakelinux for an interview with a top Perl contributor and they sent Rafael Garcia-Suarez my way. Besides being heavily involved with Perl at Mandrakesoft, Rafael is also the pumpking for the Perl 5.10 release. Rafael answered my questions about using Perl for GUI programming and how he balances his day job with being pumpking.

O'Reilly Network: Briefly tell us about the Perl work you do for Mandrakelinux.

Rafael: My main responsibility is to maintain and enhance the command-line tool urpmi (and its GUI counterpart rpmdrake), which are the Mandrakelinux equivalent of Debian's apt or Fedora's yum; that is, fetching RPMs and their dependencies and installing or upgrading them.

This job extends to whatever pertains to installing RPMs; that means that I also participate in enhancing Mandrakelinux's installer. All those tools are written in Perl.

Besides this, I also maintain the RPM of perl itself and of a load of CPAN modules for Mandrakelinux.

ORN: Perl is uncommon choice for graphical programming, yet Mandrakelinux has used Perl for over 50 graphical applications. Many of these tools are specific to Mandrakelinux, adding value to the distribution. What can you tell us about why Mandrakelinux uses Perl for this important role?

Rafael: Not all tools were always written in Perl. However using consistently a same language allows to share and reuse libraries across all tools, be it the perl/rpmlib bindings or custom graphical toolboxes. Thus, for example, the OS installer shares code with urpmi and rpmdrake. A scripting language was preferred because of rapidity of development and ease of debug -- attempts at writing rpmdrake in C were painful, although that was before I was hired by Mandrakesoft. Perl was a natural choice since there were already very good in-house skills for it.

Editors note: Recently, the Linspire distributionexemplified the use of dynamic languages to bring a graphical application to market quickly. Their Lsongs nd Lphoto programs use Python.

ORN: Could you give a specific example of where Perl has made a noticeable difference in shortening development time?

Rafael: I think that using a scripting language in general shortens the development time, notably due to the shorter write code / compile / test / debug cycle. However perl is particularly useful due to the high number of development modules available on CPAN. For example running the OS installer under Devel::Trace produces lots of logs, but is tremendously helpful to trace obscure bugs. You can't do this in C without adding printfs everywhere and recompiling the whole stuff.

ORN: What tools does Mandrakelinux use for automated testing of graphical Perl applications?

Rafael: Er, interns ?

More seriously, there is no automated testing for GUIs. Automated testing of such applications raises several difficult problems, since they often modify a system's configuration or necessitate some specific hardware (and I'm not even speaking of the OS installer GUI).

Writing more unit tests is definitively something I want to do in the future, however; it would be very useful to have complete sets of regression tests for the urpmi dependency solver, for example.

ORN: What has been the response of the Perl community to Mandrakelinux Perl-based tools, especially in terms of contributing patches back to your organization?

Rafael: The people who send patches for the tools are mostly interested in improving the distribution they use; even if they might belong to the Perl community as well, their point of view is the one of a Mandrakelinux user. That's one of the reasons why the tools have little visibility outside the MDK community.

Another reason is that there never was a strong motivation in Mandrakesoft for splitting the libraries in what is and is not MDK-specific, and to write clear and comprehensive documentation: both need efforts, don't pay immediately and are likely to be postponed when you have deadlines.

However, the CVS repository in which the tools' source code is kept is openly accessible; some contributors (i.e. non Mandrakesoft employees) have been granted commit access to it. We now need to make the learning curve softer.

ORN:What advice do have for Perl programmers interested in contributing to the Perl-based Mandrakelinux utilities? Any helpful hints for getting started?

Rafael: As with any open-source project, if you want to learn how it's done and to contribute, use the latest version available. In the case of Mandrakelinux, that would be "cooker", the development distribution. Subscribe to the mailing list, become familiar with the tools, have a checkout from the CVS repository, get yourself a Bugzilla account and don't be afraid to ask questions. Learning to build RPMs, at least to be able to rebuild the RPMs of the tools, would be helpful too. Those questions are covered in the wiki; a good page to begin with is:

http://qa.mandrakesoft.com/twiki/bin/view/Main/HowTo

ORN: What challenges have you faced maintaining Perl as a core part of the operating system, with so many key utilities depending on it?

Rafael: There are two kinds of challenges: spatial and temporal, would I say.

First, spatially, you have to devise how to split the standard Perl distribution on smaller packages ("perl-base" for the essentials, "perl" for the rest of modules, "perl-devel" for Perl development tools and "perl-doc" for, well, perldoc itself and the standard documentation.) This split is not arbitrary. When you maintain a core tool like urpmi, which is essential to system administration, you don't want it to require too many Perl modules, or even too many core modules. (The same goes for the installer, that must not take all the space on the installation CDs). So perl-base contains the modules used by urpmi, and urpmi doesn't use modules that are in perl but not in perl-base.

ORN: Perl has a reputation for being "slow" when used for graphical programming. How is that addressed in Mandrakelinux applications?

Rafael: I think that Perl doesn't deserve this reputation, only some Perl programs do ! The MDK tools use the perl-Gtk2 bindings (mostly for historical reasons, the Qt bindings weren't mature enough when the development of those tools started); and since they're pretty close to the C lib, performance is very acceptable.

Did you know that the game Frozen Bubble, written by a former Mandrakesoft employee, is implemented in Perl ? It's not anywhere near slow. Actually people are often surprised to learn that Frozen Bubble or the MDK tools are written in Perl, since they don't give the impression of slowness generally associated to scripted GUIs.

In fact, it appears that the speed bottlenecks of the MDK tools are, like in other programs, data processing, not display.

ORN: You've recently become the pumpking for Perl 5.10. How does your this interact with your day job and how do you balance the two positions?

Rafael: I was deeply involved in the development of Perl 5 before that, taking time to review and apply patches and so on. So I was mostly working on it on evenings and week-ends. I can now work on Perl 5 during my dayjob, since Mandrakesoft allows its developers to work on free projects they like part time. However, I'd point out that my day job is a bit special, since, contrary to proprietary projects, I'm always in contact with its community of users, via mail, IRC or other internet-based media (and even sometimes in real life.) Thus I'm sometimes led to work during my free time as well... In other words the frontier between day job and other open source development is blurred. In both cases things have to be done. The only difference is that for the day job thing, I have deadlines, and they pay me for it.

ORN: Do you have a favorite Perl module that more people should know about?

Rafael: I don't really know, I learn new modules mostly by hanging around in places where the cool kids discover them before me -- mailing lists, mongers, use.perl. I use B::Concise all the time, but I suspect it's not useful for people who are not familiar with the internals of perl. Also, recently, I found encoding::warnings useful for debugging Unicode-related bugs.

Visit the home of the Perl programming language: Perl.org

Sponsored by

Powered by Movable Type 5.02