[libvirt] [libvirt PATCHv7 1/1] add DHCP snooping

Michal Privoznik mprivozn at redhat.com
Tue Mar 27 12:52:19 UTC 2012


Round two

On 26.03.2012 22:25, David L Stevens wrote:
> This patch adds DHCP snooping support to libvirt. The learning method for
> IP addresses is specified by setting the "ip_learning" variable to one of
> "any" [default] (existing IP learning code), "none" (static only addresses)
> or "dhcp" (DHCP snooping).
> 
> Active leases are saved in a lease file and reloaded on restart or HUP.
> 
> Changes since v6:
> - replace pthread_cancel() with synchronous cancelation method
> 
> Signed-off-by: David L Stevens <dlstevens at us.ibm.com>
> ---
>  docs/formatnwfilter.html.in            |   17 +
>  src/Makefile.am                        |    2 +
>  src/nwfilter/nwfilter_dhcpsnoop.c      | 1139 ++++++++++++++++++++++++++++++++
>  src/nwfilter/nwfilter_dhcpsnoop.h      |   38 ++
>  src/nwfilter/nwfilter_driver.c         |    6 +
>  src/nwfilter/nwfilter_gentech_driver.c |   59 ++-
>  6 files changed, 1248 insertions(+), 13 deletions(-)
>  create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c
>  create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h
> 
> diff --git a/docs/formatnwfilter.html.in b/docs/formatnwfilter.html.in
> index 9cb7644..ad10765 100644
> --- a/docs/formatnwfilter.html.in
> +++ b/docs/formatnwfilter.html.in
> @@ -2189,6 +2189,23 @@
>         <br/><br/>
>         In case a VM is resumed after suspension or migrated, IP address
>         detection will be restarted.
> +       <br/><br/>
> +       Variable <i>ip_learning</i> may be used to specify
> +       the IP address learning method. Valid values are <i>any</i>, <i>dhcp</i>,
> +       or <i>none</i>. The default value is <i>any</i>, meaning that libvirt
> +       may use any packet to determine the address in use by a VM. A value of
> +       <i>dhcp</i> specifies that libvirt should only honor DHCP server-assigned
> +       addresses with valid leases. If <i>ip_learning</i> is set to <i>none</i>,
> +       libvirt does not do address learning and referencing <i>IP</i> without
> +       assigning it an explicit value is an error.
> +       <br/><br/>
> +       Use of <i>ip_learning=dhcp</i> (DHCP snooping) provides additional
> +       anti-spoofing security, especially when combined with a filter allowing
> +       only trusted DHCP servers to assign addresses. If the DHCP lease expires,
> +       the VM will no longer be able to use the IP address until it acquires a
> +       new, valid lease from a DHCP server. If the VM is migrated, it must get
> +       a new valid DHCP lease to use an IP address (e.g., by
> +       bringing the VM interface down and up again).
>       </p>
>  
>      <h3><a name="nwflimitsmigr">VM Migration</a></h3>
> diff --git a/src/Makefile.am b/src/Makefile.am
> index a2aae9d..4382caf 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -509,6 +509,8 @@ NWFILTER_DRIVER_SOURCES =					\
>  		nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c	\
>  		nwfilter/nwfilter_gentech_driver.c			\
>  		nwfilter/nwfilter_gentech_driver.h			\
> +		nwfilter/nwfilter_dhcpsnoop.c				\
> +		nwfilter/nwfilter_dhcpsnoop.h				\
>  		nwfilter/nwfilter_ebiptables_driver.c			\
>  		nwfilter/nwfilter_ebiptables_driver.h			\
>  		nwfilter/nwfilter_learnipaddr.c				\
> diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c
> new file mode 100644
> index 0000000..8e5dcc5
> --- /dev/null
> +++ b/src/nwfilter/nwfilter_dhcpsnoop.c
> @@ -0,0 +1,1139 @@
> +/*
> + * nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM
> + *                         on an interface
> + *
> + * Copyright (C) 2011 IBM Corp.
> + * Copyright (C) 2011 David L Stevens
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
> + *
> + * Author: David L Stevens <dlstevens at us.ibm.com>
> + * Based in part on work by Stefan Berger <stefanb at us.ibm.com>
> + */
> +
> +#include <config.h>
> +
> +#ifdef HAVE_LIBPCAP
> +#include <pcap.h>
> +#endif
> +
> +#include <fcntl.h>
> +#include <sys/ioctl.h>
> +#include <signal.h>

Do we need signal.h? It seems unused to me.

> +
> +#include <arpa/inet.h>
> +#include <net/ethernet.h>
> +#include <netinet/ip.h>
> +#include <netinet/udp.h>
> +#include <net/if.h>
> +#include <net/if_arp.h>
> +#include <intprops.h>

Why do we include this but never use it?


> +
> +#include "internal.h"
> +
> +#include "buf.h"
> +#include "memory.h"
> +#include "logging.h"
> +#include "datatypes.h"
> +#include "virterror_internal.h"
> +#include "threads.h"
> +#include "conf/nwfilter_params.h"
> +#include "conf/domain_conf.h"
> +#include "nwfilter_gentech_driver.h"
> +#include "nwfilter_ebiptables_driver.h"
> +#include "nwfilter_dhcpsnoop.h"
> +#include "virnetdev.h"
> +#include "virfile.h"
> +#include "configmake.h"
> +
> +#define VIR_FROM_THIS VIR_FROM_NWFILTER
> +
> +#ifdef HAVE_LIBPCAP
> + 
> +#define LEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.leases"
> +#define TMPLEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.ltmp"
> +static int lease_fd = -1;
> +static int nleases = 0; /* number of active leases */
> +static int wleases = 0; /* number of written leases */
> +
> +static virHashTablePtr SnoopReqs;
> +static virHashTablePtr IfnameToKey;
> +static pthread_mutex_t SnoopLock = PTHREAD_MUTEX_INITIALIZER;
> +
> +/* snooper thread management */
> +
> +static virHashTablePtr Active;
> +static pthread_mutex_t ActiveLock = PTHREAD_MUTEX_INITIALIZER;
> +
> +#define active_lock()    { pthread_mutex_lock(&ActiveLock); }
> +#define active_unlock()  { pthread_mutex_unlock(&ActiveLock); }
> +
> +static char *
> +SnoopActivate(pthread_t thread)
> +{
> +    int len;
> +    char *key;
> +
> +    len = sizeof(thread)*2 + 3; /* "0x"+'\0' */
> +    if (VIR_ALLOC_N(key, len)) {
> +        virReportOOMError();
> +        return NULL;
> +    }
> +    (void) snprintf(key, len, "0x%0*lX", sizeof(thread)*2,
> +                    (unsigned long int)thread);
> +    key[len-1] = '\0';
> +
> +    active_lock();
> +    if (virHashAddEntry(Active, key, strdup("1")))
> +        VIR_FREE(key);
> +    active_unlock();
> +    return key;
> +}
> +
> +static void
> +SnoopCancel(char **ThreadKey)
> +{
> +    if (*ThreadKey == NULL)
> +        return;
> +
> +    active_lock();
> +    (void) virHashRemoveEntry(Active, *ThreadKey);
> +    *ThreadKey = NULL;
> +    active_unlock();
> +}
> +
> +static bool
> +SnoopIsActive(char *ThreadKey)
> +{
> +    void *entry;
> +
> +    if (ThreadKey == NULL)
> +        return 0;
> +    active_lock();
> +    entry = virHashLookup(Active, ThreadKey);
> +    active_unlock();
> +    return entry != NULL;
> +}
> +
> +#define snoop_lock()    { pthread_mutex_lock(&SnoopLock); }
> +#define snoop_unlock()  { pthread_mutex_unlock(&SnoopLock); }
> +
> +#define VIR_IFKEY_LEN   ((VIR_UUID_STRING_BUFLEN) + (VIR_MAC_STRING_BUFLEN))
> +
> +struct virNWFilterSnoopReq {
> +    virNWFilterTechDriverPtr  techdriver;
> +    const char               *ifname;
> +    int                       ifindex;
> +    const char               *linkdev;
> +    enum virDomainNetType     nettype;
> +    char                      ifkey[VIR_IFKEY_LEN];
> +    unsigned char             macaddr[VIR_MAC_BUFLEN];
> +    const char               *filtername;
> +    virNWFilterHashTablePtr   vars;
> +    virNWFilterDriverStatePtr driver;
> +    /* start and end of lease list, ordered by lease time */
> +    struct iplease           *start;
> +    struct iplease           *end;
> +    char                     *threadkey;
> +};
> +
> +#define POLL_INTERVAL    10*1000 /* 10 secs */
> +#define MAXERRS               25 /* retries on failing device */
> +
> +struct iplease {
> +    uint32_t                    ipl_ipaddr;
> +    uint32_t                    ipl_server;
> +    struct virNWFilterSnoopReq *ipl_req;
> +    unsigned int                ipl_timeout;
> +    /* timer list */
> +    struct iplease             *ipl_prev;
> +    struct iplease             *ipl_next;
> +};
> +
> +static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr);
> +static void ipl_update(struct iplease *pl, uint32_t timeout);
> + 
> +static struct virNWFilterSnoopReq *newreq(const char *ifkey);
> +
> +static void lease_open(void);
> +static void lease_close(void);
> +static void lease_load(void);
> +static void lease_save(struct iplease *ipl);
> +static void lease_restore(struct virNWFilterSnoopReq *req);
> +static void lease_refresh(void);
> +
> +/*
> + * ipl_ladd - add an IP lease to a list
> + */
> +static void
> +ipl_ladd(struct iplease *plnew, struct iplease **start, struct iplease **end)
> +{
> +    struct iplease             *pl;
> +
> +    plnew->ipl_next = plnew->ipl_prev = 0;
> +    if (!*start) {
> +        *start = *end = plnew;
> +        return;
> +    }
> +    for (pl = *end; pl && plnew->ipl_timeout < pl->ipl_timeout;
> +         pl = pl->ipl_prev)
> +        /* empty */ ;
> +    if (!pl) {
> +        plnew->ipl_next = *start;
> +        *start = plnew;
> +    } else {
> +        plnew->ipl_next = pl->ipl_next;
> +        pl->ipl_next = plnew;
> +    }
> +    plnew->ipl_prev = pl;
> +    if (plnew->ipl_next)
> +        plnew->ipl_next->ipl_prev = plnew;
> +    else
> +        *end = plnew;
> +}
> +
> +/*
> + * ipl_tadd - add an IP lease to the timer list
> + */
> +static void
> +ipl_tadd(struct iplease *plnew)
> +{
> +    struct virNWFilterSnoopReq *req = plnew->ipl_req;
> +
> +    ipl_ladd(plnew, &req->start, &req->end);
> +}
> + 
> +/*
> + * ipl_install - install rule for a lease
> + */
> +static int
> +ipl_install(struct iplease *ipl)
> +{
> +    char                     ipbuf[20];    /* dotted decimal IP addr string */
> +    int                      rc;
> +    virNWFilterVarValuePtr   ipVar;
> +
> +    if (!inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("ipl_install inet_ntop " "failed (0x%08X)"),

join these two strings

> +                               ipl->ipl_ipaddr);
> +        return -1;
> +    }
> +    ipVar = virNWFilterVarValueCreateSimpleCopyValue(ipbuf);
> +    if (!ipVar) {
> +        virReportOOMError();
> +        return -1;
> +    }
> +    if (virNWFilterHashTablePut(ipl->ipl_req->vars, "IP", ipVar, 1)) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Could not add variable \"IP\" to hashmap"));
> +        virNWFilterVarValueFree(ipVar);
> +        return -1;
> +    }
> +    rc = virNWFilterInstantiateFilterLate(NULL,
> +                                          ipl->ipl_req->ifname,
> +                                          ipl->ipl_req->ifindex,
> +                                          ipl->ipl_req->linkdev,
> +                                          ipl->ipl_req->nettype,
> +                                          ipl->ipl_req->macaddr,
> +                                          ipl->ipl_req->filtername,
> +                                          ipl->ipl_req->vars,
> +                                          ipl->ipl_req->driver);
> +    if (rc)
> +        return -1;
> +    return 0;
> +}
> +
> +/*
> + * ipl_add - create or update an IP lease
> + */
> +static void
> +ipl_add(struct iplease *plnew, bool update_leasefile)
> +{
> +    struct iplease *pl;
> +    struct virNWFilterSnoopReq *req = plnew->ipl_req;
> +
> +    pl = ipl_getbyip(req->start, plnew->ipl_ipaddr);
> +    if (pl) {
> +        ipl_update(pl, plnew->ipl_timeout);
> +        if (update_leasefile)
> +            lease_save(pl);
> +        return;
> +    }
> +    /* support for multiple addresses requires the ability to add filters
> +     * to existing chains, or to instantiate address lists via
> +     * virNWFilterInstantiateFilterLate(). Until one of those capabilities
> +     * is added, don't allow a new address when one is already assigned to
> +     * this interface.
> +     */
> +    if (req->start)
> +         return;    /* silently ignore multiple addresses */
> +
> +    if (VIR_ALLOC(pl) < 0) {
> +        virReportOOMError();
> +        return;
> +    }
> +    *pl = *plnew;
> +
> +    if (req->threadkey && ipl_install(pl) < 0) {
> +        VIR_FREE(pl);
> +        return;
> +    }
> +    ipl_tadd(pl);
> +    nleases++;
> +    if (update_leasefile)
> +        lease_save(pl);
> +}
> +
> +/*
> + * ipl_tdel - remove an IP lease from a list
> + */
> +static void
> +ipl_ldel(struct iplease *ipl, struct iplease **start, struct iplease **end)
> +{
> +    if (ipl->ipl_prev)
> +        ipl->ipl_prev->ipl_next = ipl->ipl_next;
> +    else
> +        *start = ipl->ipl_next;
> +    if (ipl->ipl_next)
> +        ipl->ipl_next->ipl_prev = ipl->ipl_prev;
> +    else
> +        *end = ipl->ipl_prev;
> +    ipl->ipl_next = ipl->ipl_prev = 0;
> +}
> +
> +/*
> + * ipl_tdel - remove an IP lease from the timer list
> + */
> +static void
> +ipl_tdel(struct iplease *ipl)
> +{
> +    struct virNWFilterSnoopReq *req = ipl->ipl_req;
> +
> +    ipl_ldel(ipl, &req->start, &req->end);
> +    ipl->ipl_timeout = 0;
> +}
> +
> +/*
> + * ipl_del - delete an IP lease
> + */
> +static void
> +ipl_del(struct virNWFilterSnoopReq *req, uint32_t ipaddr, bool update_leasefile)
> +{
> +    struct iplease *ipl;
> +
> +    ipl = ipl_getbyip(req->start, ipaddr);
> +    if (ipl == NULL)
> +        return;
> +
> +    ipl_tdel(ipl);
> +
> +    if (update_leasefile) {
> +        lease_save(ipl);
> +
> +        /*
> +         * for multiple address support, this needs to remove those rules
> +         * referencing "IP" with ipl's ip value.
> +         */
> +        if (req->techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr,
> +                                                NULL, false))
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "ipl_ldel failed");

use _("...");

> +    }
> +    VIR_FREE(ipl);
> +    nleases--;
> +}
> +
> +/*
> + * ipl_update - update the timeout on an IP lease
> + */
> +static void
> +ipl_update(struct iplease *ipl, uint32_t timeout)
> +{
> +    if (timeout < ipl->ipl_timeout)
> +        return;  /* no take-backs */
> +    ipl_tdel(ipl);
> +    ipl->ipl_timeout = timeout;
> +    ipl_tadd(ipl);
> +    return;
> +}
> +
> +/*
> + * ipl_getbyip - lookup IP lease by IP address
> + */
> +static struct iplease *
> +ipl_getbyip(struct iplease *start, uint32_t ipaddr)
> +{
> +    struct iplease *pl;
> +
> +    for (pl = start; pl && pl->ipl_ipaddr != ipaddr; pl = pl->ipl_next)
> +        /* empty */ ;
> +    return pl;
> +}
> +
> +/*
> + * ipl_trun - run the IP lease timeout list
> + */
> +static unsigned int
> +ipl_trun(struct virNWFilterSnoopReq *req)
> +{
> +    uint32_t now;
> +
> +    now = time(0);
> +    while (req->start && req->start->ipl_timeout <= now)
> +        ipl_del(req, req->start->ipl_ipaddr, 1);
> +    return 0;
> +}
> +
> +typedef unsigned char Eaddr[6];
> +
> +struct eth {
> +    Eaddr eh_dst;
> +    Eaddr eh_src;
> +    unsigned short eh_type;
> +    union {
> +        unsigned char eu_data[0];
> +        struct vlan_hdr {
> +            unsigned short ev_flags;
> +            unsigned short ev_type;
> +            unsigned char  ev_data[0];
> +        } eu_vlh;
> +    } eth_un;
> +} ATTRIBUTE_PACKED;
> +
> +#define eh_data eth_un.eu_data
> +#define ehv_data eth_un.eu_vlh.ev_data
> +#define ehv_type eth_un.eu_vlh.ev_type
> +
> +struct dhcp {
> +    unsigned char  d_op;
> +    unsigned char  d_htype;
> +    unsigned char  d_hlen;
> +    unsigned char  d_hops;
> +    unsigned int   d_xid;
> +    unsigned short d_secs;
> +    unsigned short d_flags;
> +    unsigned int   d_ciaddr;
> +    unsigned int   d_yiaddr;
> +    unsigned int   d_siaddr;
> +    unsigned int   d_giaddr;
> +    unsigned char  d_chaddr[16];
> +    char           d_sname[64];
> +    char           d_file[128];
> +    unsigned char  d_opts[0];
> +};
> +
> +/* DHCP options */
> +
> +#define DHCPO_PAD         0
> +#define DHCPO_LEASE      51     /* lease time in secs */
> +#define DHCPO_MTYPE      53     /* message type */
> +#define DHCPO_END       255     /* end of options */
> +
> +/* DHCP message types */
> +#define DHCPDECLINE     4
> +#define DHCPACK         5
> +#define DHCPRELEASE     7
> +
> +unsigned char dhcp_magic[4] = { 99, 130, 83, 99 };
> +
> +static int
> +dhcp_getopt(struct dhcp *pd, int len, int *pmtype, int *pleasetime)
> +{
> +    int oind, olen;
> +    int oend;
> +
> +    olen = len - sizeof *pd;
> +    oind = 0;
> +
> +    if (olen < 4)               /* bad magic */
> +        return -1;
> +    if (memcmp(dhcp_magic, pd->d_opts, sizeof dhcp_magic) != 0)
> +        return -1;              /* bad magic */
> +    oind += sizeof dhcp_magic;
> +
> +    oend = 0;
> +
> +    *pmtype = *pleasetime = 0;
> +
> +    while (oind < olen) {
> +        switch (pd->d_opts[oind]) {
> +            case DHCPO_LEASE:
> +                if (olen - oind < 6)
> +                    goto malformed;
> +                if (*pleasetime)
> +                    return -1;  /* duplicate lease time */
> +                *pleasetime =
> +                    ntohl(*(unsigned int *) (pd->d_opts + oind + 2));
> +                break;
> +            case DHCPO_MTYPE:
> +                if (olen - oind < 3)
> +                    goto malformed;
> +                if (*pmtype)
> +                    return -1;  /* duplicate message type */
> +                *pmtype = pd->d_opts[oind + 2];
> +                break;
> +            case DHCPO_PAD:
> +                oind++;
> +                continue;
> +
> +            case DHCPO_END:
> +                oend = 1;
> +                break;
> +            default:
> +                if (olen - oind < 2)
> +                    goto malformed;
> +        }
> +        if (oend)
> +            break;
> +        oind += pd->d_opts[oind + 1] + 2;
> +    }
> +    return 0;
> +  malformed:
> +    VIR_WARN("got lost in the options!");
> +    return -1;
> +}
> +
> +static void
> +dhcpdecode(struct virNWFilterSnoopReq *req, struct eth *pep, int len)
> +{
> +    struct iphdr   *pip;
> +    struct udphdr  *pup;
> +    struct dhcp    *pd;
> +    struct iplease  ipl;
> +    int             mtype, leasetime;
> +
> +    /* go through the protocol headers */
> +    switch (ntohs(pep->eh_type)) {
> +    case ETHERTYPE_IP:
> +        pip = (struct iphdr *) pep->eh_data;
> +        len -= offsetof(struct eth, eh_data);
> +        break;
> +    case ETHERTYPE_VLAN:
> +        if (ntohs(pep->ehv_type) != ETHERTYPE_IP)
> +            return;
> +        pip = (struct iphdr *) pep->ehv_data;
> +        len -= offsetof(struct eth, ehv_data);
> +        break;
> +    default:
> +        return;
> +    }
> +    pip = (struct iphdr *) pep->eh_data;
> +    len -= sizeof(*pep);
> +    pup = (struct udphdr *) ((char *) pip + (pip->ihl << 2));
> +    len -= pip->ihl << 2;
> +    pd = (struct dhcp *) ((char *) pup + sizeof(*pup));
> +    len -= sizeof(*pup);
> +    if (len < 0)
> +        return;                 /* dhcpdecode: invalid packet length */
> +    if (dhcp_getopt(pd, len, &mtype, &leasetime) < 0)
> +        return;
> +
> +    memset(&ipl, 0, sizeof(ipl));
> +    ipl.ipl_ipaddr = pd->d_yiaddr;
> +    ipl.ipl_server = pd->d_siaddr;
> +    if (leasetime == ~0)
> +        ipl.ipl_timeout = ~0;
> +    else
> +        ipl.ipl_timeout = time(0) + leasetime;
> +    ipl.ipl_req = req;
> +
> +    switch (mtype) {
> +        case DHCPACK:
> +            ipl_add(&ipl, 1);
> +            break;
> +        case DHCPDECLINE:
> +        case DHCPRELEASE:
> +            ipl_del(req, ipl.ipl_ipaddr, 1);
> +            break;
> +        default:
> +            break;
> +    }
> +}
> +
> +#define PBUFSIZE        576     /* >= IP/TCP/DHCP headers */
> +#define TIMEOUT          30 /* secs */
> +
> +static pcap_t *
> +dhcpopen(const char *intf)
> +{
> +    pcap_t             *handle = NULL;
> +    struct bpf_program  fp;
> +    char                filter[64];
> +    char                pcap_errbuf[PCAP_ERRBUF_SIZE];
> +    time_t              start;
> +
> +    start = time(0);
> +    while (handle == NULL && time(0) - start < TIMEOUT)
> +        handle = pcap_open_live(intf, PBUFSIZE, 0, POLL_INTERVAL, pcap_errbuf);
> +
> +    if (handle == NULL) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_open_live: %s"), pcap_errbuf);
> +        return 0;
> +    }
> +
> +    sprintf(filter, "port 67 or dst port 68");
> +    if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_compile: %s"), pcap_geterr(handle));
> +        return 0;
> +    }
> +    if (pcap_setfilter(handle, &fp) != 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_setfilter: %s"), pcap_geterr(handle));
> +        return 0;
> +    }
> +    pcap_freecode(&fp);
> +    return handle;
> +}
> +
> +static void
> +snoopReqFree(struct virNWFilterSnoopReq *req)
> +{
> +    struct iplease *ipl;
> +
> +    if (!req)
> +        return;
> +
> +    /* free all leases */
> +    for (ipl = req->start; ipl; ipl = req->start)
> +        ipl_del(req, ipl->ipl_ipaddr, 0);
> +
> +    /* free all req data */
> +    VIR_FREE(req->ifname);
> +    VIR_FREE(req->linkdev);
> +    VIR_FREE(req->filtername);
> +    virNWFilterHashTableFree(req->vars);
> +    VIR_FREE(req);
> +}
> +
> +static void *
> +virNWFilterDHCPSnoop(void *req0)
> +{
> +    struct virNWFilterSnoopReq *req = req0;
> +    pcap_t                     *handle;
> +    struct pcap_pkthdr         *hdr;
> +    struct eth                 *packet;
> +    int                         ifindex;
> +    int                         errcount;
> +    char                       *threadkey;
> +
> +    handle = dhcpopen(req->ifname);
> +    if (!handle)
> +        return 0;
> +
> +    req->threadkey = SnoopActivate(pthread_self());
> +    threadkey = strdup(req->threadkey);
> +
> +    ifindex = if_nametoindex(req->ifname);
> +
> +    snoop_lock();
> +    lease_restore(req);
> +    snoop_unlock();
> +
> +    errcount = 0;
> +    while (1) {
> +        int rv;
> +
> +        snoop_lock();
> +        ipl_trun(req);
> +        snoop_unlock();
> +
> +        rv = pcap_next_ex(handle, &hdr, (const u_char **)&packet);
> +
> +        if (!SnoopIsActive(threadkey)) {
> +            VIR_FREE(threadkey);
> +            pcap_close(handle);
> +            return 0;
> +        }
> +        if (rv < 0) {
> +            if (virNetDevValidateConfig(req->ifname, NULL, ifindex) <= 0)
> +                break;
> +            if (++errcount > MAXERRS) {
> +                virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                       _("ifname \"%s\" failing; reopening"),
> +                                       req->ifname);
> +                pcap_close(handle);
> +                handle = dhcpopen(req->ifname);
> +                if (!handle)
> +                    break;
> +            }
> +            continue;
> +        }
> +        errcount = 0;
> +        if (rv) {
> +            snoop_lock();
> +            dhcpdecode(req, packet, hdr->caplen);
> +            snoop_unlock();
> +        }
> +    }
> +    SnoopCancel(&req->threadkey);
> +    (void) virHashRemoveEntry(IfnameToKey, req->ifname);
> +    VIR_FREE(req->ifname);
> +    /* if we still have a valid lease, keep the req for restarts */
> +    if (!req->start || req->start->ipl_timeout < time(0))
> +        (void) virHashRemoveEntry(SnoopReqs, req->ifkey);
> +    VIR_FREE(threadkey);
> +    pcap_close(handle);
> +    return 0;
> +}
> +
> +static void
> +ifkeyFormat(char *ifkey, const unsigned char *vmuuid,
> +            unsigned const char *macaddr)
> +{
> +    virUUIDFormat(vmuuid, ifkey);
> +    ifkey[VIR_UUID_STRING_BUFLEN-1] = '-';
> +    virMacAddrFormat(macaddr, ifkey + VIR_UUID_STRING_BUFLEN);
> +}
> +
> +int
> +virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver,
> +                        const char *ifname,
> +                        const char *linkdev,
> +                        enum virDomainNetType nettype,
> +                        const unsigned char *vmuuid,
> +                        const unsigned char *macaddr,
> +                        const char *filtername,
> +                        virNWFilterHashTablePtr filterparams,
> +                        virNWFilterDriverStatePtr driver)
> +{
> +    struct virNWFilterSnoopReq *req;
> +    bool                        isnewreq;
> +    char                        ifkey[VIR_IFKEY_LEN];
> +    pthread_t                   thread;
> +
> +    ifkeyFormat(ifkey, vmuuid, macaddr);
> +    snoop_lock();
> +    req = virHashLookup(SnoopReqs, ifkey);
> +    isnewreq = req == NULL;
> +    if (!isnewreq) {
> +        if (req->threadkey) {
> +            snoop_unlock();
> +            return 0;
> +        }
> +    } else {
> +        req = newreq(ifkey);
> +        if (!req) {
> +            snoop_unlock();
> +            return 1;
> +        }
> +    }
> +
> +    req->techdriver = techdriver;
> +    req->ifindex = if_nametoindex(ifname);
> +    req->linkdev = linkdev ? strdup(linkdev) : NULL;
> +    req->nettype = nettype;
> +    req->ifname = strdup(ifname);
> +    memcpy(req->macaddr, macaddr, sizeof(req->macaddr));
> +    req->filtername = strdup(filtername);
> +    if (req->filtername == NULL) {
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        virReportOOMError();
> +        return 1;
> +    }
> +
> +    if (techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr, NULL,
> +                                       false)) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "applyDHCPOnlyRules "
> +                               "failed - spoofing not protected!");

use _("...")

> +    }
> +
> +    req->vars = virNWFilterHashTableCreate(0);
> +    if (!req->vars) {
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        virReportOOMError();
> +        return 1;
> +    }
> +    if (virNWFilterHashTablePutAll(filterparams, req->vars)) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq: can't copy variables"
> +                                " on if %s"), ifkey);
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        return 1;
> +    }
> +    req->driver = driver;
> +
> +    if (virHashAddEntry(IfnameToKey, ifname, req->ifkey)) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq ifname map failed"
> +                                 " on interface \"%s\" key \"%s\""), ifname,
> +                               ifkey);
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        return 1;
> +    }
> +    if (isnewreq && virHashAddEntry(SnoopReqs, ifkey, req)) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq req add failed on"
> +                               " interface \"%s\" ifkey \"%s\""), ifname,
> +                               ifkey);
> +        (void) virHashRemoveEntry(IfnameToKey, ifname);
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        return 1;
> +    }
> +    snoop_unlock();
> +    if (pthread_create(&thread, NULL, virNWFilterDHCPSnoop, req) != 0) {
> +        snoop_lock();
> +        (void) virHashRemoveEntry(IfnameToKey, ifname);
> +        (void) virHashRemoveEntry(SnoopReqs, ifkey);
> +        snoop_unlock();
> +        snoopReqFree(req);
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq pthread_create failed"
> +                                " on interface \"%s\""), ifname);
> +        return 1;
> +    }
> +    return 0;
> +}
> +
> +/*
> + * freeReq - hash table free function to kill a request
> + */
> +static void
> +freeReq(void *req0, const void *name ATTRIBUTE_UNUSED)
> +{
> +    struct virNWFilterSnoopReq *req = (struct virNWFilterSnoopReq *) req0;
> +
> +    if (!req)
> +        return;
> +
> +    if (req->threadkey)
> +        SnoopCancel(&req->threadkey);
> +    snoopReqFree(req);
> +}
> +
> +static void
> +lease_close(void)
> +{
> +    VIR_FORCE_CLOSE(lease_fd);
> +}
> +
> +static void
> +lease_open(void)
> +{
> +    lease_close();
> +
> +    lease_fd = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND, 0644);
> +}
> +
> +int
> +virNWFilterDHCPSnoopInit(void)
> +{
> +    if (SnoopReqs)
> +        return 0;
> +
> +    snoop_lock();
> +    IfnameToKey = virHashCreate(0, NULL);
> +    SnoopReqs = virHashCreate(0, freeReq);
> +    if (!SnoopReqs) {
> +        snoop_unlock();
> +        virReportOOMError();
> +        return -1;
> +    }
> +    lease_load();
> +    lease_open();
> +
> +    snoop_unlock();
> +
> +    active_lock();
> +    Active = virHashCreate(0, 0);
> +    if (!Active) {
> +        active_unlock();
> +        virReportOOMError();
> +        return -1;
> +    }
> +    active_unlock();
> +    return 0;
> +}
> +
> +void
> +virNWFilterDHCPSnoopEnd(const char *ifname)
> +{
> +    char *ifkey = NULL;
> + 
> +    snoop_lock();
> +    if (!SnoopReqs) {
> +        snoop_unlock();
> +        return;
> +    }
> +
> +    if (ifname) {
> +        ifkey = (char *)virHashLookup(IfnameToKey, ifname);
> +        (void) virHashRemoveEntry(IfnameToKey, ifname);
> +        if (!ifkey) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("ifname \"%s\" not in key map"), ifname);
> +            snoop_unlock();
> +            return;
> +        }
> +    }
> +
> +    if (ifkey) {
> +        struct virNWFilterSnoopReq *req;
> +
> +        req = virHashLookup(SnoopReqs, ifkey);
> +        if (!req) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("ifkey \"%s\" has no req"), ifkey);
> +            snoop_unlock();
> +            return;
> +        }
> +        if (!req->start || req->start->ipl_timeout < time(0)) {
> +            (void) virHashRemoveEntry(SnoopReqs, req->ifkey);
> +            snoop_unlock();
> +            return;
> +        }
> +        /* keep valid lease req; drop interface association */
> +        SnoopCancel(&req->threadkey);
> +        VIR_FREE(req->ifname);
> +    } else {                      /* free all of them */
> +        lease_close();
> +        virHashFree(IfnameToKey);
> +        virHashFree(SnoopReqs);
> +        IfnameToKey = virHashCreate(0, 0);
> +        if (!IfnameToKey) {
> +            snoop_unlock();
> +            virReportOOMError();
> +            return;
> +        }
> +        SnoopReqs = virHashCreate(0, freeReq);
> +        if (!SnoopReqs) {
> +            virHashFree(IfnameToKey);
> +            snoop_unlock();
> +            virReportOOMError();
> +            return;
> +        }
> +        lease_load();
> +    }
> +    snoop_unlock();
> +}
> +
> +static int
> +lease_write(int lfd, const char *ifkey, struct iplease *ipl)
> +{
> +    char lbuf[256],ipstr[16],dhcpstr[16];
> +
> +    if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipstr, sizeof ipstr) == 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("inet_ntop(0x%08X) failed"), ipl->ipl_ipaddr);
> +        return -1;
> +    }
> +    if (inet_ntop(AF_INET, &ipl->ipl_server, dhcpstr, sizeof dhcpstr) == 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("inet_ntop(0x%08X) failed"), ipl->ipl_server);
> +        return -1;
> +    }
> +    /* time intf ip dhcpserver */
> +    snprintf(lbuf, sizeof(lbuf), "%u %s %s %s\n", ipl->ipl_timeout,
> +             ifkey, ipstr, dhcpstr);
> +    if (write(lfd, lbuf, strlen(lbuf)) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("lease file write failed: %s"),
> +                               strerror(errno));
> +        return -1;
> +    }
> +    (void) fsync(lfd);
> +    return 0;
> +}
> +
> +static void
> +lease_save(struct iplease *ipl)
> +{
> +    struct virNWFilterSnoopReq *req = ipl->ipl_req;
> +
> +    if (lease_fd < 0)
> +       lease_open();
> +    if (lease_write(lease_fd, req->ifkey, ipl) < 0)
> +       return;
> +    /* keep dead leases at < ~95% of file size */
> +    if (++wleases >= nleases*20)
> +        lease_load();   /* load & refresh lease file */
> +}
> +
> +static struct virNWFilterSnoopReq *
> +newreq(const char *ifkey)
> +{
> +    struct virNWFilterSnoopReq *req;
> +
> +    if (VIR_ALLOC(req) < 0) {
> +        virReportOOMError();
> +        return NULL;
> +    }
> +    strncpy(req->ifkey, ifkey, sizeof req->ifkey);
> +
> +    return req;
> +}
> +
> +static void
> +SaveSnoopReqIter(void *payload,
> +                 const void *name ATTRIBUTE_UNUSED,
> +                 void *data)
> +{
> +    struct virNWFilterSnoopReq *req = payload;
> +    int tfd = (int)data;
> +    struct iplease *ipl;
> +
> +    /* clean up orphaned, expired leases */
> +    if (!req->threadkey) {
> +        uint32_t now;
> +
> +        now = time(0);
> +        for (ipl = req->start; ipl; ipl = ipl->ipl_next)
> +            if (ipl->ipl_timeout < now)
> +                ipl_del(req, ipl->ipl_ipaddr , 0);
> +        if (!req->start) {
> +            snoopReqFree(req);
> +            return;
> +        }
> +    }
> +    for (ipl = req->start; ipl; ipl = ipl->ipl_next)
> +        (void) lease_write(tfd, req->ifkey, ipl);
> +}
> +
> +static void
> +lease_refresh(void)
> +{
> +    int tfd;
> +
> +    (void) unlink(TMPLEASEFILE);
> +    /* lease file loaded, delete old one */
> +    tfd = open(TMPLEASEFILE, O_CREAT|O_RDWR|O_TRUNC|O_EXCL, 0644);
> +    if (tfd < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("open(\"%s\"): %s"),
> +                               TMPLEASEFILE, strerror(errno));
> +        return;
> +    }
> +    if (SnoopReqs)
> +        virHashForEach(SnoopReqs, SaveSnoopReqIter, (void *)tfd);
> +    (void) close(tfd);
> +    if (rename(TMPLEASEFILE, LEASEFILE) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("rename(\"%s\", \"%s\"): %s"),
> +                               TMPLEASEFILE, LEASEFILE, strerror(errno));
> +        (void) unlink(TMPLEASEFILE);
> +    }
> +    wleases = 0;
> +    lease_open();
> +}
> +
> +
> +static void
> +lease_load(void)
> +{
> +    char line[256], ifkey[VIR_IFKEY_LEN], ipstr[16], srvstr[16];
> +    struct iplease ipl;
> +    struct virNWFilterSnoopReq *req;
> +    time_t now;
> +    FILE *fp;
> +    int ln = 0;
> +
> +    fp = fopen(LEASEFILE, "r");
> +    time(&now);
> +    while (fp && fgets(line, sizeof(line), fp)) {
> +        if (line[strlen(line)-1] != '\n') {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("lease_load lease file line %d corrupt"),
> +                                   ln);
> +            break;
> +        }
> +        ln++;
> +        /* key len 55 = "VMUUID"+'-'+"MAC" */
> +        if (sscanf(line, "%u %55s %16s %16s", &ipl.ipl_timeout,
> +                   ifkey, ipstr, srvstr) < 4) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("lease_load lease file line %d corrupt"),
> +                                   ln);
> +            break;;
> +        }
> +        if (ipl.ipl_timeout && ipl.ipl_timeout < now)
> +            continue;
> +        req = virHashLookup(SnoopReqs, ifkey);
> +        if (!req) {
> +            req = newreq(ifkey);
> +            if (!req)
> +               break;
> +            if (virHashAddEntry(SnoopReqs, ifkey, req)) {
> +                snoopReqFree(req);
> +                virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                       _("lease_load req add failed on "
> +                                       "interface \"%s\""), ifkey);
> +                continue;
> +            }
> +        }
> +
> +        if (inet_pton(AF_INET, ipstr, &ipl.ipl_ipaddr) <= 0) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                  _("line %d corrupt ipaddr \"%s\""),
> +                                  ln, ipstr);
> +            continue;
> +        }
> +        (void) inet_pton(AF_INET, srvstr, &ipl.ipl_server);
> +        ipl.ipl_req = req;
> +
> +        if (ipl.ipl_timeout)
> +            ipl_add(&ipl, 0);
> +        else
> +            ipl_del(req, ipl.ipl_ipaddr, 0);
> +    }
> +    if (fp != NULL)
> +        (void) fclose(fp);
> +    lease_refresh();
> +}
> +
> +static void
> +lease_restore(struct virNWFilterSnoopReq *req)
> +{
> +    struct iplease *ipl;
> +
> +    for (ipl=req->start; ipl; ipl=ipl->ipl_next)
> +        (void) ipl_install(ipl);
> +}
> +
> +#else /* HAVE_LIBPCAP */
> +int
> +virNWFilterDHCPSnoopInit(void)
> +{
> +    return -1;
> +}
> +
> +void
> +virNWFilterDHCPSnoopEnd(const char *ifname ATTRIBUTE_UNUSED)
> +{
> +    return;
> +}
> +
> +int
> +virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver ATTRIBUTE_UNUSED,
> +                        const char *ifname ATTRIBUTE_UNUSED,
> +                        const char *linkdev ATTRIBUTE_UNUSED,
> +                        enum virDomainNetType nettype ATTRIBUTE_UNUSED,
> +                        char *vmuuid ATTRIBUTE_UNUSED,
> +                        const unsigned char *macaddr ATTRIBUTE_UNUSED,
> +                        const char *filtername ATTRIBUTE_UNUSED,
> +                        virNWFilterHashTablePtr filterparams ATTRIBUTE_UNUSED,
> +                        virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED)
> +{
> +    virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("libvirt was not compiled "
> +                           "with libpcap and \"ip_learning='dhcp'\" requires"
> +                           " it."));
> +    return 1;
> +}
> +#endif /* HAVE_LIBPCAP */
> diff --git a/src/nwfilter/nwfilter_dhcpsnoop.h b/src/nwfilter/nwfilter_dhcpsnoop.h
> new file mode 100644
> index 0000000..25500e2
> --- /dev/null
> +++ b/src/nwfilter/nwfilter_dhcpsnoop.h
> @@ -0,0 +1,38 @@
> +/*
> + * nwfilter_dhcpsnoop.h: support DHCP snooping for a VM on an interface
> + *
> + * Copyright (C) 2010 IBM Corp.
> + * Copyright (C) 2010 David L Stevens
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
> + *
> + * Author: David L Stevens <dlstevens at us.ibm.com>
> + */
> +
> +#ifndef __NWFILTER_DHCPSNOOP_H
> +#define __NWFILTER_DHCPSNOOP_H
> +
> +int virNWFilterDHCPSnoopInit(void);
> +int virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver,
> +                            const char *ifname,
> +                            const char *linkdev,
> +                            enum virDomainNetType nettype,
> +                            const unsigned char *vmuuid,
> +                            const unsigned char *macaddr,
> +                            const char *filtername,
> +                            virNWFilterHashTablePtr filterparams,
> +                            virNWFilterDriverStatePtr driver);
> +void virNWFilterDHCPSnoopEnd(const char *ifname);
> +#endif /* __NWFILTER_DHCPSNOOP_H */
> diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c
> index ffb4b5d..d014a19 100644
> --- a/src/nwfilter/nwfilter_driver.c
> +++ b/src/nwfilter/nwfilter_driver.c
> @@ -39,6 +39,7 @@
>  #include "nwfilter_gentech_driver.h"
>  #include "configmake.h"
>  
> +#include "nwfilter_dhcpsnoop.h"
>  #include "nwfilter_learnipaddr.h"
>  
>  #define VIR_FROM_THIS VIR_FROM_NWFILTER
> @@ -66,6 +67,8 @@ static int
>  nwfilterDriverStartup(int privileged) {
>      char *base = NULL;
>  
> +    if (virNWFilterDHCPSnoopInit() < 0)
> +        return -1;
>      if (virNWFilterLearnInit() < 0)
>          return -1;
>  
> @@ -127,6 +130,7 @@ alloc_err_exit:
>  
>  conf_init_err:
>      virNWFilterTechDriversShutdown();
> +    virNWFilterDHCPSnoopEnd(0);
>      virNWFilterLearnShutdown();
>  
>      return -1;
> @@ -149,6 +153,7 @@ nwfilterDriverReload(void) {
>      conn = virConnectOpen("qemu:///system");
>  
>      if (conn) {
> +        virNWFilterDHCPSnoopEnd(0);
>          /* shut down all threads -- they will be restarted if necessary */
>          virNWFilterLearnThreadsTerminate(true);
>  
> @@ -203,6 +208,7 @@ nwfilterDriverShutdown(void) {
>  
>      virNWFilterConfLayerShutdown();
>      virNWFilterTechDriversShutdown();
> +    virNWFilterDHCPSnoopEnd(0);
>      virNWFilterLearnShutdown();
>  
>      nwfilterDriverLock(driverState);
> diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c
> index fc71e7b..245adb0 100644
> --- a/src/nwfilter/nwfilter_gentech_driver.c
> +++ b/src/nwfilter/nwfilter_gentech_driver.c
> @@ -32,6 +32,7 @@
>  #include "virterror_internal.h"
>  #include "nwfilter_gentech_driver.h"
>  #include "nwfilter_ebiptables_driver.h"
> +#include "nwfilter_dhcpsnoop.h"
>  #include "nwfilter_learnipaddr.h"
>  #include "virnetdev.h"
>  #include "datatypes.h"
> @@ -42,6 +43,8 @@
>  #define NWFILTER_STD_VAR_MAC "MAC"
>  #define NWFILTER_STD_VAR_IP  "IP"
>  
> +#define NWFILTER_DFLT_LEARN  "any"
> +
>  static int _virNWFilterTeardownFilter(const char *ifname);
>  
>  
> @@ -662,6 +665,9 @@ virNWFilterInstantiate(const unsigned char *vmuuid ATTRIBUTE_UNUSED,
>      void **ptrs = NULL;
>      int instantiate = 1;
>      char *buf;
> +    virNWFilterVarValuePtr lv;
> +    const char *learning;
> +    bool reportIP = false;
>  
>      virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0);
>      if (!missing_vars) {
> @@ -678,22 +684,47 @@ virNWFilterInstantiate(const unsigned char *vmuuid ATTRIBUTE_UNUSED,
>      if (rc < 0)
>          goto err_exit;
>  
> +    lv = virHashLookup(vars->hashTable, "ip_learning");
> +    if (lv && lv->valType == NWFILTER_VALUE_TYPE_SIMPLE)
> +        learning = lv->u.simple.value;
> +    else
> +        learning = NULL;
> +
> +    if (learning == NULL)
> +        learning = NWFILTER_DFLT_LEARN;
> +
>      if (virHashSize(missing_vars->hashTable) == 1) {
>          if (virHashLookup(missing_vars->hashTable,
>                            NWFILTER_STD_VAR_IP) != NULL) {
> -            if (virNWFilterLookupLearnReq(ifindex) == NULL) {
> -                rc = virNWFilterLearnIPAddress(techdriver,
> -                                               ifname,
> -                                               ifindex,
> -                                               linkdev,
> -                                               nettype, macaddr,
> -                                               filter->name,
> -                                               vars, driver,
> -                                               DETECT_DHCP|DETECT_STATIC);
> +            if (c_strcasecmp(learning, "none") == 0) {        /* no learning */
> +                reportIP = true;
> +                goto err_unresolvable_vars;
>              }
> -            goto err_exit;
> -        }
> -        goto err_unresolvable_vars;
> +            if (c_strcasecmp(learning, "dhcp") == 0) {
> +                rc = virNWFilterDHCPSnoopReq(techdriver, ifname, linkdev,
> +                                             nettype, vmuuid, macaddr,
> +                                             filter->name, vars, driver);
> +                goto err_exit;
> +            } else if (c_strcasecmp(learning, "any") == 0) {
> +                if (virNWFilterLookupLearnReq(ifindex) == NULL) {
> +                    rc = virNWFilterLearnIPAddress(techdriver,
> +                                                   ifname,
> +                                                   ifindex,
> +                                                   linkdev,
> +                                                   nettype, macaddr,
> +                                                   filter->name,
> +                                                   vars, driver,
> +                                                   DETECT_DHCP|DETECT_STATIC);
> +                }
> +                goto err_exit;
> +            } else {
> +                rc = 1;
> +                virNWFilterReportError(VIR_ERR_PARSE_FAILED, _("filter '%s' "
> +                                       "learning value '%s' invalid."),
> +                                       filter->name, learning);
> +            }
> +        } else
> +             goto err_unresolvable_vars;
>      } else if (virHashSize(missing_vars->hashTable) > 1) {
>          goto err_unresolvable_vars;
>      } else if (!forceWithPendingReq &&
> @@ -761,7 +792,7 @@ err_exit:
>  
>  err_unresolvable_vars:
>  
> -    buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, false);
> +    buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, reportIP);
>      if (buf) {
>          virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
>                     _("Cannot instantiate filter due to unresolvable "
> @@ -1092,6 +1123,8 @@ _virNWFilterTeardownFilter(const char *ifname)
>          return -1;
>      }
>  
> +    virNWFilterDHCPSnoopEnd(ifname);
> +
>      virNWFilterTerminateLearnReq(ifname);
>  
>      if (virNWFilterLockIface(ifname) < 0)

You'll need to add src/nwfilter/nwfilter_dhcpsnoop.c to POTFILES:

--- ./po/POTFILES.in
+++ ./po/POTFILES.in
@@ -51,6 +51,7 @@
 src/node_device/node_device_linux_sysfs.c
 src/node_device/node_device_udev.c
 src/nodeinfo.c
+src/nwfilter/nwfilter_dhcpsnoop.c
 src/nwfilter/nwfilter_driver.c
 src/nwfilter/nwfilter_ebiptables_driver.c
 src/nwfilter/nwfilter_gentech_driver.c




More information about the libvir-list mailing list