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@xxxxxxxxxx> > Signed-off-by: Stefan Berger <stefanb@xxxxxxxxxxxxxxxxxx> > > --- > > 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> > + <interface type='bridge'> > + <source bridge='virbr0'/> > + <filterref filter='clean-traffic'> > + <parameter name='ip_learning' value='dhcp'/> > + </filterref> > + </interface> > +</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@xxxxxxxxxx> > + * Stefan Berger <stefanb@xxxxxxxxxxxxxxxxxx> > + * > + * 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@xxxxxxxxxx> > + */ > + > +/* > + * 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@xxxxxxxxxx> > + */ > + > +#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@xxxxxxxxxxxx | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/ -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list