[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Re: [libvirt] [PATCH V11 4/7] nwfilter: add DHCP snooping



On Tue, Apr 17, 2012 at 10:44:05AM -0400, Stefan Berger 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.
> 
> The following interface XML activates and uses the DHCP snooping:
> 
>     <interface type='bridge'>
>       <source bridge='virbr0'/>
>       <filterref filter='clean-traffic'>
>         <parameter name='ip_learning' value='dhcp'/>
>       </filterref>
>     </interface>
> 
> All filters containig the variable 'IP' are automatically adjusted when
> the VM receives an IP address via DHCP. However, multiple IP addresses per
> interface are silently ignored in this patch, thus only supporting one IP
> address per interface. Multiple IP address support is added in a later
> patch in this series.
> 
> Signed-off-by: David L Stevens <dlstevens us ibm com>
> Signed-off-by: Stefan Berger <stefanb linux vnet ibm com>
> 
> ---
> 
> Changes since v10:
> - using 2 pcap captures now, one for incoming and one for outgoing traffic
>   to prevent libvirt from picking up spoofed DHCP packets and adding bogus
>   leases
> - more cleanups on variable names
> 
> Changes since v9:
> - introduced usage of thread pool for having a worker that evaluates the
>   received DHCP packets and have it instantiate the new filters; it does
>   more time consuming work while the 'normal' thread tries to maximies
>   its time in libpcap packet capture function
> - added rate limitation for evaluation of DCHP packets to prevent
>   flooding the worker thread
> - thread startup synchronization to avoid missing packets on a PXE-booting
>   VM that may send DHCP packets early
> - more work on documentation
> - introducing #defines for reserved variable names
> 
> Changes since v8:
> - naming consistency
> - memory leaks
> - if-before-free not being necessary
> - separate shutdown function (for avoiding a valgrind reporting memory leak)
> - a compilation error ("%d", strlen())
> - pass NULL for a pointer rather than 0
> - use common exits where possible
> - make 'make syntax-check' pass
> - due to a locking bug resulting in a deadlock reworked the locking and
>   introduced a reference counter for the SnoopReq that must be held while
>   the 'req' variable is accessed; it resulred in finer grained locking with
>   the virNWFilterSnoopLock()
> - improved on the documentation
> 
> Changes since v7:
> - renamed functions as suggested
> - collected local state into "virNWFilterSnoopState" struct
> - cleaned up include file list
> - misc code cleanups per review comments
> 
> ---
>  docs/formatnwfilter.html.in            |  143 +-
>  src/Makefile.am                        |    2 
>  src/conf/nwfilter_params.h             |    5 
>  src/nwfilter/nwfilter_dhcpsnoop.c      | 2025 +++++++++++++++++++++++++++++++++
>  src/nwfilter/nwfilter_dhcpsnoop.h      |   39 
>  src/nwfilter/nwfilter_driver.c         |    6 
>  src/nwfilter/nwfilter_gentech_driver.c |   63 -
>  7 files changed, 2238 insertions(+), 45 deletions(-)
>  create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c
>  create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h
> 
> Index: libvirt-acl/docs/formatnwfilter.html.in
> ===================================================================
> --- libvirt-acl.orig/docs/formatnwfilter.html.in
> +++ libvirt-acl/docs/formatnwfilter.html.in
> @@ -371,6 +371,118 @@
>        Further, the notation of $VARIABLE is short-hand for $VARIABLE[ 0]  The
>        former notation always assumes the iterator with Id '0'.
>      <p>
> +
> +    <h3><a name="nwfelemsRulesAdvIPAddrDetection">Automatic IP address detection</a></h3>
> +    <p>
> +       The detection of IP addresses used on a virtual machine's interface
> +       is automatically activated if the variable <code>IP</code> is referenced
> +       but not value has been assign to it.
> +       <span class="since">Since 0.9.12</span>
> +       the variable <code>ip_learning</code> can be used to specify
> +       the IP address learning method to use. Valid values are <code>any</code>,
> +       <code>dhcp</code>, or <code>none</code>.
> +       <br/><br/>
> +       The value <code>any</code> means that libvirt may use any packet to
> +       determine the address in use by a virtual machine, which is the default
> +       behavior if the variable <code>ip_learning</code> is not set. This method
> +       will only detect a single IP address on an interface.
> +       Once a VM's IP address has been detected, its IP network traffic
> +       will be locked to that address, if for example IP address spoofing
> +       is prevented by one of its filters. In that case the user of the VM
> +       will not be able to change the IP address on the interface inside
> +       the VM, which would be considered IP address spoofing.
> +       When a VM is migrated to another host or resumed after a suspend operation,
> +       the first packet sent by the VM will again determine the IP address it can
> +       use on a particular interface.
> +       <br/><br>
> +       A value of <code>dhcp</code> specifies that libvirt should only honor DHCP
> +       server-assigned addresses with valid leases. This method supports the detection
> +       and usage of multiple IP address per interface.
> +       When a VM is resumed after a suspend operation, still valid IP address leases
> +       are applied to its filters. Otherwise the VM is expected to again use DHCP to obtain new
> +       IP addresses. The migration of a VM to another physical host requires that
> +       the VM again runs the DHCP protocol.
> +       <br/><br/>
> +       Use of <code>ip_learning=dhcp</code> (DHCP snooping) provides additional
> +       anti-spoofing security, especially when combined with a filter allowing
> +       only trusted DHCP servers to assign addresses. To enable this, set the
> +       variable <code>DHCPSERVER</code> to the IP address of a valid DHCP server
> +       and provide filters that use this variable to filter incoming DHCP responses.
> +       <br/><br/>
> +       When DHCP snooping is enable and 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).
> +       <br/><br/>
> +       Note that automatic DHCP detection listens to the DHCP traffic
> +       the VM exchanges with the DHCP server of the infrastructure. To avoid
> +       denial-of-service attacks on libvirt, the evaluation of those packets
> +       is rate-limited, meaning that a VM sending an excessive number of DHCP
> +       packets per seconds on an interface will not have all of those packets
> +       evaluated and thus filters may not get adapted. Normal DHCP client
> +       behavior is assumed to send a low number of DHCP packets per second.
> +       Further, it is important to setup approriate filters on all VMs in
> +       the infrastructure to avoid them  being able to send DHCP
> +       packets. Therefore VMs must either be prevented from sending UDP and TCP
> +       traffic from port 67 to port 68 or the <code>DHCPSERVER</code>
> +       variable should be used on all VMs to restrict DHCP server messages to
> +       only be allowed to originate from trusted DHCP servers. At the same
> +       time anti-spoofing prevention must be enabled on all VMs in the subnet.
> +       <br/><br/>
> +       If <code>ip_learning</code> is set to <code>none</code>, libvirt does not do
> +       IP address learning and referencing <code>IP</code> without assigning it an
> +       explicit value is an error.
> +       <br/><br/>
> +       The following XML provides an example for the activation of IP address learning
> +       using the DHCP snooping method:
> +    </p>
> +<pre>
> +    &lt;interface type='bridge'&gt;
> +      &lt;source bridge='virbr0'/&gt;
> +      &lt;filterref filter='clean-traffic'&gt;
> +        &lt;parameter name='ip_learning' value='dhcp'/&gt;
> +      &lt;/filterref&gt;
> +    &lt;/interface&gt;
> +</pre>
> +
> +    <h3><a name="nwfelemsReservedVars">Reserved Variables</a></h3>
> +    <p>
> +      The following table lists reserved variables in use by libvirt.
> +    </p>
> +      <table class="top_table">
> +       <tr>
> +         <th> Variable Name </th>
> +         <th> Semantics </th>
> +       </tr>
> +       <tr>
> +         <td> MAC </td>
> +         <td> The MAC address of the interface </td>
> +       </tr>
> +       <tr>
> +         <td> IP </td>
> +         <td> The list of IP addresses in use by an interface </td>
> +       </tr>
> +       <tr>
> +         <td> IPV6 </td>
> +         <td> Not currently implemented:
> +              the list of IPV6 addresses in use by an interface </td>
> +       </tr>
> +       <tr>
> +         <td> DHCPSERVER </td>
> +         <td> The list of IP addresses of trusted DHCP servers</td>
> +       </tr>
> +       <tr>
> +         <td> DHCPSERVERV6 </td>
> +         <td> Not currently implemented:
> +              The list of IPv6 addresses of trusted DHCP servers</td>
> +       </tr>
> +       <tr>
> +         <td> ip_learning </td>
> +         <td> The choice of the IP address detection mode </td>
> +       </tr>
> +      </table>
> +
>      <h2><a name="nwfelems">Element and attribute overview</a></h2>
>  
>      <p>
> @@ -1629,6 +1741,7 @@
>       The following sections discuss advanced filter configuration
>       topics.
>      </p>
> +
>      <h4><a name="nwfelemsRulesAdvTracking">Connection tracking</a></h4>
>      <p>
>       The network filtering subsystem (on Linux) makes use of the connection
> @@ -2161,36 +2274,6 @@
>       filtering subsystem.
>      </p>
>  
> -    <h3><a name="nwflimitsIP">IP Address Detection</a></h3>
> -     <p>
> -       In case a network filter references the variable
> -       <i>IP</i> and no variable was defined in any higher layer
> -       references to the filter, IP address detection will automatically
> -       be started when the filter is to be instantiated (VM start, interface
> -       hotplug event). Only IPv4
> -       addresses can be detected and only a single IP address
> -       legitimately in use by a VM on a single interface will be detected.
> -       In case a VM was to use multiple IP address on a single interface
> -       (IP aliasing),
> -       the IP addresses would have to be provided explicitly either
> -       in the network filter itself or as variables used in attributes'
> -       values. These
> -       variables must then be defined in a higher level reference to the filter
> -       and each assigned the value of the IP address that the VM is expected
> -       to be using.
> -       Different IP addresses in use by multiple interfaces of a VM
> -       (one IP address each) will be independently detected.
> -       <br/><br/>
> -       Once a VM's IP address has been detected, its IP network traffic
> -       may be locked to that address, if for example IP address spoofing
> -       is prevented by one of its filters. In that case the user of the VM
> -       will not be able to change the IP address on the interface inside
> -       the VM, which would be considered IP address spoofing.
> -       <br/><br/>
> -       In case a VM is resumed after suspension or migrated, IP address
> -       detection will be restarted.
> -     </p>
> -
>      <h3><a name="nwflimitsmigr">VM Migration</a></h3>
>       <p>
>        VM migration is only supported if the whole filter tree
> Index: libvirt-acl/src/Makefile.am
> ===================================================================
> --- libvirt-acl.orig/src/Makefile.am
> +++ libvirt-acl/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				\
> Index: libvirt-acl/src/nwfilter/nwfilter_dhcpsnoop.c
> ===================================================================
> --- /dev/null
> +++ libvirt-acl/src/nwfilter/nwfilter_dhcpsnoop.c
> @@ -0,0 +1,2025 @@
> +/*
> + * nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM
> + *                       on an interface
> + *
> + * Copyright (C) 2011,2012 IBM Corp.
> + *
> + * Authors:
> + *    David L Stevens <dlstevens us ibm com>
> + *    Stefan Berger <stefanb linux vnet ibm com>
> + *
> + * 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
> + *
> + * Based in part on work by Stefan Berger <stefanb us ibm com>
> + */
> +
> +/*
> + * Note about testing:
> + *   On the host run in a shell:
> + *      while :; do kill -SIGHUP `pidof libvirtd` ; echo "HUP $RANDOM"; sleep 20; done
> + *
> + *   Inside a couple of VMs that for example use the 'clean-traffic' filter:
> + *      while :; do kill -SIGTERM `pidof dhclient`; dhclient eth0 ; ifconfig eth0; done
> + *
> + *   On the host check the lease file and that it's periodically shortened:
> + *      cat /var/run/libvirt/network/nwfilter.leases ; date +%s
> + *
> + *   On the host also check that the ebtables rules 'look' ok:
> + *      ebtables -t nat -L
> + */
> +#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 "viratomic.h"
> +#include "threadpool.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"
> +
> +struct virNWFilterSnoopState {
> +    /* lease file */
> +    int                  leaseFD;
> +    virAtomicInt         nLeases; /* number of active leases */
> +    virAtomicInt         wLeases; /* number of written leases */
> +    virAtomicInt         nThreads; /* number of running threads */
> +    /* thread management */
> +    virHashTablePtr      snoopReqs;
> +    virHashTablePtr      ifnameToKey;
> +    virMutex             snoopLock;  /* protects SnoopReqs and IfNameToKey */
> +    virHashTablePtr      active;
> +    virMutex             activeLock; /* protects Active */
> +};
> +
> +#define virNWFilterSnoopLock() \
> +    { virMutexLock(&virNWFilterSnoopState.snoopLock); }
> +#define virNWFilterSnoopUnlock() \
> +    { virMutexUnlock(&virNWFilterSnoopState.snoopLock); }
> +#define virNWFilterSnoopActiveLock() \
> +    { virMutexLock(&virNWFilterSnoopState.activeLock); }
> +#define virNWFilterSnoopActiveUnlock() \
> +    { virMutexUnlock(&virNWFilterSnoopState.activeLock); }
> +
> +#define VIR_IFKEY_LEN   ((VIR_UUID_STRING_BUFLEN) + (VIR_MAC_STRING_BUFLEN))
> +
> +typedef struct _virNWFilterSnoopReq virNWFilterSnoopReq;
> +typedef virNWFilterSnoopReq *virNWFilterSnoopReqPtr;
> +
> +typedef struct _virNWFilterSnoopIPLease virNWFilterSnoopIPLease;
> +typedef virNWFilterSnoopIPLease *virNWFilterSnoopIPLeasePtr;
> +
> +typedef enum {
> +    THREAD_STATUS_NONE,
> +    THREAD_STATUS_OK,
> +    THREAD_STATUS_FAIL,
> +} virNWFilterSnoopThreadStatus;
> +
> +struct _virNWFilterSnoopReq {
> +    /*
> +     * reference counter: while the req is on the
> +     * publicSnoopReqs hash, the refctr may only
> +     * be modified with the SnoopLock held
> +     */
> +    unsigned int                         refctr;
> +
> +    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 */
> +    virNWFilterSnoopIPLeasePtr           start;
> +    virNWFilterSnoopIPLeasePtr           end;
> +    char                                *threadkey;
> +
> +    virNWFilterSnoopThreadStatus         threadStatus;
> +    virCond                              threadStatusCond;
> +
> +    int                                  jobCompletionStatus;
> +    /* the number of submitted jobs in the worker's queue */
> +    virAtomicInt                         queueSize;
> +    /*
> +     * protect those members that can change while the
> +     * req is on the public SnoopReq hash and
> +     * at least one reference is held:
> +     * - ifname
> +     * - threadkey
> +     * - start
> +     * - end
> +     * - a lease while it is on the list
> +     * - threadStatus
> +     * (for refctr, see above)
> +     */
> +    virMutex                             lock;
> +};
> +
> +/*
> + * Note about lock-order:
> + * 1st: virNWFilterSnoopLock()
> + * 2nd: virNWFilterSnoopReqLock(req)
> + *
> + * Rationale: Former protects the SnoopReqs hash, latter its contents
> + */
> +
> +struct _virNWFilterSnoopIPLease {
> +    uint32_t                   ipAddress;
> +    uint32_t                   ipServer;
> +    virNWFilterSnoopReqPtr     snoopReq;
> +    unsigned int               timeout;
> +    /* timer list */
> +    virNWFilterSnoopIPLeasePtr prev;
> +    virNWFilterSnoopIPLeasePtr next;
> +};
> +
> +
> +typedef unsigned char Eaddr[6];
> +
> +typedef struct _virNWFilterSnoopEthHdr virNWFilterSnoopEthHdr;
> +typedef virNWFilterSnoopEthHdr *virNWFilterSnoopEthHdrPtr;
> +
> +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
> +
> +
> +typedef struct _virNWFilterSnoopDHCPHdr virNWFilterSnoopDHCPHdr;
> +typedef virNWFilterSnoopDHCPHdr *virNWFilterSnoopDHCPHdrPtr;
> +
> +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
> +
> +#define PCAP_PBUFSIZE              576 /* >= IP/TCP/DHCP headers */
> +#define PCAP_POLL_INTERVAL_MS      10*1000 /* 10 secs */
> +#define PCAP_READ_MAXERRS          25 /* retries on failing device */
> +
> +typedef struct _virNWFilterDHCPDecodeJob virNWFilterDHCPDecodeJob;
> +typedef virNWFilterDHCPDecodeJob *virNWFilterDHCPDecodeJobPtr;
> +
> +struct _virNWFilterDHCPDecodeJob {
> +    unsigned char packet[PCAP_PBUFSIZE];
> +    int caplen;
> +    bool fromVM;
> +};
> +
> +#define DHCP_PKT_RATE          10 /* pkts/sec */
> +#define DHCP_PKT_BURST         50 /* pkts/sec */
> +#define DHCP_BURST_INTERVAL_S  10 /* sec */
> +
> +#define MAX_QUEUED_JOBS        (DHCP_PKT_BURST + 2 * DHCP_PKT_RATE)
> +
> +typedef struct _virNWFilterSnoopRateLimitConf virNWFilterSnoopRateLimitConf;
> +typedef virNWFilterSnoopRateLimitConf *virNWFilterSnoopRateLimitConfPtr;
> +
> +struct _virNWFilterSnoopRateLimitConf {
> +    time_t prev;
> +    unsigned int pkt_ctr;
> +    time_t burst;
> +    const unsigned int rate;
> +    const unsigned int burst_rate;
> +    const unsigned int burst_interval;
> +};
> +
> +/* local function prototypes */
> +static int virNWFilterSnoopReqLeaseDel(virNWFilterSnoopReqPtr req,
> +                                       uint32_t ipaddr, bool update_leasefile);
> +
> +static void virNWFilterSnoopReqLock(virNWFilterSnoopReqPtr req);
> +static void virNWFilterSnoopReqUnlock(virNWFilterSnoopReqPtr req);
> +
> +static void virNWFilterSnoopLeaseFileLoad(void);
> +static void virNWFilterSnoopLeaseFileSave(virNWFilterSnoopIPLeasePtr ipl);
> +
> +/* local variables */
> +static struct virNWFilterSnoopState virNWFilterSnoopState = {
> +    .leaseFD = -1,
> +};
> +
> +static const unsigned char dhcp_magic[4] = { 99, 130, 83, 99 };
> +
> +
> +static char *
> +virNWFilterSnoopActivate(virThreadPtr thread)
> +{
> +    char *key;
> +    unsigned long threadID = (unsigned long int)thread->thread;
> +
> +    if (virAsprintf(&key, "0x%0*lX", (int)sizeof(threadID)*2, threadID) < 0) {
> +        virReportOOMError();
> +        return NULL;
> +    }
> +
> +    virNWFilterSnoopActiveLock();
> +
> +    if (virHashAddEntry(virNWFilterSnoopState.active, key, (void *)0x1) < 0) {
> +        VIR_FREE(key);
> +    }
> +
> +    virNWFilterSnoopActiveUnlock();
> +
> +    return key;
> +}
> +
> +static void
> +virNWFilterSnoopCancel(char **threadKey)
> +{
> +    if (*threadKey == NULL)
> +        return;
> +
> +    virNWFilterSnoopActiveLock();
> +
> +    (void) virHashRemoveEntry(virNWFilterSnoopState.active, *threadKey);
> +    VIR_FREE(*threadKey);
> +
> +    virNWFilterSnoopActiveUnlock();
> +}
> +
> +static bool
> +virNWFilterSnoopIsActive(char *threadKey)
> +{
> +    void *entry;
> +
> +    if (threadKey == NULL)
> +        return 0;
> +
> +    virNWFilterSnoopActiveLock();
> +
> +    entry = virHashLookup(virNWFilterSnoopState.active, threadKey);
> +
> +    virNWFilterSnoopActiveUnlock();
> +
> +    return entry != NULL;
> +}
> +
> +/*
> + * virNWFilterSnoopListAdd - add an IP lease to a list
> + */
> +static void
> +virNWFilterSnoopListAdd(virNWFilterSnoopIPLeasePtr plnew,
> +                        virNWFilterSnoopIPLeasePtr *start,
> +                        virNWFilterSnoopIPLeasePtr *end)
> +{
> +    virNWFilterSnoopIPLeasePtr 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;
> +}
> +
> +/*
> + * virNWFilterSnoopListDel - remove an IP lease from a list
> + */
> +static void
> +virNWFilterSnoopListDel(virNWFilterSnoopIPLeasePtr ipl,
> +                        virNWFilterSnoopIPLeasePtr *start,
> +                        virNWFilterSnoopIPLeasePtr *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;

 Please use NULL to set pointers to zero

> +}
> +
> +/*
> + * virNWFilterSnoopLeaseTimerAdd - add an IP lease to the timer list
> + */
> +static void
> +virNWFilterSnoopIPLeaseTimerAdd(virNWFilterSnoopIPLeasePtr plnew)
> +{
> +    virNWFilterSnoopReqPtr req = plnew->snoopReq;
> +
> +    /* protect req->start / req->end */
> +    virNWFilterSnoopReqLock(req);
> +
> +    virNWFilterSnoopListAdd(plnew, &req->start, &req->end);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +}
> +
> +/*
> + * virNWFilterSnoopLeaseTimerDel - remove an IP lease from the timer list
> + */
> +static void
> +virNWFilterSnoopIPLeaseTimerDel(virNWFilterSnoopIPLeasePtr ipl)
> +{
> +    virNWFilterSnoopReqPtr req = ipl->snoopReq;
> +
> +    /* protect req->start / req->end */
> +    virNWFilterSnoopReqLock(req);
> +
> +    virNWFilterSnoopListDel(ipl, &req->start, &req->end);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    ipl->timeout = 0;
> +}
> +
> +/*
> + * virNWFilterSnoopInstallRule - install rule for a lease
> + *
> + * @instantiate: when calling this function in a loop, indicate
> + *               the last call with 'true' here so that the
> + *               rules all get instantiated
> + *               Always calling this with 'true' is fine, but less
> + *               efficient.
> + */
> +static int
> +virNWFilterSnoopIPLeaseInstallRule(virNWFilterSnoopIPLeasePtr ipl,
> +                                   bool instantiate)
> +{
> +    char ipbuf[INET_ADDRSTRLEN];
> +    int rc = -1;
> +    virNWFilterVarValuePtr ipVar;
> +    virNWFilterSnoopReqPtr req;
> +
> +    if (!inet_ntop(AF_INET, &ipl->ipAddress, ipbuf, sizeof(ipbuf))) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("%s: inet_ntop failed "
> +                                 " (0x%08X)"),
> +                               __func__, ipl->ipAddress);
> +        return -1;
> +    }
> +    ipVar = virNWFilterVarValueCreateSimpleCopyValue(ipbuf);
> +    if (!ipVar) {
> +        virReportOOMError();
> +        return -1;
> +    }
> +    if (virNWFilterHashTablePut(ipl->snoopReq->vars, NWFILTER_VARNAME_IP,
> +                                ipVar, 1) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Could not add variable \""
> +                                 NWFILTER_VARNAME_IP "\" to hashmap"));
> +        virNWFilterVarValueFree(ipVar);
> +        return -1;
> +    }
> +
> +    if (!instantiate)
> +        return 0;
> +
> +    /* instantiate the filters */
> +    req = ipl->snoopReq;
> +
> +    /* protect req->ifname */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (req->ifname)
> +        rc = virNWFilterInstantiateFilterLate(NULL,
> +                                              req->ifname,
> +                                              req->ifindex,
> +                                              req->linkdev,
> +                                              req->nettype,
> +                                              req->macaddr,
> +                                              req->filtername,
> +                                              req->vars,
> +                                              req->driver);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    return rc;
> +}
> +
> +/*
> + * virNWFilterSnoopIPLeaseUpdate - update the timeout on an IP lease
> + */
> +static void
> +virNWFilterSnoopIPLeaseUpdate(virNWFilterSnoopIPLeasePtr ipl, time_t timeout)
> +{
> +    if (timeout < ipl->timeout)
> +        return;  /* no take-backs */
> +
> +    virNWFilterSnoopIPLeaseTimerDel(ipl);
> +    ipl->timeout = timeout;
> +    virNWFilterSnoopIPLeaseTimerAdd(ipl);
> +}
> +
> +/*
> + * virNWFilterSnoopGetByIP - lookup IP lease by IP address
> + */
> +static virNWFilterSnoopIPLeasePtr
> +virNWFilterSnoopIPLeaseGetByIP(virNWFilterSnoopIPLeasePtr start,
> +                               uint32_t ipaddr)
> +{
> +    virNWFilterSnoopIPLeasePtr pl;
> +
> +    for (pl = start; pl && pl->ipAddress != ipaddr; pl = pl->next)
> +        /* empty */ ;
> +    return pl;
> +}
> +
> +/*
> + * virNWFilterSnoopReqLeaseTimerRun - run the IP lease timeout list
> + */
> +static unsigned int
> +virNWFilterSnoopReqLeaseTimerRun(virNWFilterSnoopReqPtr req)
> +{
> +    time_t now = time(0);
> +
> +    /* protect req->start */
> +    virNWFilterSnoopReqLock(req);
> +
> +    while (req->start && req->start->timeout <= now)
> +        virNWFilterSnoopReqLeaseDel(req, req->start->ipAddress, true);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    return 0;
> +}
> +
> +/*
> + * Get a reference to the given Snoop request
> + */
> +static void
> +virNWFilterSnoopReqGet(virNWFilterSnoopReqPtr req)
> +{
> +    req->refctr++;

  Sounds racy I would lock here, unless this is expected to be called
  while already locked (in which case should be documented, but why
  make a function for it then ?)

> +}
> +
> +/*
> + * Create a new Snoop request. Initialize it with the given
> + * interface key. The caller must release the request with a call
> + * to virNWFilerSnoopReqPut(req).
> + */
> +static virNWFilterSnoopReqPtr
> +virNWFilterSnoopReqNew(const char *ifkey)
> +{
> +    virNWFilterSnoopReqPtr req;
> +
> +    if (ifkey == NULL || strlen(ifkey) != VIR_IFKEY_LEN - 1) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterSnoopReqNew called with invalid "
> +                                 "key \"%s\" (%u)"),
> +                               ifkey ? ifkey : "",
> +                               (unsigned int)strlen(ifkey));
> +        return NULL;
> +    }
> +
> +    if (VIR_ALLOC(req) < 0) {
> +        virReportOOMError();
> +        return NULL;
> +    }
> +
> +    virNWFilterSnoopReqGet(req);
> +
> +    req->threadStatus = THREAD_STATUS_NONE;
> +
> +    if (virMutexInitRecursive(&req->lock) < 0 ||
> +        virStrcpyStatic(req->ifkey, ifkey) == NULL ||
> +        virCondInit(&req->threadStatusCond) < 0 ||
> +        virAtomicIntInit(&req->queueSize) < 0)
> +        VIR_FREE(req);
> +
> +    return req;
> +}
> +
> +/*
> + * Free a snoop request unless it is still referenced.
> + * All its associated leases are also freed.
> + * The lease file is NOT rewritten.
> + */
> +static void
> +virNWFilterSnoopReqFree(virNWFilterSnoopReqPtr req)
> +{
> +    virNWFilterSnoopIPLeasePtr ipl;
> +
> +    if (!req)
> +        return;
> +
> +    if (req->refctr != 0)
> +        return;
> +
> +    /* free all leases */
> +    for (ipl = req->start; ipl; ipl = req->start)
> +        virNWFilterSnoopReqLeaseDel(req, ipl->ipAddress, false);
> +
> +    /* free all req data */
> +    VIR_FREE(req->ifname);
> +    VIR_FREE(req->linkdev);
> +    VIR_FREE(req->filtername);
> +    virNWFilterHashTableFree(req->vars);
> +
> +    VIR_FREE(req);
> +}
> +
> +/*
> + * Lock a Snoop request 'req'
> + */
> +static void
> +virNWFilterSnoopReqLock(virNWFilterSnoopReqPtr req)
> +{
> +    virMutexLock(&req->lock);
> +}
> +
> +/*
> + * Unlock a Snoop request 'req'
> + */
> +static void
> +virNWFilterSnoopReqUnlock(virNWFilterSnoopReqPtr req)
> +{
> +    virMutexUnlock(&req->lock);
> +}
> +
> +/*
> + * virNWFilterSnoopReqRelease - hash table free function to kill a request
> + */
> +static void
> +virNWFilterSnoopReqRelease(void *req0, const void *name ATTRIBUTE_UNUSED)
> +{
> +    virNWFilterSnoopReqPtr req = (virNWFilterSnoopReqPtr) req0;
> +
> +    if (!req)
> +        return;
> +
> +    /* protect req->threadkey */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (req->threadkey)
> +        virNWFilterSnoopCancel(&req->threadkey);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    virNWFilterSnoopReqFree(req);
> +}
> +
> +/*
> + * virNWFilterSnoopReqGetByIFKey
> + *
> + * Get a Snoop request given an interface key; caller must release
> + * the Snoop request with a call to virNWFilterSnoopReqPut()
> + */
> +static virNWFilterSnoopReqPtr
> +virNWFilterSnoopReqGetByIFKey(const char *ifkey)
> +{
> +    virNWFilterSnoopReqPtr req;
> +
> +    virNWFilterSnoopLock();
> +
> +    req = virHashLookup(virNWFilterSnoopState.snoopReqs, ifkey);
> +    if (req)
> +        virNWFilterSnoopReqGet(req);
> +
> +    virNWFilterSnoopUnlock();
> +
> +    return req;
> +}
> +
> +/*
> + * Drop the reference to the Snoop request. Don't use the req
> + * after this call.
> + */
> +static void
> +virNWFilterSnoopReqPut(virNWFilterSnoopReqPtr req)
> +{
> +    if (!req)
> +        return;
> +
> +    virNWFilterSnoopLock()
> +
> +    req->refctr--;
> +
> +    if (req->refctr == 0) {
> +        /*
> +         * delete the request:
> +         * - if we don't find req on the global list anymore
> +         *   (this happens during SIGHUP)
> +         * we would keep the request:
> +         * - if we still have a valid lease, keep the req for restarts
> +         */
> +        if (virHashLookup(virNWFilterSnoopState.snoopReqs, req->ifkey) != req) {
> +            virNWFilterSnoopReqRelease(req, NULL);
> +        } else if (!req->start || req->start->timeout < time(0)) {
> +            (void) virHashRemoveEntry(virNWFilterSnoopState.snoopReqs,
> +                                      req->ifkey);
> +        }
> +    }
> +
> +    virNWFilterSnoopUnlock();
> +}
> +
> +/*
> + * virNWFilterSnoopReqLeaseAdd - create or update an IP lease
> + */
> +static int
> +virNWFilterSnoopReqLeaseAdd(virNWFilterSnoopReqPtr req,
> +                            virNWFilterSnoopIPLeasePtr plnew,
> +                            bool update_leasefile)
> +{
> +    virNWFilterSnoopIPLeasePtr pl;
> +
> +    plnew->snoopReq = req;
> +
> +    /* protect req->start and the lease */
> +    virNWFilterSnoopReqLock(req);
> +
> +    pl = virNWFilterSnoopIPLeaseGetByIP(req->start, plnew->ipAddress);
> +
> +    if (pl) {
> +        virNWFilterSnoopIPLeaseUpdate(pl, plnew->timeout);
> +
> +        virNWFilterSnoopReqUnlock(req);
> +
> +        goto exit;
> +    }
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    /* 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 0;    /* silently ignore multiple addresses */
> +
> +    if (VIR_ALLOC(pl) < 0) {
> +        virReportOOMError();
> +        return -1;
> +    }
> +    *pl = *plnew;
> +
> +    /* protect req->threadkey */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (req->threadkey && virNWFilterSnoopIPLeaseInstallRule(pl, true) < 0) {
> +        virNWFilterSnoopReqUnlock(req);
> +        VIR_FREE(pl);
> +        return -1;
> +    }
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    /* put the lease on the req's list */
> +    virNWFilterSnoopIPLeaseTimerAdd(pl);
> +
> +    virAtomicIntAdd(&virNWFilterSnoopState.nLeases, 1);
> +
> +exit:
> +    if (update_leasefile)
> +        virNWFilterSnoopLeaseFileSave(pl);
> +
> +    return 0;
> +}
> +
> +/*
> + * Restore a Snoop request -- walk its list of leases
> + * and re-build the filtering rules with them
> + */
> +static int
> +virNWFilterSnoopReqRestore(virNWFilterSnoopReqPtr req)
> +{
> +    int ret = 0;
> +    virNWFilterSnoopIPLeasePtr ipl;
> +
> +    /* protect req->start */
> +    virNWFilterSnoopReqLock(req);
> +
> +    for (ipl = req->start; ipl; ipl = ipl->next) {
> +        /* instantiate the rules at the last lease */
> +        bool is_last = (ipl->next == NULL);
> +        if (virNWFilterSnoopIPLeaseInstallRule(ipl, is_last) < 0) {
> +            ret = -1;
> +            break;
> +        }
> +    }
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    return ret;
> +}
> +
> +/*
> + * virNWFilterSnoopReqLeaseDel - delete an IP lease
> + *
> + * @update_leasefile: set to 'true' if the lease expired or the lease
> + *                    was returned to the DHCP server and therefore
> + *                    this has to be noted in the lease file.
> + *                    set to 'false' for any other reason such as for
> + *                    example when calling only to free the lease's
> + *                    memory or when calling this function while reading
> + *                    leases from the file.
> + *
> + * Returns 0 on success, -1 if the instantiation of the rules failed
> + */
> +static int
> +virNWFilterSnoopReqLeaseDel(virNWFilterSnoopReqPtr req,
> +                            uint32_t ipaddr, bool update_leasefile)
> +{
> +    int ret = 0;
> +    virNWFilterSnoopIPLeasePtr ipl;
> +
> +    /* protect req->start & req->ifname */
> +    virNWFilterSnoopReqLock(req);
> +
> +    ipl = virNWFilterSnoopIPLeaseGetByIP(req->start, ipaddr);
> +    if (ipl == NULL)
> +        goto lease_not_found;
> +
> +    virNWFilterSnoopIPLeaseTimerDel(ipl);
> +
> +    if (update_leasefile) {
> +        const virNWFilterVarValuePtr dhcpsrvrs =
> +            virHashLookup(req->vars->hashTable, NWFILTER_VARNAME_DHCPSERVER);
> +
> +        virNWFilterSnoopLeaseFileSave(ipl);
> +
> +        /*
> +         * for multiple address support, this needs to remove those rules
> +         * referencing "IP" with ipl's ip value.
> +         */
> +        if (req->techdriver &&
> +            req->techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr,
> +                                                dhcpsrvrs, false) < 0) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("virNWFilterSnoopListDel failed"));
> +            ret = -1;
> +        }
> +
> +    }
> +    VIR_FREE(ipl);
> +
> +    virAtomicIntSub(&virNWFilterSnoopState.nLeases, 1);
> +
> +lease_not_found:
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    return ret;
> +}
> +
> +static int
> +virNWFilterSnoopDHCPGetOpt(virNWFilterSnoopDHCPHdrPtr 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;
> +}
> +
> +/*
> + * Decode the DHCP options
> + *
> + * Returns 0 in case of full success.
> + * Returns -2 in case of some error with the packet.
> + * Returns -1 in case of error with the installation of rules
> + */
> +static int
> +virNWFilterSnoopDHCPDecode(virNWFilterSnoopReqPtr req,
> +                           virNWFilterSnoopEthHdrPtr pep,
> +                           int len, bool fromVM)
> +{
> +    struct iphdr *pip;
> +    struct udphdr *pup;
> +    virNWFilterSnoopDHCPHdrPtr pd;
> +    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(virNWFilterSnoopEthHdr, eh_data);
> +        break;
> +    case ETHERTYPE_VLAN:
> +        if (ntohs(pep->ehv_type) != ETHERTYPE_IP)
> +            return -2;
> +        pip = (struct iphdr *) pep->ehv_data;
> +        len -= offsetof(virNWFilterSnoopEthHdr, ehv_data);
> +        break;
> +    default:
> +        return -2;
> +    }
> +
> +    if (len < 0)
> +        return -2;
> +
> +    pip = (struct iphdr *) pep->eh_data;
> +    len -= sizeof(*pep);
> +    if (len < 0)
> +        return -2;
> +
> +    /* check for spoofed or packets not destined for this VM */
> +    if (fromVM) {
> +        if (memcmp(req->macaddr, pep->eh_src, 6) != 0)
> +            return -2;
> +    } else {
> +        /*
> +         * packets not destined for us can occurr while the bridge is
> +         * learning the MAC addresses on ports
> +         */
> +        if (memcmp(req->macaddr, pep->eh_dst, 6) != 0)
> +            return -2;
> +    }
> +
> +    pup = (struct udphdr *) ((char *) pip + (pip->ihl << 2));
> +    len -= pip->ihl << 2;
> +    if (len < 0)
> +        return -2;
> +
> +    pd = (virNWFilterSnoopDHCPHdrPtr) ((char *) pup + sizeof(*pup));
> +    len -= sizeof(*pup);
> +    if (len < 0)
> +        return -2;                 /* invalid packet length */
> +
> +    if (virNWFilterSnoopDHCPGetOpt(pd, len, &mtype, &leasetime) < 0)
> +        return -2;
> +
> +    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;
> +
> +    /* check that the type of message comes from the right direction */
> +    switch (mtype) {
> +        case DHCPACK:
> +        case DHCPDECLINE:
> +            if (fromVM)
> +                return -2;
> +            break;
> +        case DHCPRELEASE:
> +            if (!fromVM)
> +                return -2;
> +            break;
> +        default:
> +            break;
> +    }
> +
> +    switch (mtype) {
> +        case DHCPACK:
> +            if (virNWFilterSnoopReqLeaseAdd(req, &ipl, true) < 0)
> +                return -1;
> +            break;
> +        case DHCPDECLINE:
> +        case DHCPRELEASE:
> +            if (virNWFilterSnoopReqLeaseDel(req, ipl.ipAddress, true) < 0)
> +                return -1;
> +            break;
> +        default:
> +            return -2;
> +            break;
> +    }
> +    return 0;
> +}
> +
> +static pcap_t *
> +virNWFilterSnoopDHCPOpen(const char *ifname, const char *filter,
> +                         pcap_direction_t dir)
> +{
> +    pcap_t *handle = NULL;
> +    struct bpf_program fp;
> +    char pcap_errbuf[PCAP_ERRBUF_SIZE];
> +
> +    handle = pcap_open_live(ifname, PCAP_PBUFSIZE, 0, PCAP_POLL_INTERVAL_MS,
> +                            pcap_errbuf);
> +
> +    if (handle == NULL) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_open_live: %s"), pcap_errbuf);
> +        goto cleanup;
> +    }
> +
> +    if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_compile: %s"), pcap_geterr(handle));
> +        goto cleanup;
> +    }
> +
> +    if (pcap_setfilter(handle, &fp) != 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_setfilter: %s"), pcap_geterr(handle));
> +        goto cleanup_freecode;
> +    }
> +
> +    if (pcap_setdirection(handle, dir) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("pcap_setdirection: %s"),
> +                               pcap_geterr(handle));
> +        goto cleanup_freecode;
> +    }
> +
> +    pcap_freecode(&fp);
> +
> +    return handle;
> +
> +cleanup_freecode:
> +    pcap_freecode(&fp);
> +cleanup:
> +    pcap_close(handle);
> +
> +    return NULL;
> +}
> +
> +/*
> + * Worker function to decode the DHCP message and with that
> + * also do the time-consuming work of instantiating the filters
> + */
> +static void virNWFilterDHCPDecodeWorker(void *jobdata, void *opaque)
> +{
> +    virNWFilterSnoopReqPtr req = opaque;
> +    virNWFilterDHCPDecodeJobPtr job = jobdata;
> +    virNWFilterSnoopEthHdrPtr packet = (virNWFilterSnoopEthHdrPtr)job->packet;
> +
> +    if (virNWFilterSnoopDHCPDecode(req, packet,
> +                                   job->caplen, job->fromVM) == -1) {
> +        req->jobCompletionStatus = -1;
> +
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Instantiation of rules failed on "
> +                                 "interface '%s'"), req->ifname);
> +    }
> +    VIR_FREE(job);
> +    virAtomicIntSub(&req->queueSize, 1);
> +}
> +
> +/*
> + * Submit a job to the worker thread doing the time-consuming work...
> + */
> +static int
> +virNWFilterSnoopDHCPDecodeJobSubmit(virThreadPoolPtr pool,
> +                                    virNWFilterSnoopReqPtr req,
> +                                    virNWFilterSnoopEthHdrPtr pep,
> +                                    int len, pcap_direction_t dir)
> +{
> +    virNWFilterDHCPDecodeJobPtr job;
> +    int ret;
> +
> +    if (len <= 0 || len > sizeof(job->packet))
> +        return 0;
> +
> +    if (VIR_ALLOC(job) < 0) {
> +        virReportOOMError();
> +        return -1;
> +    }
> +
> +    memcpy(job->packet, pep, len);
> +    job->caplen = len;
> +    job->fromVM = (dir == PCAP_D_IN);
> +
> +    ret = virThreadPoolSendJob(pool, 0, job);
> +
> +    if (ret == 0)
> +        virAtomicIntAdd(&req->queueSize, 1);
> +
> +    return ret;
> +}
> +
> +/*
> + * virNWFilterSnoopRateLimit -- limit the rate of jobs submitted to the
> + *                              worker thread
> + *
> + * Help defend the worker thread from being flooded with likely bogus packets
> + * sent by the VM.
> + *
> + * rl: The state of the rate limiter
> + *
> + * Returns the delta of packets compared to the rate, i.e. if the rate
> + * is 4 (pkts/s) and we now have received 5 within a second, it would
> + * return 1. If the number of packets is below the rate, it returns 0.
> + */
> +static int
> +virNWFilterSnoopRateLimit(virNWFilterSnoopRateLimitConfPtr rl)
> +{
> +    time_t now = time(0);
> +    int diff;
> +#define IN_BURST(n,b) ((n)-(b) <= 1) /* bursts span 2 discreet seconds */
> +
> +    if (rl->prev != now && !IN_BURST(now, rl->burst)) {
> +        rl->prev = now;
> +        rl->pkt_ctr = 1;
> +    } else {
> +        rl->pkt_ctr++;
> +        if (rl->pkt_ctr >= rl->rate) {
> +            if (IN_BURST(now, rl->burst)) {
> +                /* in a burst */
> +                diff = rl->pkt_ctr - rl->burst_rate;
> +                if (diff > 0)
> +                    return diff;
> +                return 0;
> +            }
> +            if (rl->prev - rl->burst > rl->burst_interval) {
> +                /* this second will start a new burst */
> +                rl->burst = rl->prev;
> +                return 0;
> +            }
> +            /* previous burst is too close */
> +            return rl->pkt_ctr - rl->rate;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +/*
> + * The DHCP snooping thread. It spends most of its time in the pcap
> + * library and if it gets suitable packets, it submits them to the worker
> + * thread for processing.
> + */
> +static void
> +virNWFilterDHCPSnoopThread(void *req0)
> +{
> +    virNWFilterSnoopReqPtr req = req0;
> +    struct pcap_pkthdr *hdr;
> +    virNWFilterSnoopEthHdrPtr packet;
> +    int ifindex = 0;
> +    int errcount = 0;
> +    int tmp = -1, i, rv, n;
> +    char *threadkey = NULL;
> +    virThreadPoolPtr worker = NULL;
> +    time_t last_displayed = 0, last_displayed_queue = 0;
> +    struct {
> +        pcap_t *handle;
> +        pcap_direction_t dir;
> +        const char *filter;
> +    } pcapConf[] = {
> +        {
> +            .dir = PCAP_D_IN, /* from VM */
> +            .filter = "dst port 67 and src port 68",
> +        }, {
> +            .dir = PCAP_D_OUT, /* to VM */
> +            .filter = "src port 67 and dst port 68",
> +        },
> +    };
> +    struct pollfd fds[] = {
> +        {
> +            /* get a POLLERR if interface goes down or disappears */
> +            .events = POLLIN | POLLERR,
> +        }, {
> +            .events = POLLIN | POLLERR,
> +        },
> +    };
> +    virNWFilterSnoopRateLimitConf rateLimit = {
> +        .prev = time(0),
> +        .pkt_ctr = 0,
> +        .burst = 0,
> +        .rate = DHCP_PKT_RATE,
> +        .burst_rate = DHCP_PKT_BURST,
> +        .burst_interval = DHCP_BURST_INTERVAL_S,
> +    };
> +    bool error = false;
> +
> +    /* whoever started us increased the reference counter for the req for us */
> +
> +    /* protect req->ifname & req->threadkey */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (req->ifname && req->threadkey) {
> +        for (i = 0; i < ARRAY_CARDINALITY(pcapConf); i++) {
> +            pcapConf[i].handle =
> +                virNWFilterSnoopDHCPOpen(req->ifname, pcapConf[i].filter,
> +                                         pcapConf[i].dir);
> +            if (!pcapConf[i].handle) {
> +                error = true;
> +                break;
> +            }
> +            fds[i].fd = pcap_fileno(pcapConf[i].handle);
> +        }
> +        tmp = virNetDevGetIndex(req->ifname, &ifindex);
> +        threadkey = strdup(req->threadkey);
> +        worker = virThreadPoolNew(1, 1, 0,
> +                                  virNWFilterDHCPDecodeWorker,
> +                                  req);
> +    }
> +
> +    /* let creator know how well we initialized */
> +    if (error == true || !threadkey || tmp < 0 || !worker ||
> +        ifindex != req->ifindex)
> +        req->threadStatus = THREAD_STATUS_FAIL;
> +    else
> +        req->threadStatus = THREAD_STATUS_OK;
> +
> +    virCondSignal(&req->threadStatusCond);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    if (req->threadStatus != THREAD_STATUS_OK)
> +        goto exit;
> +
> +    while (!error) {
> +        n = poll(fds, ARRAY_CARDINALITY(fds), 1000 * 10);
> +
> +        if (n < 0)
> +            error = true;
> +
> +        virNWFilterSnoopReqLeaseTimerRun(req);
> +
> +        /*
> +         * Check whether we were cancelled or whether
> +         * a previously submitted job failed.
> +         */
> +        if (!virNWFilterSnoopIsActive(threadkey) ||
> +            req->jobCompletionStatus != 0)
> +            goto exit;
> +
> +        for (i = 0; n > 0 && i < ARRAY_CARDINALITY(fds); i++) {
> +            if (!fds[i].revents)
> +                continue;
> +
> +            fds[i].revents = 0;
> +            n--;
> +
> +            rv = pcap_next_ex(pcapConf[i].handle, &hdr,
> +                              (const u_char **)&packet);
> +
> +            if (rv < 0) {
> +                /* error reading from socket */
> +                tmp = -1;
> +
> +                /* protect req->ifname */
> +                virNWFilterSnoopReqLock(req);
> +
> +                if (req->ifname)
> +                    tmp = virNetDevValidateConfig(req->ifname, NULL, ifindex);
> +
> +                virNWFilterSnoopReqUnlock(req);
> +
> +                if (tmp <= 0) {
> +                    error = true;
> +                    break;
> +                }
> +
> +                if (++errcount > PCAP_READ_MAXERRS) {
> +                    pcap_close(pcapConf[i].handle);
> +                    pcapConf[i].handle = NULL;
> +
> +                    /* protect req->ifname */
> +                    virNWFilterSnoopReqLock(req);
> +
> +                    virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                           _("interface '%s' failing; "
> +                                             "reopening"),
> +                                           req->ifname);
> +                    if (req->ifname)
> +                        pcapConf[i].handle =
> +                            virNWFilterSnoopDHCPOpen(req->ifname,
> +                                                     pcapConf[i].filter,
> +                                                     pcapConf[i].dir);
> +
> +                    virNWFilterSnoopReqUnlock(req);
> +
> +                    if (!pcapConf[i].handle) {
> +                        error = true;
> +                        break;
> +                    }
> +                }
> +                continue;
> +            }
> +
> +            errcount = 0;
> +
> +            if (rv) {
> +                int diff;
> +
> +                /* submit packet to worker thread */
> +                if (virAtomicIntRead(&req->queueSize) > MAX_QUEUED_JOBS) {
> +                    if (last_displayed_queue - time(0) > 10) {
> +                        last_displayed_queue = time(0);
> +                        VIR_WARN("Worker thread for interface '%s' has a "
> +                                 "job queue that is too long\n",
> +                                 req->ifname);
> +                    }
> +                    continue;
> +                }
> +
> +                diff = virNWFilterSnoopRateLimit(&rateLimit);
> +                if (diff > 0) {
> +                    if (diff > DHCP_PKT_RATE) {
> +                        /* twice the allowed rate -- serious flooding */
> +                        usleep(1 * 1000); /* 1 ms */
> +                    }
> +                    /* rate-limited warnings */
> +                    if (time(0) - last_displayed > 10) {
> +                         last_displayed = time(0);
> +                         VIR_WARN("Too many DHCP packets on interface '%s'",
> +                                  req->ifname);
> +                    }
> +                    continue;
> +                }
> +
> +                if (virNWFilterSnoopDHCPDecodeJobSubmit(worker, req, packet,
> +                                                        hdr->caplen,
> +                                                        pcapConf[i].dir) < 0) {
> +                    virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                           _("Job submission failed on "
> +                                           "interface '%s'"), req->ifname);
> +                    error = true;
> +                    break;
> +                }
> +            }
> +        } /* for all fds */
> +    } /* while (!error) */
> +
> +    /* protect IfNameToKey */
> +    virNWFilterSnoopLock();
> +
> +    /* protect req->ifname & req->threadkey */
> +    virNWFilterSnoopReqLock(req);
> +
> +    virNWFilterSnoopCancel(&req->threadkey);
> +
> +    (void) virHashRemoveEntry(virNWFilterSnoopState.ifnameToKey, req->ifname);
> +
> +    VIR_FREE(req->ifname);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +    virNWFilterSnoopUnlock();
> +
> +exit:
> +    virThreadPoolFree(worker);
> +
> +    virNWFilterSnoopReqPut(req);
> +
> +    VIR_FREE(threadkey);
> +
> +    for (i = 0; i < ARRAY_CARDINALITY(pcapConf); i++) {
> +        if (pcapConf[i].handle)
> +            pcap_close(pcapConf[i].handle);
> +    }
> +
> +    virAtomicIntSub(&virNWFilterSnoopState.nThreads, 1);
> +
> +    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)
> +{
> +    virNWFilterSnoopReqPtr req;
> +    bool isnewreq;
> +    char ifkey[VIR_IFKEY_LEN];
> +    int tmp;
> +    virThread thread;
> +    virNWFilterVarValuePtr dhcpsrvrs;
> +
> +    virNWFilterSnoopIFKeyFMT(ifkey, vmuuid, macaddr);
> +
> +    req = virNWFilterSnoopReqGetByIFKey(ifkey);
> +    isnewreq = (req == NULL);
> +    if (!isnewreq) {
> +        if (req->threadkey) {
> +            virNWFilterSnoopReqPut(req);
> +            return 0;
> +        }
> +        /* a recycled req may still have filtername and vars */
> +        VIR_FREE(req->filtername);
> +        virNWFilterHashTableFree(req->vars);
> +    } else {
> +        req = virNWFilterSnoopReqNew(ifkey);
> +        if (!req)
> +            return -1;
> +    }
> +
> +    req->driver = driver;
> +    req->techdriver = techdriver;
> +    tmp = virNetDevGetIndex(ifname, &req->ifindex);
> +    req->linkdev = linkdev ? strdup(linkdev) : NULL;
> +    req->nettype = nettype;
> +    req->ifname = strdup(ifname);
> +    memcpy(req->macaddr, macaddr, sizeof(req->macaddr));
> +    req->filtername = strdup(filtername);
> +    req->vars = virNWFilterHashTableCreate(0);
> +
> +    if (!req->ifname || !req->filtername || !req->vars || tmp < 0) {
> +        virReportOOMError();
> +        goto exit_snoopreqput;
> +    }
> +
> +    dhcpsrvrs = virHashLookup(filterparams->hashTable,
> +                              NWFILTER_VARNAME_DHCPSERVER);
> +
> +    if (techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr,
> +                                       dhcpsrvrs, false) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("applyDHCPOnlyRules "
> +                               "failed - spoofing not protected!"));
> +        goto exit_snoopreqput;
> +    }
> +
> +    if (virNWFilterHashTablePutAll(filterparams, req->vars) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq: can't copy variables"
> +                                " on if %s"), ifkey);
> +        goto exit_snoopreqput;
> +    }
> +
> +    virNWFilterSnoopLock();
> +
> +    if (virHashAddEntry(virNWFilterSnoopState.ifnameToKey, ifname,
> +                        req->ifkey) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq ifname map failed"
> +                                 " on interface \"%s\" key \"%s\""), ifname,
> +                               ifkey);
> +        goto exit_snoopunlock;
> +    }
> +
> +    if (isnewreq &&
> +        virHashAddEntry(virNWFilterSnoopState.snoopReqs, ifkey, req) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq req add failed on"
> +                               " interface \"%s\" ifkey \"%s\""), ifname,
> +                               ifkey);
> +        goto exit_rem_ifnametokey;
> +    }
> +
> +    /* prevent thread from holding req */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (virThreadCreate(&thread, false, virNWFilterDHCPSnoopThread,
> +                        req) != 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("virNWFilterDHCPSnoopReq virThreadCreate "
> +                                 "failed on interface '%s'"), ifname);
> +        goto exit_snoopreq_unlock;
> +    }
> +
> +    virAtomicIntAdd(&virNWFilterSnoopState.nThreads, 1);
> +
> +    req->threadkey = virNWFilterSnoopActivate(&thread);
> +    if (!req->threadkey) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Actication of snoop request failed on "
> +                                 "interface '%s'"), req->ifname);
> +        goto exit_snoopreq_unlock;
> +    }
> +
> +    if (virNWFilterSnoopReqRestore(req) < 0) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("Restoring of leases failed on "
> +                               "interface '%s'"), req->ifname);
> +        goto exit_snoop_cancel;
> +    }
> +
> +    /* sync with thread */
> +    if (virCondWait(&req->threadStatusCond, &req->lock) < 0 ||
> +        req->threadStatus != THREAD_STATUS_OK)
> +        goto exit_snoop_cancel;
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    virNWFilterSnoopUnlock();
> +
> +    /* do not 'put' the req -- the thread will do this */
> +
> +    return 0;
> +
> +exit_snoop_cancel:
> +    virNWFilterSnoopCancel(&req->threadkey);
> +exit_snoopreq_unlock:
> +    virNWFilterSnoopReqUnlock(req);
> +exit_rem_ifnametokey:
> +    virHashRemoveEntry(virNWFilterSnoopState.ifnameToKey, ifname);
> +exit_snoopunlock:
> +    virNWFilterSnoopUnlock();
> +exit_snoopreqput:
> +    virNWFilterSnoopReqPut(req);
> +
> +    return -1;
> +}
> +
> +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);
> +}
> +
> +/*
> + * Write a single lease to the given file.
> + *
> + */
> +static int
> +virNWFilterSnoopLeaseFileWrite(int lfd, const char *ifkey,
> +                               virNWFilterSnoopIPLeasePtr ipl)
> +{
> +    char lbuf[256], ipstr[INET_ADDRSTRLEN], dhcpstr[INET_ADDRSTRLEN];
> +    size_t len;
> +
> +    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);
> +
> +    len = strlen(lbuf);
> +
> +    if (safewrite(lfd, lbuf, len) != len) {
> +        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                               _("lease file write failed: %s"),
> +                               strerror(errno));
> +        return -1;
> +    }
> +
> +    (void) fsync(lfd);
> +
> +    return 0;
> +}
> +
> +/*
> + * Append a single lease to the end of the lease file.
> + * To keep a limited number of dead leases, re-read the lease
> + * file if the threshold of active leases versus written ones
> + * exceeds a threshold.
> + */
> +static void
> +virNWFilterSnoopLeaseFileSave(virNWFilterSnoopIPLeasePtr ipl)
> +{
> +    virNWFilterSnoopReqPtr req = ipl->snoopReq;
> +
> +    virNWFilterSnoopLock();
> +
> +    if (virNWFilterSnoopState.leaseFD < 0)
> +        virNWFilterSnoopLeaseFileOpen();
> +    if (virNWFilterSnoopLeaseFileWrite(virNWFilterSnoopState.leaseFD,
> +                                       req->ifkey, ipl) < 0)
> +        goto err_exit;
> +
> +    /* keep dead leases at < ~95% of file size */
> +    if (virAtomicIntAdd(&virNWFilterSnoopState.wLeases, 1) >=
> +        virAtomicIntRead(&virNWFilterSnoopState.nLeases) * 20)
> +        virNWFilterSnoopLeaseFileLoad();   /* load & refresh lease file */
> +
> +err_exit:
> +    virNWFilterSnoopUnlock();
> +}
> +
> +/*
> + * Have requests removed that have no leases.
> + * Remove all expired leases.
> + * Call this function with the SnoopLock held.
> + */
> +static int
> +virNWFilterSnoopPruneIter(const void *payload,
> +                          const void *name ATTRIBUTE_UNUSED,
> +                          const void *data ATTRIBUTE_UNUSED)
> +{
> +    const virNWFilterSnoopReqPtr req = (virNWFilterSnoopReqPtr)payload;
> +    bool del_req;
> +
> +    /* clean up orphaned, expired leases */
> +
> +    /* protect req->threadkey */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (!req->threadkey)
> +        virNWFilterSnoopReqLeaseTimerRun(req);
> +
> +    /*
> +     * have the entry removed if it has no leases and no one holds a ref
> +     */
> +    del_req = ((req->start == NULL) && (req->refctr == 0));
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    return del_req;
> +}
> +
> +/*
> + * Iterator to write all leases of a single request to a file.
> + * Call this function with the SnoopLock held.
> + */
> +static void
> +virNWFilterSnoopSaveIter(void *payload,
> +                         const void *name ATTRIBUTE_UNUSED,
> +                         void *data)
> +{
> +    virNWFilterSnoopReqPtr req = payload;
> +    int  tfd = *(int *)data;
> +    virNWFilterSnoopIPLeasePtr ipl;
> +
> +    /* protect req->start */
> +    virNWFilterSnoopReqLock(req);
> +
> +    for (ipl = req->start; ipl; ipl = ipl->next)
> +        (void) virNWFilterSnoopLeaseFileWrite(tfd, req->ifkey, ipl);
> +
> +    virNWFilterSnoopReqUnlock(req);
> +}
> +
> +/*
> + * Write all valid leases into a temporary file and then
> + * rename the file to the final file.
> + * Call this function with the SnoopLock held.
> + */
> +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) {
> +        /* clean up the requests */
> +        virHashRemoveSet(virNWFilterSnoopState.snoopReqs,
> +                         virNWFilterSnoopPruneIter, NULL);
> +        /* now save them */
> +        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);
> +    }
> +    virAtomicIntSet(&virNWFilterSnoopState.wLeases, 0);
> +    virNWFilterSnoopLeaseFileOpen();
> +}
> +
> +
> +static void
> +virNWFilterSnoopLeaseFileLoad(void)
> +{
> +    char line[256], ifkey[VIR_IFKEY_LEN];
> +    char ipstr[INET_ADDRSTRLEN], srvstr[INET_ADDRSTRLEN];
> +    virNWFilterSnoopIPLease ipl;
> +    virNWFilterSnoopReqPtr req;
> +    time_t now;
> +    FILE *fp;
> +    int ln = 0, tmp;
> +
> +    /* protect the lease file */
> +    virNWFilterSnoopLock();
> +
> +    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 = virNWFilterSnoopReqGetByIFKey(ifkey);
> +        if (!req) {
> +            req = virNWFilterSnoopReqNew(ifkey);
> +            if (!req)
> +               break;
> +
> +            tmp = virHashAddEntry(virNWFilterSnoopState.snoopReqs, ifkey, req);
> +
> +            if (tmp < 0) {
> +                virNWFilterSnoopReqPut(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);
> +            virNWFilterSnoopReqPut(req);
> +            continue;
> +        }
> +        (void) inet_pton(AF_INET, srvstr, &ipl.ipServer);
> +        ipl.snoopReq = req;
> +
> +        if (ipl.timeout)
> +            virNWFilterSnoopReqLeaseAdd(req, &ipl, false);
> +        else
> +            virNWFilterSnoopReqLeaseDel(req, ipl.ipAddress, false);
> +
> +        virNWFilterSnoopReqPut(req);
> +    }
> +
> +    VIR_FORCE_FCLOSE(fp);
> +
> +    virNWFilterSnoopLeaseFileRefresh();
> +
> +    virNWFilterSnoopUnlock();
> +}
> +
> +/*
> + * Wait until all threads have ended.
> + */
> +static void
> +virNWFilterSnoopJoinThreads(void)
> +{
> +    while (virAtomicIntRead(&virNWFilterSnoopState.nThreads) != 0) {
> +        VIR_WARN("Waiting for snooping threads to terminate: %u\n",
> +                 virAtomicIntRead(&virNWFilterSnoopState.nThreads));
> +        usleep(1000 * 1000);
> +    }
> +}
> +
> +/*
> + * Iterator to remove a requests, repeatedly called on one
> + * request after another.
> + * The requests' ifname is freed allowing for an association
> + * of the Snoop request's leases with the same VM under a
> + * different interface name at a later time.
> + */
> +static int
> +virNWFilterSnoopRemAllReqIter(const void *payload,
> +                              const void *name ATTRIBUTE_UNUSED,
> +                              const void *data ATTRIBUTE_UNUSED)
> +{
> +    const virNWFilterSnoopReqPtr req = (virNWFilterSnoopReqPtr)payload;
> +
> +    /* protect req->ifname */
> +    virNWFilterSnoopReqLock(req);
> +
> +    if (req->ifname) {
> +        (void) virHashRemoveEntry(virNWFilterSnoopState.ifnameToKey,
> +                                  req->ifname);
> +
> +        VIR_FREE(req->ifname);
> +    }
> +
> +    virNWFilterSnoopReqUnlock(req);
> +
> +    /* removal will call virNWFilterSnoopCancel() */
> +    return 1;
> +}
> +
> +
> +/*
> + * Terminate all threads; keep the SnoopReqs hash allocated
> + */
> +static void
> +virNWFilterSnoopEndThreads(void)
> +{
> +    virNWFilterSnoopLock();
> +    virHashRemoveSet(virNWFilterSnoopState.snoopReqs,
> +                     virNWFilterSnoopRemAllReqIter,
> +                     NULL);
> +    virNWFilterSnoopUnlock();
> +}
> +
> +int
> +virNWFilterDHCPSnoopInit(void)
> +{
> +    if (virNWFilterSnoopState.snoopReqs)
> +        return 0;
> +
> +    if (virMutexInitRecursive(&virNWFilterSnoopState.snoopLock) < 0 ||
> +        virMutexInit(&virNWFilterSnoopState.activeLock) < 0 ||
> +        virAtomicIntInit(&virNWFilterSnoopState.nLeases) < 0 ||
> +        virAtomicIntInit(&virNWFilterSnoopState.wLeases) < 0 ||
> +        virAtomicIntInit(&virNWFilterSnoopState.nThreads) < 0)
> +        return -1;
> +
> +    virNWFilterSnoopState.ifnameToKey = virHashCreate(0, NULL);
> +    virNWFilterSnoopState.active = virHashCreate(0, NULL);
> +    virNWFilterSnoopState.snoopReqs =
> +        virHashCreate(0, virNWFilterSnoopReqRelease);
> +
> +    if (!virNWFilterSnoopState.ifnameToKey ||
> +        !virNWFilterSnoopState.snoopReqs ||
> +        !virNWFilterSnoopState.active) {
> +        virReportOOMError();
> +        goto err_exit;
> +    }
> +
> +    virNWFilterSnoopLeaseFileLoad();
> +    virNWFilterSnoopLeaseFileOpen();
> +
> +    return 0;
> +
> +err_exit:
> +    virHashFree(virNWFilterSnoopState.ifnameToKey);
> +    virNWFilterSnoopState.ifnameToKey = NULL;
> +
> +    virHashFree(virNWFilterSnoopState.snoopReqs);
> +    virNWFilterSnoopState.snoopReqs = NULL;
> +
> +    virHashFree(virNWFilterSnoopState.active);
> +    virNWFilterSnoopState.active = NULL;
> +
> +    return -1;
> +}
> +
> +void
> +virNWFilterDHCPSnoopEnd(const char *ifname)
> +{
> +    char *ifkey = NULL;
> +
> +    virNWFilterSnoopLock();
> +
> +    if (!virNWFilterSnoopState.snoopReqs)
> +        goto cleanup;
> +
> +    if (ifname) {
> +        ifkey = (char *)virHashLookup(virNWFilterSnoopState.ifnameToKey,
> +                                      ifname);
> +        if (!ifkey) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("ifname \"%s\" not in key map"), ifname);
> +            goto cleanup;
> +        }
> +
> +        (void) virHashRemoveEntry(virNWFilterSnoopState.ifnameToKey, ifname);
> +    }
> +
> +    if (ifkey) {
> +        virNWFilterSnoopReqPtr req;
> +
> +        req = virNWFilterSnoopReqGetByIFKey(ifkey);
> +        if (!req) {
> +            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +                                   _("ifkey \"%s\" has no req"), ifkey);
> +            goto cleanup;
> +        }
> +
> +        /* protect req->ifname & req->threadkey */
> +        virNWFilterSnoopReqLock(req);
> +
> +        /* keep valid lease req; drop interface association */
> +        virNWFilterSnoopCancel(&req->threadkey);
> +
> +        VIR_FREE(req->ifname);
> +
> +        virNWFilterSnoopReqUnlock(req);
> +
> +        virNWFilterSnoopReqPut(req);
> +    } else {                      /* free all of them */
> +        virNWFilterSnoopLeaseFileClose();
> +
> +        virHashRemoveAll(virNWFilterSnoopState.ifnameToKey);
> +
> +        /* tell the threads to terminate */
> +        virNWFilterSnoopEndThreads();
> +
> +        virNWFilterSnoopLeaseFileLoad();
> +    }
> +
> +cleanup:
> +    virNWFilterSnoopUnlock();
> +}
> +
> +void
> +virNWFilterDHCPSnoopShutdown(void)
> +{
> +    virNWFilterSnoopEndThreads();
> +    virNWFilterSnoopJoinThreads();
> +
> +    virNWFilterSnoopLock();
> +
> +    virNWFilterSnoopLeaseFileClose();
> +    virHashFree(virNWFilterSnoopState.ifnameToKey);
> +    virHashFree(virNWFilterSnoopState.snoopReqs);
> +
> +    virNWFilterSnoopUnlock();
> +
> +    virNWFilterSnoopActiveLock();
> +    virHashFree(virNWFilterSnoopState.active);
> +    virNWFilterSnoopActiveUnlock();
> +}
> +
> +#else /* HAVE_LIBPCAP */
> +
> +int
> +virNWFilterDHCPSnoopInit(void)
> +{
> +    return -1;
> +}
> +
> +void
> +virNWFilterDHCPSnoopEnd(const char *ifname ATTRIBUTE_UNUSED)
> +{
> +    return;
> +}
> +
> +void
> +virNWFilterDHCPSnoopShutdown(void)
> +{
> +    return;
> +}
> +
> +int
> +virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver ATTRIBUTE_UNUSED,
> +                        const char *ifname ATTRIBUTE_UNUSED,
> +                        const char *linkdev ATTRIBUTE_UNUSED,
> +                        enum virDomainNetType nettype ATTRIBUTE_UNUSED,
> +                        const unsigned 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;
> +}

  if libpcap wasn't detected shouln't all these dummy functions log an
  error ?

> +#endif /* HAVE_LIBPCAP */
> Index: libvirt-acl/src/nwfilter/nwfilter_dhcpsnoop.h
> ===================================================================
> --- /dev/null
> +++ libvirt-acl/src/nwfilter/nwfilter_dhcpsnoop.h
> @@ -0,0 +1,39 @@
> +/*
> + * 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 us ibm com>
> + */
> +
> +#ifndef __NWFILTER_DHCPSNOOP_H
> +#define __NWFILTER_DHCPSNOOP_H
> +
> +int virNWFilterDHCPSnoopInit(void);
> +void virNWFilterDHCPSnoopShutdown(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 */
> Index: libvirt-acl/src/nwfilter/nwfilter_driver.c
> ===================================================================
> --- libvirt-acl.orig/src/nwfilter/nwfilter_driver.c
> +++ libvirt-acl/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();
> +    virNWFilterDHCPSnoopShutdown();
>      virNWFilterLearnShutdown();
>  
>      return -1;
> @@ -149,6 +153,7 @@ nwfilterDriverReload(void) {
>      conn = virConnectOpen("qemu:///system");
>  
>      if (conn) {
> +        virNWFilterDHCPSnoopEnd(NULL);
>          /* shut down all threads -- they will be restarted if necessary */
>          virNWFilterLearnThreadsTerminate(true);
>  
> @@ -203,6 +208,7 @@ nwfilterDriverShutdown(void) {
>  
>      virNWFilterConfLayerShutdown();
>      virNWFilterTechDriversShutdown();
> +    virNWFilterDHCPSnoopShutdown();
>      virNWFilterLearnShutdown();
>  
>      nwfilterDriverLock(driverState);
> Index: libvirt-acl/src/nwfilter/nwfilter_gentech_driver.c
> ===================================================================
> --- libvirt-acl.orig/src/nwfilter/nwfilter_gentech_driver.c
> +++ libvirt-acl/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"
> @@ -39,8 +40,10 @@
>  #define VIR_FROM_THIS VIR_FROM_NWFILTER
>  
>  
> -#define NWFILTER_STD_VAR_MAC "MAC"
> -#define NWFILTER_STD_VAR_IP  "IP"
> +#define NWFILTER_STD_VAR_MAC NWFILTER_VARNAME_MAC
> +#define NWFILTER_STD_VAR_IP  NWFILTER_VARNAME_IP
> +
> +#define NWFILTER_DFLT_LEARN  "any"
>  
>  static int _virNWFilterTeardownFilter(const char *ifname);
>  
> @@ -662,6 +665,9 @@ virNWFilterInstantiate(const unsigned ch
>      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 ch
>      if (rc < 0)
>          goto err_exit;
>  
> +    lv = virHashLookup(vars->hashTable, NWFILTER_VARNAME_IP_LEARNING);
> +    if (lv)
> +        learning = virNWFilterVarValueGetNthValue(lv, 0);
> +    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 (STRCASEEQ(learning, "none")) {        /* no learning */
> +                reportIP = true;
> +                goto err_unresolvable_vars;
>              }
> -            goto err_exit;
> -        }
> -        goto err_unresolvable_vars;
> +            if (STRCASEEQ(learning, "dhcp")) {
> +                rc = virNWFilterDHCPSnoopReq(techdriver, ifname, linkdev,
> +                                             nettype, vmuuid, macaddr,
> +                                             filter->name, vars, driver);
> +                goto err_exit;
> +            } else if (STRCASEEQ(learning, "any")) {
> +                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 *i
>          return -1;
>      }
>  
> +    virNWFilterDHCPSnoopEnd(ifname);
> +
>      virNWFilterTerminateLearnReq(ifname);
>  
>      if (virNWFilterLockIface(ifname) < 0)
> Index: libvirt-acl/src/conf/nwfilter_params.h
> ===================================================================
> --- libvirt-acl.orig/src/conf/nwfilter_params.h
> +++ libvirt-acl/src/conf/nwfilter_params.h
> @@ -91,6 +91,11 @@ int virNWFilterHashTablePutAll(virNWFilt
>  # define VALID_VARVALUE \
>    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:"
>  
> +# define NWFILTER_VARNAME_IP "IP"
> +# define NWFILTER_VARNAME_MAC "MAC"
> +# define NWFILTER_VARNAME_IP_LEARNING "ip_learning"
> +# define NWFILTER_VARNAME_DHCPSERVER "DHCPSERVER"
> +
>  enum virNWFilterVarAccessType {
>      VIR_NWFILTER_VAR_ACCESS_ELEMENT = 0,
>      VIR_NWFILTER_VAR_ACCESS_ITERATOR = 1,

  A fairly big patch ! I have tried to really read everything, but
finding locking issues, especially when there is multiple layers
of locks is nearly impossible by review.
  I think the best is to get this running and tested as much as possible
before the next release,

  ACK

Daniel

-- 
Daniel Veillard      | libxml Gnome XML XSLT toolkit  http://xmlsoft.org/
daniel veillard com  | Rpmfind RPM search engine http://rpmfind.net/
http://veillard.com/ | virtualization library  http://libvirt.org/


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]