------------------------------------------------------------------------- LIBDHCP : library programming interface to the ISC DHCP and DHPv6 clients An Overview Copyright (C) 2006 Red Hat, Inc. All rights reserved. Red Hat Authors: Jason Vas Dias <jvdias@redhat.com> David Cantrell <dcantrell@redhat.com> ------------------------------------------------------------------------- LIBDHCP enables programs to invoke the ISC dhclient (IPv4 DHCP) and DHCPv6 client (IPv6 DHCP) libraries, libdhcp4client and libdhcp6client, within one process, and to use the lease objects returned to configure network interface parameters. 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 - enum type "DHCP_Preference" in dhcp_nic.h : /* Don't do IPv4 DHCP */ DHCPv4_DISABLE = 1, /* Don't do IPv6 DHCP */ DHCPv6_DISABLE = 2, /* Configure IPv6 addresses / routes / DNS first */ IPv6_PREFERENCE = 4, /* Don't configure DHCPv4 addresses */ DHCPv4_DISABLE_ADDRESSES = 8, /* Don't configure DHCPv4 routes */ DHCPv4_DISABLE_ROUTES = 16, /* Don't configure DHCPv4 resolv.conf entries */ DHCPv4_DISABLE_RESOLVER = 32, /* Don't configure DHCPv6 address (ie. use radvd) */ DHCPv6_DISABLE_ADDRESSES = 64, /* Don't configure DHCPv6 resolv.conf entries */ DHCPv6_DISABLE_RESOLVER = 128, /* Don't set hostname if DHCPv4 host-name option sent */ DHCPv4_DISABLE_HOSTNAME_SET=256 You can specify the "capabilities" the DHCP clients are allowed to exercise, with enum type "DHCP_Capability" from libdhcp.h: /* DHCP client "capabilities" */ /* use / do not use persistent lease database files */ DHCP_USE_LEASE_DATABASE = 1, /* use / do not use pid file */ DHCP_USE_PID_FILE = 2, /* * 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. */ /* configure interfaces UP/DOWN as required */ DHCP_CONFIGURE_INTERFACES = 4, /* configure interface addresses as required */ DHCP_CONFIGURE_ADDRESSES = 8, /* configure routes as required */ DHCP_CONFIGURE_ROUTES =16, /* configure resolv.conf as required */ DHCP_CONFIGURE_RESOLVER =32, /* DHCPv6 only: */ /* configure radvd.conf & restart radvd as required */ DHCP_CONFIGURE_RADVD =64, Then, call the main dhcp interface function, 'do_dhcp' (from dhcp_nic.h) with the values you chose: extern DHCP_nic *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 until it gives up. The timeout should not be 0 at the moment, otherwise if for instance DHCPv6 runs first, and no DHCPv6 servers are contactable, the process will continue indefinitely, and DHCPv4 servers will not be tried. It is intended to get each client running in a separate thread in the next release, and then a timeout of 0 can be specified safely. 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: DHCP_nic *nic = do_dhcp ( 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 return a "Network Interface Configuration" ( NIC ) structure - well, actually two NIC structures, one for each client, encapsulated in the "DHCP_nic" structure - as yet, no 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 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 the 'r_r' structure members will be correctly laid out for access by your C program. CAVEATS: As mentioned above, the DHCPv6 and DHCPv4 clients should be able to run in separate threads - they currently cannot, though the build can enable threads with -DDHCP_USE_THREADS - this is for testing & development purposes only. Yes, there are memory leaks in this early beta release - I am currently working to resolve them. The next feature to be added will be a binary-compatible replacement for the old 'pump' library, which is currently in development. 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 dcantrell@redhat.com.