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

David L Stevens dlstevens at us.ibm.com
Fri Mar 30 19:07:19 UTC 2012


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 v7:
- renamed functions as suggested
- collected local state into "virNWFilterSnoopState" struct
- cleaned up include file list
- misc code cleanups per review comments

Signed-off-by: David L Stevens <dlstevens at us.ibm.com>
---
 docs/formatnwfilter.html.in            |   17 +
 po/POTFILES.in                         |    1 +
 src/Makefile.am                        |    2 +
 src/nwfilter/nwfilter_dhcpsnoop.c      | 1197 ++++++++++++++++++++++++++++++++
 src/nwfilter/nwfilter_dhcpsnoop.h      |   38 +
 src/nwfilter/nwfilter_driver.c         |    6 +
 src/nwfilter/nwfilter_gentech_driver.c |   59 ++-
 7 files changed, 1307 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/po/POTFILES.in b/po/POTFILES.in
index 88be04e..d4b0a86 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -51,6 +51,7 @@ src/node_device/node_device_hal.c
 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
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..8c2ff50
--- /dev/null
+++ b/src/nwfilter/nwfilter_dhcpsnoop.c
@@ -0,0 +1,1197 @@
+/*
+ * 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 <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <net/if.h>
+
+#include "memory.h"
+#include "logging.h"
+#include "datatypes.h"
+#include "virterror_internal.h"
+#include "conf/domain_conf.h"
+#include "nwfilter_gentech_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 struct {
+/* lease file */
+    int                  LeaseFD;
+    int                  nLeases; /* number of active leases */
+    int                  wLeases; /* number of written leases */
+/* thread management */
+    virHashTablePtr      SnoopReqs;
+    virHashTablePtr      IfnameToKey;
+    virHashTablePtr      Active;
+    virMutex             SnoopLock;
+    virMutex             ActiveLock;
+} virNWFilterSnoopState = {
+    .LeaseFD = -1,
+    .SnoopLock = { .lock=PTHREAD_MUTEX_INITIALIZER },
+    .ActiveLock = { .lock=PTHREAD_MUTEX_INITIALIZER },
+};
+
+#define virNWFilterSnoopLock() \
+    { virMutexLock(&virNWFilterSnoopState.SnoopLock); }
+#define virNWFilterSnoopUnlock() \
+    { virMutexUnlock(&virNWFilterSnoopState.SnoopLock); }
+#define virNWFilterSnoopLockActive() \
+    { virMutexLock(&virNWFilterSnoopState.ActiveLock); }
+#define virNWFilterSnoopUnlockActive() \
+    { virMutexUnlock(&virNWFilterSnoopState.ActiveLock); }
+
+static char *
+virNWFilterSnoopActivate(virThreadPtr thread)
+{
+    char            *key, *data;
+    unsigned long    threadID = (unsigned long int)thread->thread;
+
+    if (virAsprintf(&key, "0x%0*lX", (int)sizeof(threadID)*2, threadID) < 0) {
+        virReportOOMError();
+        return NULL;
+    }
+
+    virNWFilterSnoopLockActive();
+    data = strdup("1");
+    if (data == NULL) {
+        virReportOOMError();
+        VIR_FREE(key);
+    } else if (virHashAddEntry(virNWFilterSnoopState.Active, key, data)) {
+        VIR_FREE(key);
+        VIR_FREE(data);
+    }
+    virNWFilterSnoopUnlockActive();
+    return key;
+}
+
+static void
+virNWFilterSnoopCancel(char **ThreadKey)
+{
+    if (*ThreadKey == NULL)
+        return;
+
+    virNWFilterSnoopLockActive();
+    (void) virHashRemoveEntry(virNWFilterSnoopState.Active, *ThreadKey);
+    *ThreadKey = NULL;
+    virNWFilterSnoopUnlockActive();
+}
+
+static bool
+virNWFilterSnoopIsActive(char *ThreadKey)
+{
+    void *entry;
+
+    if (ThreadKey == NULL)
+        return 0;
+    virNWFilterSnoopLockActive();
+    entry = virHashLookup(virNWFilterSnoopState.Active, ThreadKey);
+    virNWFilterSnoopUnlockActive();
+    return entry != NULL;
+}
+
+#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 virNWFilterSnoopIPLease      *start;
+    struct virNWFilterSnoopIPLease      *end;
+    char                                *threadkey;
+};
+
+#define POLL_INTERVAL    10*1000 /* 10 secs */
+#define MAXERRS               25 /* retries on failing device */
+
+struct virNWFilterSnoopIPLease {
+    uint32_t                        IPAddress;
+    uint32_t                        IPServer;
+    struct virNWFilterSnoopReq     *SnoopReq;
+    unsigned int                    Timeout;
+    /* timer list */
+    struct virNWFilterSnoopIPLease *prev;
+    struct virNWFilterSnoopIPLease *next;
+};
+
+static struct virNWFilterSnoopIPLease *
+    virNWFilterSnoopGetByIP(struct virNWFilterSnoopIPLease *start,
+                            uint32_t ipaddr);
+static void virNWFilterSnoopUpdateLease(struct virNWFilterSnoopIPLease *pl,
+                                        time_t timeout);
+
+static struct virNWFilterSnoopReq *virNWFilterSnoopNewReq(const char *ifkey);
+
+static void virNWFilterSnoopLeaseFileOpen(void);
+static void virNWFilterSnoopLeaseFileClose(void);
+static void virNWFilterSnoopLeaseFileLoad(void);
+static void virNWFilterSnoopLeaseFileSave(struct virNWFilterSnoopIPLease *ipl);
+static void virNWFilterSnoopLeaseFileRestore(struct virNWFilterSnoopReq *req);
+static void virNWFilterSnoopLeaseFileRefresh(void);
+
+/*
+ * virNWFilterSnoopListAdd - add an IP lease to a list
+ */
+static void
+virNWFilterSnoopListAdd(struct virNWFilterSnoopIPLease *plnew,
+                        struct virNWFilterSnoopIPLease **start,
+                        struct virNWFilterSnoopIPLease **end)
+{
+    struct virNWFilterSnoopIPLease      *pl;
+
+    plnew->next = plnew->prev = 0;
+    if (!*start) {
+        *start = *end = plnew;
+        return;
+    }
+    for (pl = *end; pl && plnew->Timeout < pl->Timeout;
+         pl = pl->prev)
+        /* empty */ ;
+    if (!pl) {
+        plnew->next = *start;
+        *start = plnew;
+    } else {
+        plnew->next = pl->next;
+        pl->next = plnew;
+    }
+    plnew->prev = pl;
+    if (plnew->next)
+        plnew->next->prev = plnew;
+    else
+        *end = plnew;
+}
+
+/*
+ * virNWFilterSnoopTimerAdd - add an IP lease to the timer list
+ */
+static void
+virNWFilterSnoopTimerAdd(struct virNWFilterSnoopIPLease *plnew)
+{
+    struct virNWFilterSnoopReq *req = plnew->SnoopReq;
+
+    virNWFilterSnoopListAdd(plnew, &req->start, &req->end);
+}
+
+/*
+ * virNWFilterSnoopInstallRule - install rule for a lease
+ */
+static int
+virNWFilterSnoopInstallRule(struct virNWFilterSnoopIPLease *ipl)
+{
+    char                     ipbuf[INET_ADDRSTRLEN];
+    int                      rc;
+    virNWFilterVarValuePtr   ipVar;
+
+    if (!inet_ntop(AF_INET, &ipl->IPAddress, ipbuf, sizeof(ipbuf))) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterSnoopInstallRule inet_ntop failed "
+                                 " (0x%08X)"), ipl->IPAddress);
+        return -1;
+    }
+    ipVar = virNWFilterVarValueCreateSimpleCopyValue(ipbuf);
+    if (!ipVar) {
+        virReportOOMError();
+        return -1;
+    }
+    if (virNWFilterHashTablePut(ipl->SnoopReq->vars, "IP", ipVar, 1)) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("Could not add variable \"IP\" to hashmap"));
+        virNWFilterVarValueFree(ipVar);
+        return -1;
+    }
+    rc = virNWFilterInstantiateFilterLate(NULL,
+                                          ipl->SnoopReq->ifname,
+                                          ipl->SnoopReq->ifindex,
+                                          ipl->SnoopReq->linkdev,
+                                          ipl->SnoopReq->nettype,
+                                          ipl->SnoopReq->macaddr,
+                                          ipl->SnoopReq->filtername,
+                                          ipl->SnoopReq->vars,
+                                          ipl->SnoopReq->driver);
+    if (rc)
+        return -1;
+    return 0;
+}
+
+/*
+ * virNWFilterSnoopLeaseAdd - create or update an IP lease
+ */
+static void
+virNWFilterSnoopLeaseAdd(struct virNWFilterSnoopIPLease *plnew,
+                         bool update_leasefile)
+{
+    struct virNWFilterSnoopIPLease *pl;
+    struct virNWFilterSnoopReq *req = plnew->SnoopReq;
+
+    pl = virNWFilterSnoopGetByIP(req->start, plnew->IPAddress);
+    if (pl) {
+        virNWFilterSnoopUpdateLease(pl, plnew->Timeout);
+        if (update_leasefile)
+            virNWFilterSnoopLeaseFileSave(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 && virNWFilterSnoopInstallRule(pl) < 0) {
+        VIR_FREE(pl);
+        return;
+    }
+    virNWFilterSnoopTimerAdd(pl);
+    virNWFilterSnoopState.nLeases++;
+    if (update_leasefile)
+        virNWFilterSnoopLeaseFileSave(pl);
+}
+
+/*
+ * virNWFilterSnoopListDel - remove an IP lease from a list
+ */
+static void
+virNWFilterSnoopListDel(struct virNWFilterSnoopIPLease *ipl,
+                        struct virNWFilterSnoopIPLease **start,
+                        struct virNWFilterSnoopIPLease **end)
+{
+    if (ipl->prev)
+        ipl->prev->next = ipl->next;
+    else
+        *start = ipl->next;
+    if (ipl->next)
+        ipl->next->prev = ipl->prev;
+    else
+        *end = ipl->prev;
+    ipl->next = ipl->prev = 0;
+}
+
+/*
+ * virNWFilterSnoopTimerDel - remove an IP lease from the timer list
+ */
+static void
+virNWFilterSnoopTimerDel(struct virNWFilterSnoopIPLease *ipl)
+{
+    struct virNWFilterSnoopReq *req = ipl->SnoopReq;
+
+    virNWFilterSnoopListDel(ipl, &req->start, &req->end);
+    ipl->Timeout = 0;
+}
+
+/*
+ * virNWFilterSnoopLeaseDel - delete an IP lease
+ */
+static void
+virNWFilterSnoopLeaseDel(struct virNWFilterSnoopReq *req,
+                         uint32_t ipaddr, bool update_leasefile)
+{
+    struct virNWFilterSnoopIPLease *ipl;
+
+    ipl = virNWFilterSnoopGetByIP(req->start, ipaddr);
+    if (ipl == NULL)
+        return;
+
+    virNWFilterSnoopTimerDel(ipl);
+
+    if (update_leasefile) {
+        virNWFilterSnoopLeaseFileSave(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,
+                                   _("virNWFilterSnoopListDel failed"));
+    }
+    VIR_FREE(ipl);
+    virNWFilterSnoopState.nLeases--;
+}
+
+/*
+ * virNWFilterSnoopUpdateLease - update the timeout on an IP lease
+ */
+static void
+virNWFilterSnoopUpdateLease(struct virNWFilterSnoopIPLease *ipl, time_t timeout)
+{
+    if (timeout < ipl->Timeout)
+        return;  /* no take-backs */
+    virNWFilterSnoopTimerDel(ipl);
+    ipl->Timeout = timeout;
+    virNWFilterSnoopTimerAdd(ipl);
+    return;
+}
+
+/*
+ * virNWFilterSnoopGetByIP - lookup IP lease by IP address
+ */
+static struct virNWFilterSnoopIPLease *
+virNWFilterSnoopGetByIP(struct virNWFilterSnoopIPLease *start, uint32_t ipaddr)
+{
+    struct virNWFilterSnoopIPLease *pl;
+
+    for (pl = start; pl && pl->IPAddress != ipaddr; pl = pl->next)
+        /* empty */ ;
+    return pl;
+}
+
+/*
+ * virNWFilterSnoopTimerRun - run the IP lease timeout list
+ */
+static unsigned int
+virNWFilterSnoopTimerRun(struct virNWFilterSnoopReq *req)
+{
+    time_t now;
+
+    now = time(0);
+    while (req->start && req->start->Timeout <= now)
+        virNWFilterSnoopLeaseDel(req, req->start->IPAddress, 1);
+    return 0;
+}
+
+typedef unsigned char Eaddr[6];
+
+struct virNWFilterSnoopEthHdr {
+    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 virNWFilterSnoopDHCPHdr {
+    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
+
+static const unsigned char dhcp_magic[4] = { 99, 130, 83, 99 };
+
+static int
+virNWFilterSnoopDHCPGetOpt(struct virNWFilterSnoopDHCPHdr *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
+virNWFilterSnoopDHCPDecode(struct virNWFilterSnoopReq *req,
+                           struct virNWFilterSnoopEthHdr *pep,
+                           int len)
+{
+    struct iphdr                        *pip;
+    struct udphdr                       *pup;
+    struct virNWFilterSnoopDHCPHdr      *pd;
+    struct virNWFilterSnoopIPLease       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 virNWFilterSnoopEthHdr, eh_data);
+        break;
+    case ETHERTYPE_VLAN:
+        if (ntohs(pep->ehv_type) != ETHERTYPE_IP)
+            return;
+        pip = (struct iphdr *) pep->ehv_data;
+        len -= offsetof(struct virNWFilterSnoopEthHdr, ehv_data);
+        break;
+    default:
+        return;
+    }
+    pip = (struct iphdr *) pep->eh_data;
+    len -= sizeof(*pep);
+    if (len < 0)
+        return;
+    pup = (struct udphdr *) ((char *) pip + (pip->ihl << 2));
+    len -= pip->ihl << 2;
+    if (len < 0)
+        return;
+    pd = (struct virNWFilterSnoopDHCPHdr *) ((char *) pup + sizeof(*pup));
+    len -= sizeof(*pup);
+    if (len < 0)
+        return;                 /* invalid packet length */
+    if (virNWFilterSnoopDHCPGetOpt(pd, len, &mtype, &leasetime) < 0)
+        return;
+
+    memset(&ipl, 0, sizeof(ipl));
+    ipl.IPAddress = pd->d_yiaddr;
+    ipl.IPServer = pd->d_siaddr;
+    if (leasetime == ~0)
+        ipl.Timeout = ~0;
+    else
+        ipl.Timeout = time(0) + leasetime;
+    ipl.SnoopReq = req;
+
+    switch (mtype) {
+        case DHCPACK:
+            virNWFilterSnoopLeaseAdd(&ipl, 1);
+            break;
+        case DHCPDECLINE:
+        case DHCPRELEASE:
+            virNWFilterSnoopLeaseDel(req, ipl.IPAddress, 1);
+            break;
+        default:
+            break;
+    }
+}
+
+#define PBUFSIZE        576     /* >= IP/TCP/DHCP headers */
+#define TIMEOUT          30 /* secs */
+
+static pcap_t *
+virNWFilterSnoopDHCPOpen(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));
+        pcap_freecode(&fp);
+        return 0;
+    }
+    pcap_freecode(&fp);
+    return handle;
+}
+
+static void
+virNWFilterSnoopReqFree(struct virNWFilterSnoopReq *req)
+{
+    struct virNWFilterSnoopIPLease *ipl;
+
+    if (!req)
+        return;
+
+    /* free all leases */
+    for (ipl = req->start; ipl; ipl = req->start)
+        virNWFilterSnoopLeaseDel(req, ipl->IPAddress, 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 virNWFilterSnoopEthHdr  *packet;
+    int                             ifindex;
+    int                             errcount;
+    char                           *threadkey;
+    virThread                       thread;
+
+    handle = virNWFilterSnoopDHCPOpen(req->ifname);
+    if (!handle)
+        return;
+
+    virThreadSelf(&thread);
+    req->threadkey = virNWFilterSnoopActivate(&thread);
+    threadkey = strdup(req->threadkey);
+    if (threadkey == NULL) {
+        virReportOOMError();
+        pcap_close(handle);
+        return;
+    }
+
+    ifindex = if_nametoindex(req->ifname);
+
+    virNWFilterSnoopLock();
+    virNWFilterSnoopLeaseFileRestore(req);
+    virNWFilterSnoopUnlock();
+
+    errcount = 0;
+    while (1) {
+        int rv;
+
+        virNWFilterSnoopLock();
+        virNWFilterSnoopTimerRun(req);
+        virNWFilterSnoopUnlock();
+
+        rv = pcap_next_ex(handle, &hdr, (const u_char **)&packet);
+
+        if (!virNWFilterSnoopIsActive(threadkey)) {
+            VIR_FREE(threadkey);
+            pcap_close(handle);
+            return;
+        }
+        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 = virNWFilterSnoopDHCPOpen(req->ifname);
+                if (!handle)
+                    break;
+            }
+            continue;
+        }
+        errcount = 0;
+        if (rv) {
+            virNWFilterSnoopLock();
+            virNWFilterSnoopDHCPDecode(req, packet, hdr->caplen);
+            virNWFilterSnoopUnlock();
+        }
+    }
+    virNWFilterSnoopCancel(&req->threadkey);
+    (void) virHashRemoveEntry(virNWFilterSnoopState.IfnameToKey, req->ifname);
+    VIR_FREE(req->ifname);
+    /* if we still have a valid lease, keep the req for restarts */
+    if (!req->start || req->start->Timeout < time(0))
+        (void) virHashRemoveEntry(virNWFilterSnoopState.SnoopReqs, req->ifkey);
+    VIR_FREE(threadkey);
+    pcap_close(handle);
+    return;
+}
+
+static void
+virNWFilterSnoopIFKeyFMT(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];
+    virThread                   thread;
+
+    virNWFilterSnoopIFKeyFMT(ifkey, vmuuid, macaddr);
+    virNWFilterSnoopLock();
+    req = virHashLookup(virNWFilterSnoopState.SnoopReqs, ifkey);
+    isnewreq = req == NULL;
+    if (!isnewreq) {
+        if (req->threadkey) {
+            virNWFilterSnoopUnlock();
+            return 0;
+        }
+    } else {
+        req = virNWFilterSnoopNewReq(ifkey);
+        if (!req) {
+            virNWFilterSnoopUnlock();
+            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->ifname == NULL || req->filtername == NULL) {
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        virReportOOMError();
+        return -1;
+    }
+
+    if (techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr, NULL,
+                                       false)) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("applyDHCPOnlyRules "
+                               "failed - spoofing not protected!"));
+    }
+
+    req->vars = virNWFilterHashTableCreate(0);
+    if (!req->vars) {
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        virReportOOMError();
+        return -1;
+    }
+    if (virNWFilterHashTablePutAll(filterparams, req->vars)) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterDHCPSnoopReq: can't copy variables"
+                                " on if %s"), ifkey);
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        return -1;
+    }
+    req->driver = driver;
+
+    if (virHashAddEntry(virNWFilterSnoopState.IfnameToKey, ifname,
+                        req->ifkey)) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterDHCPSnoopReq ifname map failed"
+                                 " on interface \"%s\" key \"%s\""), ifname,
+                               ifkey);
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        return -1;
+    }
+    if (isnewreq &&
+        virHashAddEntry(virNWFilterSnoopState.SnoopReqs, ifkey, req)) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterDHCPSnoopReq req add failed on"
+                               " interface \"%s\" ifkey \"%s\""), ifname,
+                               ifkey);
+        (void) virHashRemoveEntry(virNWFilterSnoopState.IfnameToKey, ifname);
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        return -1;
+    }
+    virNWFilterSnoopUnlock();
+    if (virThreadCreate(&thread, false, virNWFilterDHCPSnoop, req) != 0) {
+        virNWFilterSnoopLock();
+        (void) virHashRemoveEntry(virNWFilterSnoopState.IfnameToKey, ifname);
+        (void) virHashRemoveEntry(virNWFilterSnoopState.SnoopReqs, ifkey);
+        virNWFilterSnoopUnlock();
+        virNWFilterSnoopReqFree(req);
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterDHCPSnoopReq virThreadCreate "
+                                 "failed on interface \"%s\""), ifname);
+        return -1;
+    }
+    return 0;
+}
+
+/*
+ * virNWFilterSnoopReqRelease - hash table free function to kill a request
+ */
+static void
+virNWFilterSnoopReqRelease(void *req0, const void *name ATTRIBUTE_UNUSED)
+{
+    struct virNWFilterSnoopReq *req = (struct virNWFilterSnoopReq *) req0;
+
+    if (!req)
+        return;
+
+    if (req->threadkey)
+        virNWFilterSnoopCancel(&req->threadkey);
+    virNWFilterSnoopReqFree(req);
+}
+
+static void
+virNWFilterSnoopLeaseFileClose(void)
+{
+    VIR_FORCE_CLOSE(virNWFilterSnoopState.LeaseFD);
+}
+
+static void
+virNWFilterSnoopLeaseFileOpen(void)
+{
+    virNWFilterSnoopLeaseFileClose();
+
+    virNWFilterSnoopState.LeaseFD = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND,
+                                         0644);
+}
+
+int
+virNWFilterDHCPSnoopInit(void)
+{
+    if (virNWFilterSnoopState.SnoopReqs)
+        return 0;
+
+    virNWFilterSnoopLock();
+    virNWFilterSnoopState.IfnameToKey = virHashCreate(0, NULL);
+    virNWFilterSnoopState.SnoopReqs =
+        virHashCreate(0, virNWFilterSnoopReqRelease);
+    if (!virNWFilterSnoopState.IfnameToKey ||
+        !virNWFilterSnoopState.SnoopReqs) {
+        virNWFilterSnoopUnlock();
+        virReportOOMError();
+        goto errexit;
+    }
+    virNWFilterSnoopLeaseFileLoad();
+    virNWFilterSnoopLeaseFileOpen();
+
+    virNWFilterSnoopUnlock();
+
+    virNWFilterSnoopLockActive();
+    virNWFilterSnoopState.Active = virHashCreate(0, 0);
+    if (!virNWFilterSnoopState.Active) {
+        virNWFilterSnoopUnlockActive();
+        virReportOOMError();
+        goto errexit;
+    }
+    virNWFilterSnoopUnlockActive();
+    return 0;
+
+errexit:
+    if (virNWFilterSnoopState.IfnameToKey) {
+        virHashFree(virNWFilterSnoopState.IfnameToKey);
+        virNWFilterSnoopState.IfnameToKey = 0;
+    }
+    if (virNWFilterSnoopState.SnoopReqs) {
+        virHashFree(virNWFilterSnoopState.SnoopReqs);
+        virNWFilterSnoopState.SnoopReqs = 0;
+    }
+    if (virNWFilterSnoopState.Active) {
+        virHashFree(virNWFilterSnoopState.Active);
+        virNWFilterSnoopState.Active = 0;
+    }
+    return -1;
+}
+
+void
+virNWFilterDHCPSnoopEnd(const char *ifname)
+{
+    char *ifkey = NULL;
+
+    virNWFilterSnoopLock();
+    if (!virNWFilterSnoopState.SnoopReqs) {
+        virNWFilterSnoopUnlock();
+        return;
+    }
+
+    if (ifname) {
+        ifkey = (char *)virHashLookup(virNWFilterSnoopState.IfnameToKey,ifname);
+        (void) virHashRemoveEntry(virNWFilterSnoopState.IfnameToKey, ifname);
+        if (!ifkey) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("ifname \"%s\" not in key map"), ifname);
+            virNWFilterSnoopUnlock();
+            return;
+        }
+    }
+
+    if (ifkey) {
+        struct virNWFilterSnoopReq *req;
+
+        req = virHashLookup(virNWFilterSnoopState.SnoopReqs, ifkey);
+        if (!req) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("ifkey \"%s\" has no req"), ifkey);
+            virNWFilterSnoopUnlock();
+            return;
+        }
+        if (!req->start || req->start->Timeout < time(0)) {
+            (void) virHashRemoveEntry(virNWFilterSnoopState.SnoopReqs,
+                                      req->ifkey);
+            virNWFilterSnoopUnlock();
+            return;
+        }
+        /* keep valid lease req; drop interface association */
+        virNWFilterSnoopCancel(&req->threadkey);
+        VIR_FREE(req->ifname);
+    } else {                      /* free all of them */
+        virNWFilterSnoopLeaseFileClose();
+        virHashFree(virNWFilterSnoopState.IfnameToKey);
+        virHashFree(virNWFilterSnoopState.SnoopReqs);
+        virNWFilterSnoopState.IfnameToKey = virHashCreate(0, 0);
+        if (!virNWFilterSnoopState.IfnameToKey) {
+            virNWFilterSnoopUnlock();
+            virReportOOMError();
+            return;
+        }
+        virNWFilterSnoopState.SnoopReqs =
+            virHashCreate(0, virNWFilterSnoopReqRelease);
+        if (!virNWFilterSnoopState.SnoopReqs) {
+            virHashFree(virNWFilterSnoopState.IfnameToKey);
+            virNWFilterSnoopUnlock();
+            virReportOOMError();
+            return;
+        }
+        virNWFilterSnoopLeaseFileLoad();
+    }
+    virNWFilterSnoopUnlock();
+}
+
+static int
+virNWFilterSnoopLeaseFileWrite(int lfd, const char *ifkey,
+                               struct virNWFilterSnoopIPLease *ipl)
+{
+    char lbuf[256], ipstr[INET_ADDRSTRLEN], dhcpstr[INET_ADDRSTRLEN];
+
+    if (inet_ntop(AF_INET, &ipl->IPAddress, ipstr, sizeof(ipstr)) == 0) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("inet_ntop(0x%08X) failed"), ipl->IPAddress);
+        return -1;
+    }
+    if (inet_ntop(AF_INET, &ipl->IPServer, dhcpstr, sizeof(dhcpstr)) == 0) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("inet_ntop(0x%08X) failed"), ipl->IPServer);
+        return -1;
+    }
+    /* time intf ip dhcpserver */
+    snprintf(lbuf, sizeof(lbuf), "%u %s %s %s\n", 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
+virNWFilterSnoopLeaseFileSave(struct virNWFilterSnoopIPLease *ipl)
+{
+    struct virNWFilterSnoopReq *req = ipl->SnoopReq;
+
+    if (virNWFilterSnoopState.LeaseFD < 0)
+       virNWFilterSnoopLeaseFileOpen();
+    if (virNWFilterSnoopLeaseFileWrite(virNWFilterSnoopState.LeaseFD,
+                                       req->ifkey, ipl) < 0)
+       return;
+    /* keep dead leases at < ~95% of file size */
+    if (++virNWFilterSnoopState.wLeases >= virNWFilterSnoopState.nLeases*20)
+        virNWFilterSnoopLeaseFileLoad();   /* load & refresh lease file */
+}
+
+static struct virNWFilterSnoopReq *
+virNWFilterSnoopNewReq(const char *ifkey)
+{
+    struct virNWFilterSnoopReq *req;
+
+    if (ifkey == NULL || strlen(ifkey) != VIR_IFKEY_LEN-1) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("virNWFilterSnoopNewReq called with invalid "
+                                 "key \"%s\" (%d)"),
+                               ifkey ? ifkey : "", strlen(ifkey));
+        return NULL;
+    }
+    if (VIR_ALLOC(req) < 0) {
+        virReportOOMError();
+        return NULL;
+    }
+    if (virStrcpyStatic(req->ifkey, ifkey) == NULL)
+        VIR_FREE(req);
+
+    return req;
+}
+
+static void
+virNWFilterSnoopSaveIter(void *payload,
+                         const void *name ATTRIBUTE_UNUSED,
+                         void *data)
+{
+    struct virNWFilterSnoopReq      *req = payload;
+    int                              tfd = *(int *)data;
+    struct virNWFilterSnoopIPLease  *ipl;
+
+    /* clean up orphaned, expired leases */
+    if (!req->threadkey) {
+        time_t now;
+
+        now = time(0);
+        for (ipl = req->start; ipl; ipl = ipl->next)
+            if (ipl->Timeout < now)
+                virNWFilterSnoopLeaseDel(req, ipl->IPAddress , 0);
+        if (!req->start) {
+            virNWFilterSnoopReqFree(req);
+            return;
+        }
+    }
+    for (ipl = req->start; ipl; ipl = ipl->next)
+        (void) virNWFilterSnoopLeaseFileWrite(tfd, req->ifkey, ipl);
+}
+
+static void
+virNWFilterSnoopLeaseFileRefresh(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 (virNWFilterSnoopState.SnoopReqs)
+        virHashForEach(virNWFilterSnoopState.SnoopReqs,
+                       virNWFilterSnoopSaveIter, (void *)&tfd);
+    VIR_FORCE_CLOSE(tfd);
+    if (rename(TMPLEASEFILE, LEASEFILE) < 0) {
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("rename(\"%s\", \"%s\"): %s"),
+                               TMPLEASEFILE, LEASEFILE, strerror(errno));
+        (void) unlink(TMPLEASEFILE);
+    }
+    virNWFilterSnoopState.wLeases = 0;
+    virNWFilterSnoopLeaseFileOpen();
+}
+
+
+static void
+virNWFilterSnoopLeaseFileLoad(void)
+{
+    char line[256], ifkey[VIR_IFKEY_LEN], ipstr[INET_ADDRSTRLEN],
+         srvstr[INET_ADDRSTRLEN];
+    struct virNWFilterSnoopIPLease 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,
+                                   _("virNWFilterSnoopLeaseFileLoad lease file "
+                                     "line %d corrupt"), ln);
+            break;
+        }
+        ln++;
+        /* key len 55 = "VMUUID"+'-'+"MAC" */
+        if (sscanf(line, "%u %55s %16s %16s", &ipl.Timeout,
+                   ifkey, ipstr, srvstr) < 4) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("virNWFilterSnoopLeaseFileLoad lease file "
+                                     "line %d corrupt"), ln);
+            break;
+        }
+        if (ipl.Timeout && ipl.Timeout < now)
+            continue;
+        req = virHashLookup(virNWFilterSnoopState.SnoopReqs, ifkey);
+        if (!req) {
+            req = virNWFilterSnoopNewReq(ifkey);
+            if (!req)
+               break;
+            if (virHashAddEntry(virNWFilterSnoopState.SnoopReqs, ifkey, req)) {
+                virNWFilterSnoopReqFree(req);
+                virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                       _("virNWFilterSnoopLeaseFileLoad req add"
+                                         " failed on interface \"%s\""), ifkey);
+                continue;
+            }
+        }
+
+        if (inet_pton(AF_INET, ipstr, &ipl.IPAddress) <= 0) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("line %d corrupt ipaddr \"%s\""),
+                                  ln, ipstr);
+            continue;
+        }
+        (void) inet_pton(AF_INET, srvstr, &ipl.IPServer);
+        ipl.SnoopReq = req;
+
+        if (ipl.Timeout)
+            virNWFilterSnoopLeaseAdd(&ipl, 0);
+        else
+            virNWFilterSnoopLeaseDel(req, ipl.IPAddress, 0);
+    }
+    if (fp != NULL)
+        (void) fclose(fp);
+    virNWFilterSnoopLeaseFileRefresh();
+}
+
+static void
+virNWFilterSnoopLeaseFileRestore(struct virNWFilterSnoopReq *req)
+{
+    struct virNWFilterSnoopIPLease *ipl;
+
+    for (ipl=req->start; ipl; ipl=ipl->next)
+        (void) virNWFilterSnoopInstallRule(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..07ae33c 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)
-- 
1.7.6.5




More information about the libvir-list mailing list