Overview

  -------------------------------------------------------------------------
  LIBDHCP : library programming interface to the ISC DHCP and DHPv6 clients

                             An Overview

    author: Jason Vas Dias<jvdias@redhat.com> Red Hat, Inc, May 2006
  -------------------------------------------------------------------------

LIBDHCP enables programs to invoke the ISC dhclient (IPv4 DHCP) and DHCPv6 client (IPv6 DHCP) libraries, libdhcp4client and libdhcp6client, within one process, to use the lease objects returned to configure network interface parameters, and provides a full-featured Network Interface Configuration (NIC) library for static or dynamic network parameter configuration.

The primary, tested, intended interface is as follows, though there are quite a few other ways of using the code (by no means all of them bug free ! :-).

First, include the main interface header:

#include <libdhcp/dhcp_nic.h>

it will include all required headers from libdhcp.

Choose which IPv6/IPv4 DHCP policy you wish libdhcp to implement, from the enum type "DHCP_Preference" in dhcp_nic.h :

    DHCPv4_DISABLE  = 1,  /* Don't do IPv4 DHCP */
    DHCPv6_DISABLE  = 2,  /* Don't do IPv6 DHCP */
    IPv6_PREFERENCE = 4,  /* Configure IPv6 addresses / routes / DNS first */ 
    DHCPv4_DISABLE_ADDRESSES = 8,  /* Don't configure DHCPv4 addresses */
    DHCPv4_DISABLE_ROUTES    = 16, /* Don't configure DHCPv4 routes */
    DHCPv4_DISABLE_RESOLVER  = 32, /* Don't configure DHCPv4 resolv.conf entries */
    DHCPv6_DISABLE_ADDRESSES = 64, /* Don't configure DHCPv6 address (ie. use radvd) */
    DHCPv6_DISABLE_RESOLVER  = 128,/* Don't configure DHCPv6 resolv.conf entries */
    DHCPv4_DISABLE_HOSTNAME_SET=256/* Don't set hostname if DHCPv4 host-name option sent */ 
    DHCP_ACCEPT_FIRST_LEASE  = 512 /* If timeout == 0, v4 and v6 clients are in separate
 processes; if this preference is set, the first client
 to get a lease will cause that lease to be accepted
 and the other client process to be terminated. */

You can specify the "capabilities" the DHCP clients are allowed to exercise, with enum type "DHCP_Capability" from libdhcp.h:

 /* DHCP client "capabilities" */ 
    DHCP_USE_LEASE_DATABASE   = 1,      /* use / do not use persistent lease database files */
    DHCP_USE_PID_FILE         = 2,      /* use / do not use pid file                        */
 /*
 DHCPv6 supports these capabilities in process, 
 while the DHCPv4 client would fork and exec the dhclient-script to implement them if these
 bits are set - otherwise, if no bits are set, the callback is called and the script is 
 not run.
  */
    DHCP_CONFIGURE_INTERFACES = 4,      /* configure interfaces UP/DOWN as required         */
    DHCP_CONFIGURE_ADDRESSES  = 8,      /* configure interface addresses as required        */
    DHCP_CONFIGURE_ROUTES     =16,      /* configure routes as required                     */
    DHCP_CONFIGURE_RESOLVER   =32,      /* configure resolv.conf as required                */
    /* DHCPv6 only: */
    DHCP_CONFIGURE_RADVD      =64,      /* configure radvd.conf & restart radvd as required */

Then, call the main dhcp interface function, 'do_dhcp' (from dhcp_nic.h) with the values you chose:

extern 
NIC_Res_t *do_dhcp
( 
    DHCP_Preference preference,
    char                  *eth_if_name,
    LIBDHCP_Capability     dhc_cap, 
    time_t                 timeout,    
    LIBDHCP_Error_Handler  error_handler,
    uint8_t                log_level,
    ... /* extra DHCPv4 dhclient arguments;
 last arg MUST be 0 .
         */
);

The timeout is a hard timeout that each client has to contact the server, until it gives up.

If the timeout is 0, and neither DHCPv4_DISABLE nor DHCPv6_DISABLE is specified, then each client will be run in a separate process, and each client will try to contact the server indefinitely until a lease is obtained.

You can define an error handler to handle all debug / error / info log messages emanating from the clients:

 int( *LIBDHCP_Error_Handler )
    ( struct libdhcp_control_s *ctl,
      int priority,  /* ala syslog(3): LOG_EMERG=0 - LOG_DEBUG=7 (+ LOG_FATAL=8 : finished -> 1)   */
      const char *fmt,
      va_list ap
    );

"log_level" in the dhcp_nic call sets the maximum level that will be logged. Two standard error handlers are defined:

  libdhcp_stderr_logger  : writes log messages to stderr
  libdhcp_syslogger      : writes log messages to syslog
No logging is done if "error_handler" is 0 .

You can specify any normal dhclient command line arg in the va_list .

ie. a call that specifies that libdhcp should runs DHCPv4 and DHCPv4, for interface named "eth0", with extra dhclient args to send the dhcp-client-identifier, vendor-class-identifier, and host-name would be:

    NIC_Res_t r =
    dhcp_nic
    ( 0,
      "eth0",
      0,
      10, /* each client has 10 seconds to get a lease */
      libdhcp_stderr_logger,
      LOG_INFO,
      "-V","i386-redhat-fc6",
      "-I", "1:00:0D:60:CF:98:E3",
      "-H", "mypc",
      0 /* !!! THE ESSENTIAL TRAILING 0 !!! */
    );
This call would then actually invoke the clients in the same process as the caller (no forks / execs / system() calls involved) and would configure the network interface "eth0" with the parameters returned.

You may want to inspect and modify the network parameters returned by DHCP before going ahead and configuring the interface. For this, the 'dhcp_nic' function is provided:

extern 
DHCP_nic *dhcp_nic
( 
    NLH_t nh, /* nic library handle */
    DHCP_Preference preference,
    char                  *eth_if_name,
    LIBDHCP_Capability     dhc_cap, 
    time_t                 timeout,    
    LIBDHCP_Error_Handler  error_handler,
    uint8_t                log_level,
    ... /* extra DHCPv4 dhclient arguments;
 last arg MUST be 0 .
         */
);

This returns a "Network Interface Configuration" ( NIC ) structure - well, actually two NIC structures, one for each client, encapsulated in the "DHCP_nic" structure - at this point, no actual interface configuration beyond setting the interface flags to (IFF_UP & IFF_RUNNING) would have been done.

( That is, of course, only if you have both the DHCPv6 IPv6 DHCP dhcp6s server and the IPv4 DHCP ISC dhcpd server running on your network. An example working /etc/dhcp6s.conf for dhcp6s is:

  ---
  # /etc/dhcp6s.conf
  interface eth0 {
   link eth0 {
        range fec0::10 to fec0::19/64;
   };
  };
  ---
  - see 'man 5 dhcps.conf' and 'man 5 dhcpd.conf'.
).

The dhcp_nic structure can then be configured on the network interfaces with the call:

   dhcp_nic_configure( nic );

If this returns 0, all configuration parameters have been successfully applied.

libdhcp contains an interface to the 'libnl' (lib NETLINK) library, ( by Thomas Graf, http://people.suug.ch/~tgr/libnl/ ), in the "nic.[ch]" files, that implement its "Network Interface Configurator", which enables the DHCP client lease parameters to be configured on the network devices - it is also a full-featured static network configurator.

For example, you can use this function:

extern
NIC_Res_t 
nic_configure
(
    NLH_t          nh,
    NIC_t          nic,
    IPaddr_list_t  *addresses,
    IProute_list_t *routes,
    IPaddr_list_t  *dns_servers,
    char           *search_list,
    char           *host_name
);

to configure a list of addresses and routes on an interface , and to configure resolv.conf and set the host name. Zero-valued pointer parameters are safely ignored. For instance, the calls :

        NIC_t nic = nic_by_name("eth0");
        IPaddr_t 
            a1 = nic_addr_from_text("172.16.80.1/22"),
            a2 = nic_addr_from_text("192.168.2.1/16"),
            a3 = nic_addr_from_text("2006:0:0:5::18/64"),
            gw1= nic_addr_from_text("192.168.2.254"),
            n2 = nic_addr_from_text("2006:0:0:6::/64"),
            gw2= nic_addr_from_text("2006:0:0:5::100"),
            ns1= nic_addr_from_text("192.168.2.200"),
            ns2= nic_addr_from_text("2006:0:0:5::200");
        int sa_len;
        IProute_t
            r1 = nic_route_new
                 (  nh,
                    nic_get_index(nic),
                    0,                             /* destination: 0 means "default" */ 
                    32,                            /* destination prefix bits - 32 for default */
                    nic_addr_sa(gw1,&sa_len),      /* gateway */
                    -1,                            /* default scope: global */
                    -1                          ,  /* no priority: increase if more than one default! */
                    -1,                            /* table: default (local) */
                    -1/*no iif*/, 0L,0/*no src*/                    
                  ),
             r2 = nic_route_new
                  ( nh, nic_get_index(nic), 
                    nic_addr_sa(n2,&sa_len), 
                    nic_addr_get_prefix(n2),
                    nic_addr_sa(gw2,&sa_len),
                    -1,-1,-1,-1, 0L, 0
                  );
        IPaddr_list_t 
             al = nic_address_list_new(a1,a2,a3,0),
             nl = nic_address_list_new(ns2,ns1,0);
        IProute_list_t
             rl = nic_route_list_new(r1,r2,0);
        nic_set_flags( nic, IFF_UP | IFF_RUNNING );
        nic_configure( nh, nic, al, rl, nl, "my.domain.com", "myhost");

Would configure the "myhost.my.domain.com" interface eth0 with the addresses 172.16.80.1/22, 192.168.2.1/16, and 2006:0:0:5::18/64, the default gateway 192.168.2.254, the additional route '2006:0:0:6::/64 via 2006:0:0:5::100', and DNS nameservers 192.168.2.200 and 2006:0:0:5::1, IFF all goes according to plan (and you are running the program as root!).

The complete details of each DHCP lease are also encapsulated in the DHCP_nic 'lease' field, including all the lease times - see 'dhcp4_lease.h' and 'dhcp6_lease.h' for details.

DHCv6 does not return options of interest that are not handled by the default configurer, and there is no support in DHCPv6 for user defined options, as yet.

But ISC IPv4 DHCP returns MANY options that are not handled in libdhcp, and supports user defined options, and libdhcp fully supports this also.

Having obtained a DHCPv4_lease in a DHCP_nic 'nic' as nic->dhcp4_lease, you can define your own option handler:

void dhcp4_nic_option_handler( DHCPv4_option *option, void *arg )
{
    switch ( option->unicode )
    { 
    case DHCP_UNIVERSE:
        switch ( option->code )
        case MY_OPTION_CODE:
            struct my_option *opt = (void*)&(option->value);
            ...
    }
}

and the dhcp4_lease code will guarantee that the option is correctly laid out as a C structure according to the DHCP option format.

For instance, you could have in your server dhcpd.conf and client dhclient.conf:

' 
  option space redhat;
  option redhat.install-server code 1 = domain-name;
  option redhat.kickstart code 2 = string;
  option redhat.install-iso code 3 = string;
  option redhat.routes code 4 = array of { ip-address, int8, ip-address, int8, int16, int8, int32 };
'

And in the dhcpd server's dhcpd.conf:

'
  class "vendor-classes" {
         match option vendor-class-identifier;
       }

  subclass "vendor-classes" "i386-redhat-fc6" {
	option redhat.install-server my.i386repo.server.com;
	option redhat.kickstart "i386-fc6.ks";
  }

  subclass "vendor-classes" "ia64-redhat-fc6" {
	option redhat.install-server my.ia64repo.server.com;
	option redhat.kickstart "ia64-fc6.ks";
  }
  
  option redhat.routes 1.2.3.4 2 4.3.2.1 1 512 2 0xfabdab,
	               8.4.2.1 6 1.4.2.8 4 768 1 0xdabfab;

  option dhcp.redhat-encapsulation code 128 = encapsulate redhat;
'

And the client must be configured to request these options in dhclient.conf:

   request dhcp.redhat-encapsulation;

Then you can define an option handler to handle these options:

void dhcp4_nic_option_handler( DHCPv4_option *option, void *arg )
{
    switch ( option->unicode )
    { 
    case REDHAT_UNIVERSE:
        switch ( option->code )
        case REDHAT_ROUTES:
            struct r_r
            {   
               u_int32_t ip1;
               u_int8_t   b1;   
               u_int32_t ip2;
               u_int16_t  s1;
               u_int8_t   b2;
               i_int32_t  i1;
            } * r = (void*) &(option->value),
 e = &(((r_r*) &(option->value))[option->n_elements]);
            for(; r < e; r++)                           
               ...      
    }
}

and call the function:

     dhcpv4_process_options ( nic->lease.dhcpv4_lease, dhcp4_nic_option_handler, arg)
and the dhcp4_nic_option_handler will be called for each DHCP optoin in the lease, and for the "REDHAT_ROUTES" option, the 'r_r' structure members will be correctly laid out for access by your C program.

CAVEATS:

ABOVE ALL: USE AT YOUR OWN RISK ! the code is very new and is still in further development and refinement.

Please report any issues / suggestions / ideas via Red Hat Bugzilla or by email to jvdias@redhat.com. /**


Generated on Fri Oct 13 18:20:34 2006 for libdhcp by  doxygen 1.4.7