dhcp-agent
**********

Introduction
************

   dhcp-agent is a suite, or a set of tools, that allow a UNIX host to
make use of the Dynamic Host Configuration protocol.

   Currently the suite is packaged with the following tools:

   * dhcp-client : A highly configurable and extendable DHCP client.

   * dhcp-sniff  : A command line DHCP sniffer.

   In good time the suite will also be packaged with:

   * dhcp-server : A configurable and extendable DHCP server.

   * dhcp-relay : A DHCP relay agent.

Building and Installing
=======================

   You can download a copy of dhcp-agent from:

   <http://dhcp-agent.sourceforge.net/>

   You will also need libdnet, libpcap, and guile from the following
locations:

   bullet libdnet version 1.7 or higher:
     <http://libdnet.sourceforge.net/>

   bullet libpcap version 0.6.2 or higher: <http://www.tcpdump.org/>

   bullet guile version 1.6.4 or higher:
     <http://www.gnu.org/software/guile/guile.html>

   Once these dependencies are installed use the `configure' script to
configure the build of dhcp-agent. Then `make' and `make install' to
install it on your system.

   The `configure' script accepts a number of flags and environment
variables. You should use `configure --help' to list these.

Default Settings
================

   The `configure' script will allow you to set some default paths
including where dhcp-agent keeps its configuration and data files.

   These are the more prominent options:

   * -prefix : the "prefix" is where all the directories are created.
     If you wish to install dhcp-agent in `/opt/dhcp-agent' then you
     will need to specify that directory prefix. By default dhcp-agent
     will use `/usr/local' as its prefix.

   * -bindir: the directory where binaries are installed

   * -sysconfdir: the directory where the configuration files are
     installed

   * -localstatedir: the directory where data files are stored. lease
     information is kept here.

System Specific Notes
=====================

GNU/Linux
---------

   The client under Linux may not be entirely happy when applied to an
aliased interface. You will see some error messages, however things
should proceed OK.

Solaris
-------

   No major issues so far. Building the package may become
"interesting" if you have not setup your build environment properly.

FreeBSD
-------

   Under FreeBSD the client will make use of two bpf devices. This is
because of a limitation in dnet. If you run out of bpf devices you may
need to compile a kernel with more bpf devices.

NetBSD
------

   NetBSD ships with a pcap which does not come with "pcap_freecode"
this results in a small mem leak when the client reinitializes its raw
network code. To avoid this upgrade to the latest version of pcap and
nuke your old pcap library.

OpenBSD
-------

   OpenBSD has some brain damage with its bpf structure. Unlike the
other BSD flavors it insists on using unsigned instead of signed
integers for the number of seconds in its timestamp. This results in
some inaccuracy when calculating timeouts. Lobby your OpenBSD
developers to stop the brain damage.

Overview of DHCP
================

   DHCP (Dynamic Host Configuration Protocol) is a protocol designed to
assign host configuration parameters from a DHCP server to a DHCP
client. The parameters are passed as a lease to the client.  The term
lease is used because the parameters usually have a finite lifetime.
This allows the network administrator to update the configuration
parameters, and later propogate the configuration changes to the client
hosts.

   DHCP is a convenient method of maintaining configuration across
multiple machines from a central configuration repository.

Lease Acquisition
-----------------

   The client begins by broadcasting a DHCP DISCOVER message, which
solicits DHCP leases from servers.

   DHCP OFFER messages are sent from DHCP servers to the client.

   The client picks one of the lease offers and responds with a DHCP
REQUEST. The server responds with a DHCP ACK to acknowledge, or DHCP
NACK to refuse assigning the lease.

   Once the client receives the acknowledgement it checks to see if the
IP address is currently active. If no address collision is detected,
the client performs its configuration based on the address passed from
the server and additional options provided in the lease.

   If address collision is detected, the client transmits a DHCP DECLINE
message informing the server that the lease is no good.

Lease Renewal
-------------

   Each lease comes with three timers, a rebind, renew, and an expiry
timer. If no rebind or renew time is specified the client can create
default rebind and renew timers from the expiry time.

   When these timers are reached the client attempts to renew the lease
by transmitting a DHCP REQUEST and awaiting an acknowledgement. If the
timer expires and the lease is not renewed, the client undoes the
system configuration and revers to its lease acquisition stage.

Lease Release
-------------

   When the client is shut down it sends a DHCP RELEASE message to the
server notifying it that the lease is no longer in use. The client,
also, unconfigures the host system its running on.

Other Information Worth Noting.
===============================

On Timeout Thresholds And Retries
---------------------------------

   The dhcp-agent suite never allows the user to specify exact timeout
and retries for any of its operations. This is intentional. Instead,
the user can specify an upper timeout period called a threshold and a
retry counter which is decremented per amount of times the threshold is
reached.

   For example, in the client configuration you can specify the
ARP-RETRIES and ARP-TIMEOUT-THRESHOLD. If you set the retries to two,
and the threshold to fifteen the client, when performing an ARP
operation will begin by transmitting the first datagram, and then wait
up to four seconds (default hard coded setting: see dhcp-libutil.h).
Then if this timeout is reached it will double the timeout, add a small
random amount to it, and try again. When the timeout has reached
fifteen seconds, it will consider the operation to have timed out
completely, and decrement its retry counter. Then it starts again.

   A setting of one for retries, and ten for a timeout threshold will
not cause the client to just try once, but keep trying for up to ten
seconds using the back-off algorithm mentioned above. This algorithm is
mentioned in RFC2131 and implemented for compliance. It also makes good
sense for ethernet networks that have high collisions or are congested
enough to have heavy packet loss.

dhcp-client
***********

   dhcp-client is a highly configurable and extendable DHCP client. Out
of the box it has been built to start up and perform DHCP for a basic
network setup with minimal fuss.

   This chapter of the manual is divided into two parts. A quickstart
guide written to get things up and going with minimal fuss and
absolutely no file editing, and a more in depth reference manual to
making use of the other DHCP client features.

Quickstart
==========

   These instructions are for a quick no-hassle basic DHCP client
setup. You don't really need to do much except know the following:

   bullet  That a DHCP server is waiting and responding to any queries
     originating from your machine.

   bullet  That you are using an ethernet connection and know the name
     of the network interface you are using. Under Linux this is
     usually "eth0", and under other operating systems like FreeBSD or
     Solaris the interface may be called "le0" or "xl0". If you are
     using a system with multiple network interfaces you should
     probably read the rest of this manual thoroughly to make sure your
     system is configured correctly by the client.

   bullet  You want to setup your IP address (netmask and broadcast
     included), default route, and domain name system and nothing else.

   bullet  You can send an ICMP echo request to the default gateway,
     otherwise you need to disable icmp latency discovery which is
     explained further down in the manual.

   bullet  Your PATH environment variable points to the dhcp-client
     binary.

   As the root user run the following command:

   `dhcp-client -i' <INTERFACE>

   Where you substitute <INTERFACE> with the name of your interface.
Alternatively if the interface is down you can omit the `-i' and
dhcp-client will automatically use the first ethernet interface which
is not up.

   You should be greeted by copyright text and the output of the client
as it queries the DHCP server and configures your system. Once the
client is done, it will fork into the background and continue to renew
your lease as timers expire. In the event of the lease expiring the
client will down your interface and cleanup any of the system
configuration it had done before.

   Although you can run the client in the foreground, if you do not, the
client will send the rest of its messages to syslogd. Currently it logs
to the daemon service [ FIXME: in the future the syslog service will be
configurable ].

Client Configuration
====================

   dhcp-client accepts a number of configuration directives. Some of
these can be passed by the command line, and others through a
configuration file.

Command Line Options
--------------------

   * -h     Print out usage.

   * -v     Print out version information

   * -a

     Always run in the foreground and do not go into the background.

   * -c

     Clear the cache. This is useful in case a stale cache is left
     behind by an earlier invocation.

   *  -k

     Kill a dhcp-client instance. Coupled with the -i option this will
     kill a specific instance of dhcp-client. Otherwise the first
     dhcp-client found on an active ethernet interface is terminated.

   * -p

     Forces dhcp-client to use promiscious mode. This is only useful on
     systems which are sufficiently broken that require promiscious mode
     to receive broadcast messages.

   * -w

     Wake a dhcp-client instance. Coupled with the -i option this will
     wake up a client on a specified interface and cause it to either
     rebind, or renew earlier depending on which timer is next. Use
     this to force the client to reacquire a lease.

   * -s

     Print out status information on the most recent lease acquired by
     the client.

   * -i "interface"

     Force dhcp-client to use the interface specified.  This is useful
     to make sure dhcp-client uses a specific interface if the host is
     multi-homed.

   * -l "level"

     Generate messages at the level specified. The level should be an
     integer between 0 - 4. The levels are no messages, error messages,
     info messages (default), warnings, diagnostic-messages,
     respectively.  Each level includes all messages below it.  The
     default is info messages which also include error messages.

   * -m "mac address"

     Fake a mac address during the DHCP operation. This will only affect
     the DHCP operations and no others.  This is useful for debugging a
     DHCP server that is not behaving itself.

The Configuration File
----------------------

   dhcp-client will look for two files for a configuration information.
Both these files should be located in the system configuration
directory. By default this is defined as `/usr/local/etc' You can
change this definition at build time by specifying a different prefix
to the `configure' script.

   The first file that is checked for begins with the name of the
interface the client is configuring. For example, if the name of our
interface is "xl0" then the configuration file to be checked will be
`/usr/local/etc/dhcp-agent/dhcp-client/xl0.conf'

   If this file does not exist then the default configuration file is
`/usr/local/etc/dhcp-agent/dhcp-client/default.conf'

   This allows dhcp-client to accept configuration per interface, and
use a default file without forcing the user to name interfaces in the
actual configuration file. It is believed that configuration files are
more portable this way and are not dependant on interface names.

Layout of Configuration File.
-----------------------------

   The configuration file is made up of directives. Each directive can
take up one line or fall on multiple lines . Each directive is
terminated by a semicolon. Whitespace is ignored unless it is found in
a quoted sting. Newlines are always ignored.

     foobar = yada,
     blah;

   And:

     foobar = yada, blah;

   Are the same.

   The parser also recognizes strings in two different formats. Quoted
and unquoted.

     "foobar"

   Is parsed exactly the same as

     foobar

   However:

     foo bar

   Will parse as two different strings, so you should use:

     "foo bar"

   Quoting is useful for strings which contain whitespace. Newlines in
quoted strings will result in an error unless preceeded by the '\'
character. There should be no need to put newlines in quoted strings
and expect them to be evaluated without the backslash (much like a
Makefile). If you want newlines to be silently ignored you need to
convince the author that they should be :-)

   The '\' character can be used to escape double quotes too.

Configuration Directives
------------------------

DHCP Option Handling
--------------------

   The following directives affect DHCP option handling by the client

 - Client Configuration Directive: request
     This directive instructs the client to request a set of dhcp
     options. The request directive accepts a list of strings which
     name the options to be requested:

          request subnet-mask, ip-address-lease-time, renewal-time,
                  rebinding-time, interface-mtu, domain-name, domain-name-servers;

 - Client Configuration Directive: require
     This directive instructs the client to require a set of dhcp
     options to be passed before accepting the lease. This is useful to
     make sure you receive a minimum of desired options. If these
     options are never met the client will eventually timeout. If this
     directive is not set, the first DHCP lease offer will be accepted.

          require subnet-mask, ip-address-lease-time, renewal-time,
                  rebinding-time, interface-mtu;

 - Client Configuration Directive: configure
     This directive instructs the client to only configure a set of
     dhcp options. This is useful if the server is passing you options
     you would rather not configure but implicitly claim to configure
     by accepting the lease. This directive does not define how options
     are configured. In order to define how configuration is done see
     "Writing Client Extensions," for more info.

          configure subnet-mask, ip-address-lease-time, renewal-time,
                    rebinding-time, interface-mtu, domain-name, domain-name-servers;

 - Client Configuration Directive: append
     This directive instructs the client to append a value to a DHCP
     option prior to system configuration. If this option is a list of
     values, the value specified is merely appended. If this value is a
     string the value is appended to the string. If this option is a
     single datum then you should be using the override option instead.

          append domain-name-servers = 127.0.0.1, 192.168.0.1;

 - Client Configuration Directive: prepend
     This directive works the same as the append directive only the
     values are placed at the beginning of the list or string. You
     should not use prepend on single datum options and should use
     override instead.

          prepend domain-name-servers = 127.0.0.1, 192.168.0.1;

 - Client Configuration Directive: override
     This directive instructs the client to overwrite a DHCP option
     with a specified value prior to system configuration. This is
     useful to for clients that wish to use an alternate value for a
     DHCP option than the one passed by the DHCP server.

          override domain-name = "example.com";

Variable Settings
-----------------

 - Client Configuration Directive: set
     This directive instructs the client to set a variable to a defined
     value.

          set default-subnet-mask = 255.255.255.0;

 - Client Configuration Directive: enable
     This directive instructs the client to set a boolean variable to a
     defined value. The boolean variable only accepts "yes" or "no" for
     values.

          enable do-measure-router-latency = no;

Configurable Variables
----------------------

   dhcp-client accepts a number of variables to configure default
values, values passed to the server, and control some of its behaviour.

 - Client Configuration Variable: hostname
     This variable can be set to a string which is passed as the
     hostname DHCP option to the server. This allows servers to pass
     configuration based on a hostname passed by the client. It will
     also instruct the client to set the hostname to that variable. If
     you want to pass a hostname to the server you should set this
     variable and not use the "override" directive because the
     "override" directive only affects system configuration.

          set hostname = "foo.example.com";

 - Client Configuration Variable: dhcp-discovery-retries
     This variable can be set to an integer value which instructs the
     client on how many times it should retry a DHCP discover before it
     gives up.

          set dhcp-discovery-retries = 3;

 - Client Configuration Variable: icmp-retries
     This variable can be set to an integer value which instructs the
     client on how many times it should retry an ICMP operation. This
     affects operations such as router latency discovery, ICMP netmask
     discovery etc.

          set icmp-retries = 3;

 - Client Configuration Variable: default-interface-mtu
     This variable can be set to an integer value which instructs the
     client on what it's default interface MTU should be. This MTU is
     used during the initial DHCP discovery messages, and later used to
     configure the interface MTU if no MTU was specified by the DHCP
     server.

          set default-interface-mtu = 1500;

 - Client Configuration Variable: default-subnet-mask
     This variable can be set to a netmask value to specify a default
     subnet mask in case none is provided by the DHCP operation. Unlike
     the "override" directive this provides a fallback in case no
     subnet-mask is provided by the server.

          set default-subnet-mask = 255.255.255.0;

 - Client Configuration Variable: do-measure-router-latency
     This variable can be set to a boolean value to indicate whether or
     not the client should attempt to send ICMP ECHO requests to
     routers passed by the DHCP server and determine which is one has
     the least latency to become the default route. Disabling this
     variable stops this operation from taking place, and the first
     router is used as the default route. This is useful if the routers
     cannot be reached by ICMP ECHO requests.

          enable do-measure-router-latency = yes;

Writing Client Extensions
=========================

   dhcp-client uses guile (GNU's Ubiquitous Intelligent Language for
Extensions) to extend itself. Extensions can currently be written for
handling DHCP options. The rest of this section assumes you are
familiar with the Scheme programming language. If you are not familiar
with the Scheme programming language then you cannot extend the client
to configure options is not programmed to handle.

The Sysconf Script
------------------

   dhcp-client calls a sysconf script which is placed in the same
directory as the configuration file. This script, like the
configuration file, can be named either "default.sysconf" or after an
interface name.

Looking At The Sysconf Script
-----------------------------

   The default sysconf script which is shipped with dhcp-agent is made
up of several lambda expressions which are placed inside closures. If
you're not familiar with the concept of closures, refer to the guile
documentation.

   Under each closure is a configure/unconfigure lambda expression which
are bound to top level symbols. These symbols are then placed in two
hooks, one for the bound state, and one for the release state.

The DNS Configuration: An Example
---------------------------------

   We'll walk through the DNS configuration (a very simple example) to
see how it was written. After this example a more in-depth discussion
follows. If you notice some details are skipped over, don't worry. It
will become clear later.

     (define configure-dns #f)
     (define unconfigure-dns #f)

   First we define two top level symbols to false. We'll later bind
against these in the closure.

     (let ((configured-domain-name #f)
            (configured-domain-name-servers #f)

   We begin the closure by defining two variables which will be used to
hold any configured domain name and domain name servers. For DNS
configuration these aren't useful, but for other sysconf code keeping
the values of configured data is useful when it comes time to
unconfigure the system on a DHCP RELEASE.

           ; check to see if we really need to configure
            (do-configure
             (lambda()
               (and (client-configure? client-control
               'dhcp-domain-name-servers)
                    (client-configure? client-control 'dhcp-domain-name)
                    (defined? 'dhcp-domain-name-servers)
                    (defined? 'dhcp-domain-name)))))

   Here we define a lambda expression which will tell us whether or not
we should be performing any configuration for dns. The expression will
return true if the options "domain-name-servers" and "domain-name" have
been passed to us by the DHCP server. Also it checks if the user has
requested that we configure these options.

       ; configure dns options
       (set! configure-dns

             (lambda ()
               (if (do-configure)
                   (let ((resolv-conf-file-port (open "/etc/resolv.conf" O_WRONLY 0644)))
                     (client-info-message "configuring resolver")
                     (map-in-order
                      (lambda (dns-server)
                        (simple-format resolv-conf-file-port "nameserver ~A\n" dns-server))  dhcp-domain-name-servers)
                     (simple-format resolv-conf-file-port "search ~A\n" dhcp-domain-name)
                     (close-port resolv-conf-file-port)

                                             ; now setup the options so we can use them again in unconfigure.
                     (set! configured-domain-name dhcp-domain-name)
                     (set! configured-domain-name-servers dhcp-domain-name-servers)))))

   The "configure-dns" function will first check if "do-configure"
returns true. It will then open "/etc/resolv.conf" and notify the user
that the resolver is being configured. It then writes out the contents
of the string list "dhcp-domain-name-servers" to the file prefixing
each string with the keyword "nameserver." Finally the "search" keyword
is written with the domain name.

   After the configuration is complete, the values are stored in
"configured-domain-name" and "configured-domain-name-servers."  This
allows us to remember the values in the event of unconfiguring the
system.

                                             ; unconfigure dns options
       (set! unconfigure-dns
             (lambda()

              ; We shouldn't really be doing anything. Any name server
              ; is a good server :-)

               #t)))

   As mentioned in the comment there's no need to do any
unconfiguration. We'd rather have a resolv.conf than delete it. You can
always modify this to delete the file, or insert a different set of
values.

     (add-hook! dhcp-bind-hook configure-dns)
     (add-hook! dhcp-release-hook unconfigure-dns)

   Finally the two routines are bound to the DHCP BIND and DHCP RELEASE
hooks. It is important to add the option handlers in reverse order.
You'll notice "configure-interface" is added last so that the interface
is configured first.

The DHCP BOUND and DHCP RELEASE hooks
-------------------------------------

   Two hooks are defined at the top level by the client.
"dhcp-bind-hook" and "dhcp-release-hook." When the client wants to
configure itself it will call "dhcp-bind-hook" and when it releases its
lease it will call "dhcp-release-hook."

How DHCP Options Are Passed To The Sysconf Script
-------------------------------------------------

   When the DHCP BOUND hook is called, all the options are defined as
top level symbols which refer to either a string, or a list of strings
depending on whether the option is a single atom, or a list of atoms [
TODO: make list of handled options along with their types. ]

   In order to check for the existance of an option, simple use
"defined?" to check if the symbol is bound.

     ; check for the routers option

     (defined? 'dhcp-routers)

   This will return a boolean value of true of false depending on
whether the DHCP option has been bound at the top level.

Scheme Routines Provided By The Client
--------------------------------------

   At the top level a "client-control" symbol is bound to a control
object which is used in every invocation of routines provided by the
client.

 - Client Sysconf Routine: client-configure? client-control
          option-symbol
     Returns #t of #f depending on whether or not the user has
     explicitly stated that the dhcp option should be configured.

 - Client Sysconf Routine: client-interface-up client-control
          ip-address netmask mtu
     Initializes the network interface the client is handling and
     assigns the requested IP-ADDRESS, the NETMASK and MTU.

 - Client Sysconf Routine: client-set-default-route client-control
          ip-address
     Sets the default route to the IP-ADDRESS specified.

 - Client Sysconf Routine: client-remove-default-route client-control
          ip-address
     Removes the default route to the IP-ADDRESS specified.

 - Client Sysconf Routine: client-get-default-mtu client-control
     Returns the default mtu specified by the user.

 - Client Sysconf Routine: client-get-default-subnet-mask client-control
     Returns the default subnet-mask specified by the user.

 - Client Sysconf Routine: client-info-message string
     Prints out the string using the client's "info_message" routine.

 - Client Sysconf Routine: client-error-message string
     Prints out the string using the client's "error_message" routine.

 - Client Sysconf Routine: client-fatal-message string
     Prints out the string using the client's "fatal_message" routine.
     This exits after passing the message to the user.

 - Client Sysconf Routine: client-shutdown client-control
     Invokes the "shutdown" routine in the client and causes the client
     to exit as cleanly as possible, relinquishing any leases it has.
     Warning! This should not be called from within a release hook.

 - Client Sysconf Routine: client-discover-icmp-latency client-control
          address-list
     Accepts a list of addresses ADDRESS-LIST and performs ICMP ECHO
     latency tests to determine which host is responding fastest. A
     list of address/average-latency pairs is returned.

 - Client Sysconf Routine: client-do-discover-icmp-latency
          client-control
     Returns #t if the user enabled "do-measure-router-latency" or #f if
     not.

Why Not Just Use A Shell Script?
--------------------------------

   Traditionally the UNIX initialization process is programmed with
simple shell scripts. This is fine because all the variables are passed
from the local system (usually set by the administrator or vendor).

   In the case of DHCP configuration data is passed from a server which
ought to be handled as untrustworthy data unless you're willing to use
it in a certain way.

   For example, assuming you wish to configure your hostname according
to the DHCP server, you will receive the hostname as a string and use
that string for your hostname. You don't, though, want the hostname
passed to be arbitrary shell code which is run inadvertently from your
shell script.

   Most DHCP clients in the wild have fixed this problem by quoting the
DHCP options as they are passed to the shell. I still foresee this as
problematic, and a security hazard.

   Guile offers the Scheme programming language which is an incredibly
small subset of Lisp. It's easy to pick up. The DHCP options are passed
to the Scheme system configuration script as strings or lists of
strings. These strings are harmless unless you are specifically asking
the Scheme interpreter to evaluate them.

   On the other hand most casual users just want basic networking up,
and the ability to tweak certain options. The DHCP client already offers
this to anyone, irregardless of their knowledge of Scheme.

Advanced Client Use
===================

   [ TODO ]

dhcp-server
***********

   [ TODO ]

dhcp-sniff
**********

   [ TODO ]

dhcp-relay
**********

   [ TODO ]