dhcp_nic.c

00001 /* dhcp_nic.c
00002  *
00003  * Network Interface Configurator for BOTH the ISC DHCP IPv4 client library
00004  * and the DHCPv6 IPv6 client.
00005  *
00006  *  Copyright(C) Jason Vas Dias <jvdias@redhat.com> Red Hat Inc. May 2006
00007  *
00008  *  This program is free software; you can redistribute it and/or modify
00009  *  it under the terms of the GNU General Public License as published by
00010  *  the Free Software Foundation at 
00011  *           http://www.fsf.org/licensing/licenses/gpl.txt
00012  *  and included in this software distribution as the "LICENSE" file.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  *  GNU General Public License for more details.
00018  */
00019 #include <sys/types.h>
00020 #include <unistd.h>
00021 #include <sys/wait.h>
00022 #include <sys/mman.h>
00023 #include <dhcp_nic.h>
00024 #include <string.h>
00025 #include <malloc.h>
00026 #include <errno.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 
00030 static
00031 int dhc_log( LIBDHCP_Control *ctl, int priority, char *fmt, ... )
00032 {
00033     if( (ctl==0) || (ctl->eh == 0) )
00034         return 0;
00035     va_list va;
00036     va_start(va,fmt);
00037     int r = ctl->eh(ctl, priority, fmt, va);
00038     va_end(va);
00039     return r;
00040 }
00041 
00042 static
00043 int dhc_log_noctl( LIBDHCP_Error_Handler eh, int log_level, int priority, char *fmt, ... )
00044 {        
00045     LIBDHCP_Control ctl;
00046     if( eh == 0 )
00047         return 0;
00048     ctl.log_level = log_level;
00049     va_list va;
00050     va_start(va,fmt);
00051     int r = eh(&ctl, priority, fmt, va);
00052     va_end(va);
00053     return r;
00054 }
00055 
00056 static int ifup( LIBDHCP_Control *control, NIC_t nic )
00057 {
00058     /* bring interface UP */
00059     uint32_t iff = nic_get_flags(nic);
00060     dhc_log
00061     (   control, LOG_INFO,
00062         "DHCP %s  - bringing link UP - %x %d",
00063         nic_get_name(nic),
00064         iff, iff & (IFF_UP | IFF_RUNNING) 
00065     );
00066     if ( ( iff & (IFF_UP | IFF_RUNNING) ) != (IFF_UP | IFF_RUNNING) ) 
00067     {
00068         nic_set_flags(nic, iff | IFF_UP | IFF_RUNNING );
00069         if ( nic_update(nic) != NIC_OK )
00070         {
00071             dhc_log
00072             (   control, LOG_ERR,
00073                 "DHCP %s - bringing link UP failed.",
00074                 nic_get_name(nic)
00075             );
00076             return 1;
00077         }
00078     }
00079     return 0;
00080 }
00081 
00082 static 
00083 DHCP_nic *dhcp_nic_va
00084 ( 
00085     NLH_t                  nh,
00086     DHCP_Preference        preference,
00087     char                  *ethX,
00088     LIBDHCP_Capability     dhc_cap, 
00089     time_t                 timeout,    
00090     LIBDHCP_Error_Handler  error_handler,
00091     uint8_t                log_level,
00092     va_list                dhclient_va
00093 )
00094 {
00095     DHCP_nic *nic = calloc(1,sizeof(DHCP_nic));
00096     if( nic == 0L )
00097     {
00098         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "dhcp_nic: out of memory");
00099         return 0L;
00100     }
00101 
00102     nic->preference = preference;
00103 
00104     nic->nh = nh;
00105     
00106     NIC_t eth = nic_by_name(nh, ethX);
00107 
00108     if( eth == 0L )
00109     {
00110         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "dhcp_nic: net_get_by_name(%s) failed", ethX);
00111         free(nic);
00112         return 0L;
00113     }
00114 
00115     LIBDHCP_Control *dhc6ctl=0;
00116     DHCPv4_control  *dhc4ctl=0;
00117 
00118     if ( (preference & DHCPv4_DISABLE) == 0 )
00119     {
00120         dhc4ctl =
00121             dhcpv4_control_va
00122             (nh, ethX, dhc_cap, timeout, error_handler, log_level, dhclient_va);
00123         if ( dhc4ctl == 0L )
00124         {
00125             dhc_log_noctl(error_handler, log_level, LOG_FATAL,"dhcp_nic: dhcpv4_control failed");
00126             free(nic);
00127             return 0;
00128         }
00129         nic->dhc4ctl = dhc4ctl;
00130     }
00131 
00132     if ( (preference & DHCPv6_DISABLE) == 0 )
00133     {
00134         dhc6ctl = 
00135             libdhcp_control_new       
00136             (   dhcp6_nic_callback,
00137                 0,  /* NO "capabilities"! */
00138                 timeout, /* timeout */
00139                 0,  /* let do_dhcpv6 worry about arg */
00140                 error_handler, log_level
00141             );
00142 
00143         if ( dhc6ctl == 0L )
00144         {
00145             dhc_log_noctl(error_handler, log_level, LOG_FATAL,"dhcp_nic: libdhcp_control_new failed");
00146             free(nic);
00147             if (dhc4ctl)
00148                 dhcpv4_control_free(dhc4ctl);
00149             return 0;
00150         }
00151 
00152         nic->dhc6ctl = dhc6ctl;
00153     }
00154 
00155     if ( ( dhc6ctl == 0L ) && (dhc4ctl == 0) )
00156     {
00157         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "dhcp_nic: both DISABLE_DHCPv6 and DISABLE_DHCv4 set.");
00158         free(nic);
00159         return 0;
00160     }
00161 
00162     if( error_handler )
00163     {
00164         nic_set_va_logger(nh, (NIC_VA_Error_Handler_t)error_handler, dhc6ctl);
00165         nic_set_loglevel( nh, log_level );
00166     }
00167 
00168     if( ifup(dhc6ctl, eth) != 0 )
00169     {
00170         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "dhcp_nic: ifup(%s) failed", ethX);
00171         if (dhc4ctl)
00172             dhcpv4_control_free(dhc4ctl);
00173         if (dhc6ctl)
00174             libdhcp_control_free(dhc6ctl);
00175         free(nic);
00176         return 0L;
00177     }
00178 
00179     DHCPv4_nic *nic4=0;
00180     DHCPv6_nic *nic6=0;
00181 
00182     if( (preference & DHCPv6_DISABLE) == 0 )
00183     {
00184         /* 
00185          * Ensure the ipv6 kernel module is loaded -
00186          * it gets auto-loaded by first process to do this:
00187          */
00188         int ipv6_sock = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
00189         close(ipv6_sock);
00190     }
00191 
00192     if( timeout > 0 )
00193     {
00194         if ( preference & IPv6_PREFERENCE )
00195         {
00196             if ( (preference & DHCPv6_DISABLE) == 0 )
00197                 nic6 = do_dhcpv6(nic->dhc6ctl, nh, ethX);
00198 
00199             if ( (preference & DHCPv4_DISABLE) == 0 )
00200                 nic4 = do_dhcpv4(nic->dhc4ctl);
00201 
00202         }else
00203         {
00204             if ( (preference & DHCPv4_DISABLE) == 0 )
00205                 nic4 = do_dhcpv4(nic->dhc4ctl);
00206 
00207             if ( (preference & DHCPv6_DISABLE) == 0 )
00208                 nic6 = do_dhcpv6(nic->dhc6ctl, nh, ethX);
00209         }
00210     }else
00211     {
00212         /* User has selected timeout==0, so each client will continue
00213          * trying to obtain a lease indefinitely. This can only work
00214          * in separate processes / threads - threads don't work because
00215          * of the select() used by both clients, so we use processses.
00216          */
00217 
00218         pid_t 
00219             dhc4_pid=-1,
00220             dhc6_pid=-1;
00221         
00222         uint8_t *sma = 
00223             mmap( 0, 16 * 8192, /* 128k - there may be LOTS of DHCPv4 options */ 
00224                   PROT_READ | PROT_WRITE, 
00225                   MAP_SHARED | MAP_ANONYMOUS,
00226                   -1, 0
00227                 );
00228                   
00229         if( sma == 0 )
00230         {
00231             dhc_log_noctl(error_handler, log_level, LOG_FATAL,
00232                           "dhcp_nic: timeout==0 and cannot obtain shared memory area - %d %s.",
00233                           errno, strerror(errno)
00234                          );
00235             if (dhc4ctl)
00236                 dhcpv4_control_free(dhc4ctl);
00237             if (dhc6ctl)
00238                 libdhcp_control_free(dhc6ctl);
00239             free(nic);
00240             return 0L; 
00241         }
00242         
00243         uint8_t 
00244             *dhc4_buf = sma,
00245             *dhc6_buf = sma + (14 * 8192);
00246 
00247 
00248         if( (preference & DHCPv4_DISABLE) == 0) {
00249             if ((dhc4_pid = fork()) == 0 ) {
00250                 nic4 = do_dhcpv4(nic->dhc4ctl);
00251 
00252                 if (nic4 && nic4->lease != 0)
00253                     dhcpv4_pack_lease(nic4->lease, dhc4_buf, (14 * 8192)); 
00254 
00255                 if (dhc4ctl)
00256                     dhcpv4_control_free(dhc4ctl);
00257                 if (dhc6ctl)
00258                     libdhcp_control_free(dhc6ctl);
00259                 free(nic);
00260                 exit(0);
00261             }
00262             if ( dhc4_pid == -1 ) {
00263                 dhc_log_noctl(error_handler, log_level, LOG_FATAL,
00264                           "dhcp_nic: timeout==0 and fork() failed - %d %s.",
00265                           errno, strerror(errno)
00266                          );
00267 
00268                 if (dhc4ctl)
00269                     dhcpv4_control_free(dhc4ctl);
00270                 if (dhc6ctl)
00271                     libdhcp_control_free(dhc6ctl);
00272                 free(nic);
00273                 return 0L; 
00274             }
00275         }
00276 
00277         if( (preference & DHCPv6_DISABLE) == 0) {
00278             if( (dhc6_pid = fork()) == 0 ) {
00279                 nic6 = do_dhcpv6(nic->dhc6ctl, nh, ethX);
00280                 if (nic6 && nic6->lease)
00281                     dhcpv6_pack_lease(nic6->lease, dhc6_buf, (2 * 8192));
00282 
00283                 if (dhc4ctl)
00284                     dhcpv4_control_free(dhc4ctl);
00285                 if (dhc6ctl)
00286                     libdhcp_control_free(dhc6ctl);
00287                 free(nic);
00288                 exit(0);
00289             }
00290 
00291             if( dhc6_pid == -1 ) {
00292                 dhc_log_noctl(error_handler, log_level, LOG_FATAL,
00293                           "dhcp_nic: timeout==0 and fork() failed - %d %s.",
00294                           errno, strerror(errno)
00295                          );
00296                 if (dhc4ctl)
00297                     dhcpv4_control_free(dhc4ctl);
00298                 if (dhc6ctl)
00299                     libdhcp_control_free(dhc6ctl);
00300                 free(nic);
00301                 return 0L; 
00302             }
00303         }
00304 
00305         pid_t wait_pid = 0;
00306         int status, dhc4_status=0, dhc6_status=0;
00307         
00308     dhc_client_wait:
00309         dhc_log_noctl(error_handler, log_level, LOG_DEBUG, "DHCP: wait - %d %d",
00310                       dhc4_status, dhc6_status
00311                      );
00312         if ((dhc4_status == 0 || dhc6_status == 0) ||
00313                 (dhc6_pid == -1 && dhc4_pid == -1))
00314             goto dhc_clients_finished;
00315 
00316         wait_pid = waitpid(-1, &status, 0);
00317 
00318         if( wait_pid == -1 )
00319             goto dhc_client_wait;
00320 
00321         if( wait_pid == dhc4_pid )
00322         {
00323             DHCPv4_lease *lease4 = dhcpv4_unpack_lease( dhc4_buf );
00324 
00325             dhc4_status = dhc4_pid;
00326 
00327             dhc_log_noctl(error_handler, log_level, LOG_DEBUG,
00328                           "DHCPv4 process finished - %p", lease4);
00329 
00330             if ( lease4 )
00331                 nic4 = dhcp4_set_lease( dhc4ctl, lease4 );
00332 
00333             if( dhc6_status == 0 )
00334             {
00335                 if( lease4 && (preference & DHCP_ACCEPT_FIRST_LEASE) )
00336                 {                   
00337                     kill(dhc6_pid, SIGKILL);
00338                     waitpid(dhc6_pid,0,WNOHANG);
00339                 }else
00340                     goto dhc_client_wait;
00341             }       
00342         }else
00343         if( wait_pid == dhc6_pid )
00344         {
00345             DHCPv6_lease *lease6 = dhcpv6_unpack_lease( dhc6_buf );
00346 
00347             dhc6_status = dhc6_pid;
00348 
00349             dhc_log_noctl(error_handler, log_level, LOG_DEBUG,
00350                           "DHCPv6 process finished - %p", lease6);
00351 
00352             if( lease6 )
00353                 nic6 = dhcp6_nic_from_lease(dhc6ctl, nh, lease6, eth);
00354 
00355             if( dhc4_status == 0 )
00356             {
00357                 if( lease6 && (preference & DHCP_ACCEPT_FIRST_LEASE) )
00358                 {
00359                     kill(dhc4_pid, SIGKILL);
00360                     waitpid(dhc4_pid,0,WNOHANG);
00361                 }else
00362                     goto dhc_client_wait;
00363             }
00364         }else
00365             goto dhc_client_wait;
00366 
00367     dhc_clients_finished:
00368         munmap( sma, 16 * 8192 );
00369 
00370     }
00371 
00372 
00373     if (!dhc4ctl) {
00374         nic4 = NULL;
00375         nic->dhc4ctl = NULL;
00376     }
00377     if (nic4 && !dhcp4_process_lease(dhc4ctl)) {
00378         dhc_log_noctl(error_handler, log_level, LOG_ERR,
00379                       "DHCP: empty DHCPv4 lease."
00380                       );
00381         dhcpv4_control_free(dhc4ctl);
00382         dhc4ctl = nic->dhc4ctl = NULL;
00383         nic4 = NULL;
00384     }
00385     if (nic6 && !dhcp6_process_lease(dhc6ctl, nic6)) {
00386         dhc_log_noctl(error_handler, log_level, LOG_ERR,
00387                       "DHCP: empty DHCPv6 lease."
00388                       );
00389         dhcpv6_nic_free(nic6);
00390         nic6 = NULL;
00391         free(dhc6ctl);
00392         nic->dhc6ctl = dhc6ctl = NULL;
00393     }
00394 
00395     if (!nic6 && !nic4) {
00396         if (dhc4ctl)
00397             dhcpv4_control_free(dhc4ctl);
00398         if (dhc6ctl)
00399             libdhcp_control_free(dhc6ctl);
00400         free(nic);
00401         return NULL;
00402     }
00403 
00404     nic->dhcpv4 = (DHCP_config*)nic4;
00405     nic->dhcpv6 = (DHCP_config*)nic6;
00406     return nic;
00407 }
00408 
00409 DHCP_nic *dhcp_nic
00410 ( 
00411     NLH_t nh,
00412     DHCP_Preference preference,
00413     char                  *ethX,
00414     LIBDHCP_Capability     dhc_cap, 
00415     time_t                 timeout,    
00416     LIBDHCP_Error_Handler  error_handler,
00417     uint8_t                log_level,
00418     ...
00419 )
00420 {    
00421     va_list dhclient_va;
00422     va_start(dhclient_va, log_level);
00423     DHCP_nic 
00424         *nic = 
00425         dhcp_nic_va
00426         (   nh,
00427             preference,
00428             ethX,
00429             dhc_cap,
00430             timeout,
00431             error_handler,
00432             log_level,
00433             dhclient_va
00434         );
00435     va_end(dhclient_va);
00436     return nic;
00437 }
00438 
00439 void dhcp_nic_free( DHCP_nic *nic  )
00440 {
00441     if( nic->dhc4ctl )
00442     {
00443         dhcpv4_control_free (  nic->dhc4ctl );
00444         nic->dhc4ctl = 0L;
00445     }
00446     if( nic->dhc6ctl )
00447     {
00448         free(nic->dhc6ctl);
00449         nic->dhc6ctl = 0L;
00450     }
00451     if( nic->dhcpv6 )
00452     {
00453         dhcpv6_nic_free( (DHCPv6_nic*) nic->dhcpv6 );
00454         nic->dhcpv6 = 0L;
00455     }
00456     if (nic->nh)
00457             nic_close(&nic->nh);
00458     free(nic);
00459 }
00460 
00461 NIC_Res_t do_dhcp
00462 ( 
00463     DHCP_Preference preference,
00464     char                  *eth_if_name,
00465     LIBDHCP_Capability     dhc_cap, 
00466     time_t                 timeout,    
00467     LIBDHCP_Error_Handler  error_handler,
00468     uint8_t                log_level,
00469     ... /* extra DHCPv4 dhclient arguments;
00470          * last arg MUST be 0 .
00471          */
00472 )
00473 {
00474     va_list dhclient_va;
00475 
00476     va_start(dhclient_va, log_level);
00477 
00478     NLH_t nh = nic_open(0);
00479 
00480     if ( nh == 0L )
00481     {
00482         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "do_dhcp: nic_open() failed");
00483         nic_close(&nh);
00484         return 0L;
00485     }
00486 
00487     DHCP_nic 
00488         *nic = 
00489         dhcp_nic_va
00490         (   nh,
00491             preference,
00492             eth_if_name,
00493             dhc_cap,
00494             timeout,
00495             error_handler,
00496             log_level,
00497             dhclient_va
00498         );
00499     va_end(dhclient_va);    
00500 
00501     if ( nic == 0L )
00502     {
00503         dhc_log_noctl(error_handler, log_level, LOG_FATAL, "do_dhcp: no leases obtained.");
00504         nic_close(&nh);
00505         return NIC_FAIL;
00506     }
00507         
00508     NIC_Res_t r = dhcp_nic_configure ( nic );
00509 
00510     dhcp_nic_free(nic);
00511     nic_close(&nh);
00512     
00513     return r;
00514 }
00515 
00516 NIC_Res_t
00517 dhcp_nic_configure( DHCP_nic *nic )
00518 {
00519     if (nic == 0)
00520         return NIC_FAIL;
00521 
00522     LIBDHCP_Control *ctl = nic->dhc6ctl;
00523     NIC_Res_t res = NIC_FAIL;    
00524     char order[3] = { 0, 0, 0 };
00525     char res_order[3] = { 0, 0, 0 };
00526     IPaddr_list_t *dns;
00527     char *search_list = 0, sl_len = 0;
00528     int i;
00529     IPaddr_list_node_t *n=0, *nn=0;
00530 
00531     if (nic->dhcpv6) {
00532         if ((!(nic->preference & DHCPv6_DISABLE_RESOLVER)) &&
00533                 !STAILQ_EMPTY(&nic->dhcpv6->dns_list))
00534             res_order[0] = 6;
00535         order[0] = 6;
00536     }
00537     
00538     if (nic->dhcpv4) {
00539         if ((!(nic->preference & DHCPv6_DISABLE_RESOLVER)) &&
00540                 !STAILQ_EMPTY(&nic->dhcpv4->dns_list))
00541             res_order[1] = 4;
00542         order[1] = 4;
00543     }
00544 
00545     sl_len = 2;
00546     search_list = calloc(sl_len, sizeof (char));
00547 
00548     if (!(nic->preference & IPv6_PREFERENCE)) {
00549         char a = order[0];
00550         order[0] = order[1];
00551         order[1] = a;
00552 
00553         a = res_order[0];
00554         res_order[0] = res_order[1];
00555         res_order[1] = res_order[0];
00556     }
00557 
00558     for (i = 0; i < 2; i++) {
00559         if (!order[i])
00560             order[i] = order[i+1];
00561         if (!res_order[i])
00562             res_order[i] = res_order[i+1];
00563     }
00564 
00565     dns = calloc(1, sizeof(IPaddr_list_t));
00566     STAILQ_INIT(dns);
00567 
00568     for (i = 0; res_order[i]; i++) {
00569         switch (res_order[i]) {
00570             case 6:
00571                 if (!nic->dhcpv6) 
00572                     continue;
00573                 STAILQ_FOREACH(n, &(nic->dhcpv6->dns_list), link) {
00574                     nn = calloc(1, sizeof(IPaddr_list_node_t));
00575                     nn->addr = n->addr;
00576                     STAILQ_INSERT_TAIL(dns, nn, link);
00577                 }
00578 
00579                 if (nic->dhcpv6->search_list) {
00580                     sl_len += strlen(nic->dhcpv6->search_list);
00581                     search_list = realloc(search_list, sl_len);
00582                     if (search_list[0])
00583                         strcat(search_list, " ");
00584                     strcat(search_list, nic->dhcpv6->search_list);
00585                 }
00586                 break;
00587             case 4:
00588                 if (!nic->dhcpv4)
00589                     continue;
00590                 STAILQ_FOREACH(n, &(nic->dhcpv4->dns_list), link) {
00591                     nn = calloc(1, sizeof(IPaddr_list_node_t));
00592                     nn->addr = n->addr;
00593                     STAILQ_INSERT_TAIL(dns, nn, link);
00594                 }
00595 
00596                 if (nic->dhcpv4->search_list) {
00597                     sl_len += strlen(nic->dhcpv4->search_list);
00598                     search_list = realloc(search_list, sl_len);
00599                     if (search_list[0])
00600                         strcat(search_list, " ");
00601                     strcat(search_list, nic->dhcpv4->search_list);
00602                 }
00603                 break;
00604         }
00605     }
00606 
00607     for (i = 0; order[i]; i++) {
00608         switch (order[i]) {
00609             case 6:
00610                 if (!nic->dhcpv6) 
00611                     continue;
00612 
00613                 res = nic_configure(nic->nh, nic->dhcpv6->nic,
00614                     (nic->preference & DHCPv6_DISABLE_ADDRESSES) ?
00615                         0 : &nic->dhcpv6->address_list, 
00616                     0,   /* never any dhcpv6 routes ! (use radvd for that.) */
00617                     dns, search_list,
00618                     0    /* dhvpv6 never sets host name */
00619                 );
00620                 if (res == NIC_OK)
00621                     dhc_log(ctl, LOG_INFO, "DHCPv6 interface configuration succeeded.");
00622                 else
00623                     dhc_log(ctl, LOG_ERR, "DHCPv6 interface configuration failed.");
00624                 break;
00625             case 4:
00626                 if (!nic->dhcpv4)
00627                     continue;
00628 
00629                 /* DHCPv4 */
00630                 if (nic->dhc4ctl && dhcpv4_mtu_option(nic->dhc4ctl)) {
00631                     nic_set_mtu(nic->dhcpv4->nic,
00632                         dhcpv4_mtu_option(nic->dhc4ctl));
00633 
00634                     if (nic_update(nic->dhcpv4->nic) != NIC_OK)
00635                         dhc_log(ctl, LOG_ERR, "DHCPv4 MTU set failed."); 
00636                 }
00637                 res=nic_configure(nic->nh, nic->dhcpv4->nic,
00638                         (nic->preference & DHCPv4_DISABLE_ADDRESSES) ?
00639                             0 : &nic->dhcpv4->address_list,
00640                         (nic->preference & DHCPv4_DISABLE_ROUTES) ?
00641                             0 : &nic->dhcpv4->route_list,
00642                         dns, search_list,
00643                         (nic->preference & DHCPv4_DISABLE_HOSTNAME_SET) ?
00644                             0 : nic->dhcpv4->host_name
00645                     );
00646                 if (res == NIC_OK)
00647                     dhc_log(ctl, LOG_INFO, "DHCPv4 interface configuration succeeded.");
00648                 else
00649                     dhc_log(ctl, LOG_ERR, "DHCPv4 interface configuration failed.");
00650                 break;
00651         }
00652     }
00653 
00654     if (!STAILQ_EMPTY(dns)) {
00655         nn = STAILQ_FIRST( dns );
00656         while ((n = nn)) {
00657             nn = STAILQ_NEXT(n, link);
00658             free(n);
00659         }
00660     }
00661     if (dns)
00662         free(dns);
00663     if (search_list)
00664         free(search_list);
00665     return res;
00666 }
00667 
00668 /*
00669  * vim:ts=8:sw=4:sts=4:et
00670  */

Generated on Thu Aug 10 22:16:40 2006 for libdhcp by  doxygen 1.4.7