331 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			331 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
ndhc, Copyright (C) 2004-2014 Nicholas J. Kain.
 | 
						|
See LICENSE for licensing information.  In short: Two-clause / New BSD.
 | 
						|
 | 
						|
Requirements:
 | 
						|
 | 
						|
Linux kernel
 | 
						|
GNU Make (tested: 3.82) or CMake (tested: 2.8)
 | 
						|
libcap (available via ftp.kernel.org)
 | 
						|
Ragel (tested: 6.7)
 | 
						|
 | 
						|
INTRODUCTION
 | 
						|
------------
 | 
						|
 | 
						|
ndhc is a multi-process, privilege-separated dhcp client.  Each subprocess runs
 | 
						|
with the minimal necessary privileges in order to perform its task.  Currently,
 | 
						|
ndhc consists of two subprocesses: the ndhc-master and ndhc-ifch.
 | 
						|
 | 
						|
ndhc-master communicates with dhcp servers and handles the vagaries of the dhcp
 | 
						|
client protocol.  It runs as a non-root user inside a chroot.  ndhc retains
 | 
						|
only the minimum necessary set of privileges required to perform its duties.
 | 
						|
These powers include the ability to bind to a low port, the ability to open a
 | 
						|
raw socket, and the ability to communicate on broadcast channels.  ndhc holds
 | 
						|
no other powers and is restricted to a chroot that contains nothing more than a
 | 
						|
domain socket filesystem object (if using syslog), a urandom device node, and a
 | 
						|
null device node.
 | 
						|
 | 
						|
ndhc-ifch handles interface change requests.  It listens on a shared pipe for
 | 
						|
such requests.  ndhc-ifch runs as a non-root user inside a chroot, and retains
 | 
						|
only the power to configure network interfaces.  ndhc-ifch automatically forks
 | 
						|
from ndhc-master to perform its job.
 | 
						|
 | 
						|
ndhc fully implements RFC5227's address conflict detection and defense.  Great
 | 
						|
care is taken to ensure that address conflicts will be detected, and ndhc also
 | 
						|
has extensive support for address defense.  Care is taken to prevent
 | 
						|
unintentional ARP flooding under any circumstance.
 | 
						|
 | 
						|
ndhc also monitors hardware link status via netlink events and reacts
 | 
						|
appropriately when interface carrier status changes or an interface is
 | 
						|
explicitly deconfigured.  This functionality can be useful on wired networks
 | 
						|
when transient carrier downtimes occur (or cables are changed), but it is
 | 
						|
particularly useful on wireless networks.
 | 
						|
 | 
						|
RFC3927's IPv4 Link Local Addressing is not supported.  I have found v4 LLAs
 | 
						|
to be more of an annoyance than a help.  v6 LLAs work much better in practice.
 | 
						|
 | 
						|
FEATURES
 | 
						|
--------
 | 
						|
 | 
						|
Privilege-separated.  ndhc does not run as root after initial startup, and
 | 
						|
capabilities are divided between the subprocesses.  Both programs run in a
 | 
						|
chroot.
 | 
						|
 | 
						|
Robust.  ndhc performs no runtime heap allocations -- malloc() is never called
 | 
						|
(and neither is brk(), mmap(), etc), and ndhc never performs recursive calls
 | 
						|
and only stack-allocates fixed-length types, so stack depth is bounded, too.
 | 
						|
 | 
						|
Active defense of IP address and IP collision avoidance.  ndhc fully implements
 | 
						|
RFC5227.  It is capable of both a normal level of tenacity in defense, where
 | 
						|
it will eventually back off and request a new lease if a peer won't relent
 | 
						|
in the case of a conflict, and of relentlessly defending a lease forever.  In
 | 
						|
either mode, it rate-limits defense messages, so it can't be tricked into
 | 
						|
flooding by a hostile peer or DHCP server, either.
 | 
						|
 | 
						|
Small.  Both ndhc avoids unnecessary outside dependencies and is written in
 | 
						|
plain C.  The only library used is libcap, as the raw raw kernel API for
 | 
						|
capabilities is not guaranteed to stay stable.
 | 
						|
 | 
						|
Fast.  ndhc filters input using the BPF/LPF mechanism so that uninteresting
 | 
						|
packets are dropped by the operating system before ndhc even sees the data.
 | 
						|
ndhc also only listens to DHCP traffic when it's necessary.
 | 
						|
 | 
						|
Flexible.  ndhc can request particular IPs, send user-specified client IDs,
 | 
						|
write a file that contains the current lease IP, write PID files, etc.
 | 
						|
 | 
						|
Self-contained.  ndhc does not exec other processes, or rely on the shell.
 | 
						|
 | 
						|
Aware of the hardware link status.  If you disconnect an interface on which
 | 
						|
ndhc is providing dhcp service, it will be aware.  When the link status
 | 
						|
returns, ndhc will fingerprint the reconnected network and make sure that it
 | 
						|
corresponds to the one on which it has a lease.  If the new network is
 | 
						|
different, it will forget about the old lease and request a new one.
 | 
						|
 | 
						|
USAGE
 | 
						|
-----
 | 
						|
 | 
						|
1) Compile and install ndhc.
 | 
						|
    a) make
 | 
						|
    b) Install the build/ndhc executable in a normal place.  I would suggest
 | 
						|
       /usr/sbin or /usr/local/sbin.
 | 
						|
 | 
						|
1alt) Compile and install ndhc.
 | 
						|
    a) Create a build directory:
 | 
						|
       mkdir build && cd build
 | 
						|
    b) Create the makefiles:
 | 
						|
       cmake ..
 | 
						|
    c) Build ndhc:
 | 
						|
       make
 | 
						|
    d) Install the ndhc/ndhc executable in a normal place.  I would suggest
 | 
						|
       /usr/sbin or /usr/local/sbin.
 | 
						|
 | 
						|
2) Time to create the jail in which ndhc will run.
 | 
						|
    a) Become root and create new group "ndhc".
 | 
						|
 | 
						|
        $ su -
 | 
						|
        # umask 077
 | 
						|
        # groupadd ndhc
 | 
						|
 | 
						|
    b) Create new users "dhcpifch" and "dhcp".  The primary group of these
 | 
						|
       users should be "ndhc".
 | 
						|
 | 
						|
        # useradd -d /var/lib/ndhc -s /sbin/nologin -g ndhc dhcpifch
 | 
						|
        # useradd -d /var/lib/ndhc -s /sbin/nologin -g ndhc dhcp
 | 
						|
 | 
						|
    c) Create the state directory where DUIDs and IAIDs will be stored.
 | 
						|
 | 
						|
        # mkdir /etc/ndhc
 | 
						|
        # chown root.root /etc/ndhc
 | 
						|
        # chmod 0755 /etc/ndhc
 | 
						|
 | 
						|
    d) Create the jail directory and set its ownership properly.
 | 
						|
 | 
						|
        # mkdir /var/lib/ndhc
 | 
						|
        # chown root.root /var/lib/ndhc
 | 
						|
        # chmod a+rx /var/lib/ndhc
 | 
						|
        # cd /var/lib/ndhc
 | 
						|
        # mkdir var
 | 
						|
        # mkdir var/state
 | 
						|
        # mkdir var/run
 | 
						|
        # chown -R dhcp.ndhc var
 | 
						|
        # chmod -R a+rx var
 | 
						|
        # chmod g+w var/run
 | 
						|
 | 
						|
    e) Create a urandom device for ndhc to use within the jail.
 | 
						|
 | 
						|
        # mkdir dev
 | 
						|
        # mknod dev/urandom c 1 9
 | 
						|
        # mknod dev/null c 1 3
 | 
						|
        # chown -R root.root dev
 | 
						|
        # chmod a+rx dev
 | 
						|
        # chmod a+r dev/urandom
 | 
						|
        # chmod a+rw dev/null
 | 
						|
 | 
						|
        f) (optional) If you wish for logging to properly work, you
 | 
						|
           will need to properly configure your logging daemon so that it
 | 
						|
           opens a domain socket in the proper location within the jail.
 | 
						|
           Since this varies per-daemon, I cannot provide a general
 | 
						|
           configuration.
 | 
						|
 | 
						|
3) At this point the jail is usable; ndhc is ready to be used.  An example
 | 
						|
   of invoking ndhc:
 | 
						|
 | 
						|
    # ndhc -b -i wan0 -u dhcp -U dhcpifch -C /var/lib/ndhc -l /var/state/wan0.lease
 | 
						|
 | 
						|
4o) If you encounter problems, I suggest running ndhc in the foreground and
 | 
						|
    examining the printed output.
 | 
						|
 | 
						|
 | 
						|
BEHAVIOR NOTES
 | 
						|
--------------
 | 
						|
 | 
						|
ndhc does not enable updates of the local hostname and resolv.conf by default.
 | 
						|
If you wish to enable these functions, use the --resolve (-R) and --hostname
 | 
						|
(-H) flags.  See ndhc --help.
 | 
						|
 | 
						|
STATE STORAGE NOTES
 | 
						|
-------------------
 | 
						|
 | 
						|
ndhc requires a read/writable directory to store the DUID/IAID states.  By
 | 
						|
default this directory is /etc/ndhc.  It exists outside the chroot.  The DUID
 | 
						|
will be stored in a single file, DUID.  The IAIDs exist per-interface and are
 | 
						|
stored in files with names similar to IAID-xx:xx:xx:xx:xx:xx, where the xx
 | 
						|
values are replaced by the Ethernet hardware address of the interface.
 | 
						|
 | 
						|
If it is impossible to read or store the DUIDs or IAIDs, ndhc will
 | 
						|
fail at start time before it performs any network activity or forks any
 | 
						|
subprocesses.
 | 
						|
 | 
						|
If the host system lacks volatile storage, then a clientid should manually
 | 
						|
be specified using the -c or --clientid command arguments.
 | 
						|
 | 
						|
RANDOMNESS NOTES
 | 
						|
----------------
 | 
						|
 | 
						|
Each ndhc subprocess maintains a combined Tausworthe PRNG that is uniquely
 | 
						|
seeded from the kernel random device at startup.  Each PRNG consumes 128 bits
 | 
						|
of entropy for its initial state.
 | 
						|
 | 
						|
DHCP does not require cryptographic randomness, so this arrangement should
 | 
						|
be more than sufficient to ensure proper UUIDs, assuming only that the
 | 
						|
kernel random device is even minimally seeded with real entropy.
 | 
						|
 | 
						|
PORTING NOTES
 | 
						|
-------------
 | 
						|
 | 
						|
DHCP clients aren't naturally very portable.  It's necessary to perform a lot
 | 
						|
of tasks that are platform-specific.  ndhc is rather platform-dependent, and it
 | 
						|
extensively uses Linux-specific features.  Some of these features are also
 | 
						|
available on the BSDs.
 | 
						|
 | 
						|
1) ndhc takes advantage of Linux capabilities so that it does not need full
 | 
						|
root privileges.  Capabilities were a proposed POSIX feature that was not made
 | 
						|
part of the official standard, so any implemention that may exist will be
 | 
						|
system-dependent.
 | 
						|
 | 
						|
2) ndhc configures network interfaces and routes.  Interface and route
 | 
						|
configuration is entirely non-portable.
 | 
						|
 | 
						|
3) ndhc uses netlink sockets extensively for fetching data, setting data,
 | 
						|
and hardware link state change notification events.
 | 
						|
 | 
						|
4) ndhc uses the Berkeley Packet Filter / Linux Packet Filter interfaces to
 | 
						|
drop unwanted packets in kernelspace.  This functionality is available on
 | 
						|
most modern unix systems, but it is not standard.
 | 
						|
 | 
						|
5) ndhc uses epoll() and signalfd().  These are Linux-specific.
 | 
						|
 | 
						|
6) Numerous socket options are used, and the AF_PACKET socket family is used
 | 
						|
for raw sockets and ARP.  These are largely Linux-specific, too.
 | 
						|
 | 
						|
7) ndhc can optionally use seccomp-filter to allow only a set of whitelisted
 | 
						|
syscalls.  This functionality is Linux-specific.
 | 
						|
 | 
						|
HISTORY
 | 
						|
-------
 | 
						|
 | 
						|
I started writing ndhc back in 2004.  My ISP at the time required a dhcp
 | 
						|
client for connection authentication, and I was not comfortable with any
 | 
						|
of the existing clients, which all ran as root and had colorful security
 | 
						|
histories.  DHCP is generally not a routed protocol, and lacks real
 | 
						|
authentication mechanisms in real world deployments (some largely
 | 
						|
abandoned RFCs for such behavior do exist), so no program existed to
 | 
						|
fill the niche of a truly secure DHCP client.
 | 
						|
 | 
						|
My router/server at the time ran a custom Linux distro that was designed
 | 
						|
for extreme security.  A root privileged DHCP client would be nearly the
 | 
						|
only root-owned process running on the machine, so I was highly motivated
 | 
						|
to develop an alternative.
 | 
						|
 | 
						|
A separate ifchd was first written entirely from scratch.  It did not take long
 | 
						|
to write, since it was by design rather simple, and I was already familiar with
 | 
						|
the quirks of Linux capabilities.  That left me with the choice of adapting an
 | 
						|
existing DHCP client or writing my own from scratch.
 | 
						|
 | 
						|
At the time, I just wanted something that would work, so my choice was to
 | 
						|
adapt udhcpc to work with ifchd.  udhcpc was chosen since it was intended to
 | 
						|
be used with resource-constrained or embedded systems, and was thus very
 | 
						|
small.  ISC dhclient was another alternative, but it is an extremely large
 | 
						|
program, and it would have been very hard to audit it for correctness.
 | 
						|
 | 
						|
udhcpc was not did not really fit my requirements well, since it was designed
 | 
						|
to be small at all costs, sacrificing correctness when necessary.  The code was
 | 
						|
hard to follow, and had many quirks.  Bounds-checking was rare, type aliasing
 | 
						|
common, and state transitions were convoluted.  Not all of the client was
 | 
						|
asynchronous, and no precautions were taken against conflicting peers.  ARP was
 | 
						|
not used at all.
 | 
						|
 | 
						|
However, it was small.  With a lot of work, I ripped out the script-calling
 | 
						|
mechanisms and replaced them with ifchd requests.  Bounds-checking was
 | 
						|
aggressively (and somewhat hamfistedly) retrofitted into the code.  It was
 | 
						|
cleaned to a degree, and importantly it worked for connecting to my ISP.
 | 
						|
 | 
						|
Then I changed ISPs.  My new ISP used PPPoE, not dhcp.  Around the same time, I
 | 
						|
also switched to using Gentoo rather than a hand-built distribution.  I didn't
 | 
						|
have time to maintain the old custom setup, and it was very hard keeping up
 | 
						|
with library vulnerabilties in eg, zlib or openssl, and ensuring that all
 | 
						|
installed binaries, dynamic and static, were updated.  ndhc was abandoned for
 | 
						|
many years.  It wasn't needed on my server, and it was "too much effort" to
 | 
						|
deviate from the stock distro dhcp clients on other machines.
 | 
						|
 | 
						|
Then, around 2008, I changed ISPs again.  This time my new ISP used dhcp and
 | 
						|
not PPPoE.  So, after a few months, I decided to dust off the old ndhc/ifchd
 | 
						|
project and adapt it to my modern standards and machines.
 | 
						|
 | 
						|
ifchd was in good shape and required little work.  I ended up rewriting
 | 
						|
ndhc.  The only parts that remained from the original were the parts that
 | 
						|
I had already rewritten before, and some of those were rewritten, too.
 | 
						|
 | 
						|
Eventually ifchd was rewritten to extensively use a Ragel-generated DFA-based
 | 
						|
parser to make it easier to verify correct behavior for all possible inputs.
 | 
						|
 | 
						|
Quite a while later, I eventually merged ifchd into the same binary as
 | 
						|
ndhc and instead rely on forking subprocesses and using pipes for IPC.  This
 | 
						|
brought a lot of simplifications, particularly for user configuration.
 | 
						|
 | 
						|
The end result is a modern DHCP client is largely RFC-compliant, except where
 | 
						|
the RFCs dictate behavior that would be problematic, overly complex, useless,
 | 
						|
or exploitable.  DHCP is poorly specified, and real-world servers and clients
 | 
						|
vary a lot from the RFCs, so these conditions are necessary for a useful
 | 
						|
program.
 | 
						|
 | 
						|
Although ndhc's implementation and behavior are different, I have to credit
 | 
						|
the idea of using netlink events to discover hardware link status transitions
 | 
						|
to Stefan Rompf and his 'dhcpclient' program.  The Linux netlink events that
 | 
						|
are used are otherwise rather obscure and poorly documented, and I wouldn't
 | 
						|
have known about them otherwise.
 | 
						|
 | 
						|
GRSECURITY NOTES
 | 
						|
----------------
 | 
						|
 | 
						|
Make sure that CONFIG_GRKERNSEC_CHROOT_CAPS is disabled.  Otherwise, ndhc will
 | 
						|
lose its capabilities (in particular, the ability to reconfigure interfaces)
 | 
						|
when it chroots.
 | 
						|
 | 
						|
DHCP PROTOCOL QUIRKS
 | 
						|
--------------------
 | 
						|
 | 
						|
Send a packet that has an options field set to:
 | 
						|
'DHCP-OPTION-OVERLOAD:3'
 | 
						|
Then in the file and sname fields:
 | 
						|
'DHCP-OPTION-OVERLOAD:3'
 | 
						|
I suspect some bad dhcp programs will hang given this input.
 | 
						|
 | 
						|
DHCP explicitly specifies that there is no minimum lease time and also
 | 
						|
specifies that the minimum default rebinding time is leasetime*0.875 and
 | 
						|
the minimum default renewing time is leasetime*0.500.  All times are relative
 | 
						|
to the instant when the lease is bound and are specified in seconds.  Taken
 | 
						|
together, this means that a client strictly implementing the RFC should
 | 
						|
accept a lease that either is perpetually rebinding (lease == 1s) or instantly
 | 
						|
expires (lease == 0s).  ndhc ignores the RFC and specifies a minimum lease
 | 
						|
time of one minute.
 | 
						|
 | 
						|
Renew and rebind times are optionally specified and may take on any value.
 | 
						|
This means that a malicious server could demand a rebind time before a renew
 | 
						|
time, or make these times ridiculously short, or specify both times past
 | 
						|
that of the lease duration.  ndhc avoids all of this nonsense by simply
 | 
						|
ignoring these options and using the default values specified by the RFC.
 | 
						|
 | 
						|
There are other quirks, but these are just several interesting ones that
 | 
						|
immediately occur to me while I'm writing this document.
 | 
						|
 |