diff --git a/docs/formatnwfilter.html.in b/docs/formatnwfilter.html.in
index 5eea5c6cae..03bacc43c1 100644
--- a/docs/formatnwfilter.html.in
+++ b/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'.
+
+
+
+ The detection of IP addresses used on a virtual machine's interface
+ is automatically activated if the variable IP
is referenced
+ but no value has been assigned to it.
+ Since 0.9.13
+ the variable CTRL_IP_LEARNING
can be used to specify
+ the IP address learning method to use. Valid values are any
,
+ dhcp
, or none
.
+
+ The value any
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 CTRL_IP_LEARNING
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.
+
+ A value of dhcp
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.
+
+ Use of CTRL_IP_LEARNING=dhcp
(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 DHCPSERVER
to the IP address of a valid DHCP server
+ and provide filters that use this variable to filter incoming DHCP responses.
+
+ When DHCP snooping is enabled 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).
+
+ 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 second 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 appropriate 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 DHCPSERVER
+ 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.
+
+ If CTRL_IP_LEARNING
is set to none
, libvirt does not do
+ IP address learning and referencing IP
without assigning it an
+ explicit value is an error.
+
+ The following XML provides an example for the activation of IP address learning
+ using the DHCP snooping method:
+
+
+ <interface type='bridge'>
+ <source bridge='virbr0'/>
+ <filterref filter='clean-traffic'>
+ <parameter name='CTRL_IP_LEARNING' value='dhcp'/>
+ </filterref>
+ </interface>
+
+
+
+
+ The following table lists reserved variables in use by libvirt.
+
+
+
+ Variable Name |
+ Semantics |
+
+
+ MAC |
+ The MAC address of the interface |
+
+
+ IP |
+ The list of IP addresses in use by an interface |
+
+
+ IPV6 |
+ Not currently implemented:
+ the list of IPV6 addresses in use by an interface |
+
+
+ DHCPSERVER |
+ The list of IP addresses of trusted DHCP servers |
+
+
+ DHCPSERVERV6 |
+ Not currently implemented:
+ The list of IPv6 addresses of trusted DHCP servers |
+
+
+ CTRL_IP_LEARNING |
+ The choice of the IP address detection mode |
+
+
+
@@ -1694,6 +1806,7 @@
The following sections discuss advanced filter configuration
topics.
+
The network filtering subsystem (on Linux) makes use of the connection
@@ -2226,36 +2339,6 @@
filtering subsystem.
-
-
- In case a network filter references the variable
- IP 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.
-
- 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.
-
- In case a VM is resumed after suspension or migrated, IP address
- detection will be restarted.
-
-
VM migration is only supported if the whole filter tree
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e5d6293672..f3a4bac231 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -54,6 +54,7 @@ src/node_device/node_device_hal.c
src/node_device/node_device_linux_sysfs.c
src/node_device/node_device_udev.c
src/nodeinfo.c
+src/nwfilter/nwfilter_dhcpsnoop.c
src/nwfilter/nwfilter_driver.c
src/nwfilter/nwfilter_ebiptables_driver.c
src/nwfilter/nwfilter_gentech_driver.c
diff --git a/src/Makefile.am b/src/Makefile.am
index c023fb9a2e..a873e2e281 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -514,6 +514,8 @@ NWFILTER_DRIVER_SOURCES = \
nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \
nwfilter/nwfilter_gentech_driver.c \
nwfilter/nwfilter_gentech_driver.h \
+ nwfilter/nwfilter_dhcpsnoop.c \
+ nwfilter/nwfilter_dhcpsnoop.h \
nwfilter/nwfilter_ebiptables_driver.c \
nwfilter/nwfilter_ebiptables_driver.h \
nwfilter/nwfilter_learnipaddr.c \
diff --git a/src/conf/nwfilter_params.h b/src/conf/nwfilter_params.h
index eab46ec573..8b9cf9c1cd 100644
--- a/src/conf/nwfilter_params.h
+++ b/src/conf/nwfilter_params.h
@@ -91,6 +91,11 @@ int virNWFilterHashTablePutAll(virNWFilterHashTablePtr src,
# define VALID_VARVALUE \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:"
+# define NWFILTER_VARNAME_IP "IP"
+# define NWFILTER_VARNAME_MAC "MAC"
+# define NWFILTER_VARNAME_CTRL_IP_LEARNING "CTRL_IP_LEARNING"
+# define NWFILTER_VARNAME_DHCPSERVER "DHCPSERVER"
+
enum virNWFilterVarAccessType {
VIR_NWFILTER_VAR_ACCESS_ELEMENT = 0,
VIR_NWFILTER_VAR_ACCESS_ITERATOR = 1,
diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c
new file mode 100644
index 0000000000..60ac943804
--- /dev/null
+++ b/src/nwfilter/nwfilter_dhcpsnoop.c
@@ -0,0 +1,2184 @@
+/*
+ * nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM
+ * on an interface
+ *
+ * Copyright (C) 2011,2012 IBM Corp.
+ *
+ * Authors:
+ * David L Stevens
+ * Stefan Berger
+ *
+ * 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
+ */
+
+/*
+ * 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
+
+#ifdef HAVE_LIBPCAP
+# include
+#endif
+
+#include
+
+#include
+#include
+#include
+#include
+
+#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"
+#include "virtime.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() \
+ do { \
+ virMutexLock(&virNWFilterSnoopState.snoopLock); \
+ } while (0)
+# define virNWFilterSnoopUnlock() \
+ do { \
+ virMutexUnlock(&virNWFilterSnoopState.snoopLock); \
+ } while (0)
+# define virNWFilterSnoopActiveLock() \
+ do { \
+ virMutexLock(&virNWFilterSnoopState.activeLock); \
+ } while (0)
+# define virNWFilterSnoopActiveUnlock() \
+ do { \
+ virMutexUnlock(&virNWFilterSnoopState.activeLock); \
+ } while (0)
+
+# 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
+ */
+ virAtomicInt refctr;
+
+ virNWFilterTechDriverPtr techdriver;
+ char *ifname;
+ int ifindex;
+ const char *linkdev;
+ enum virDomainNetType nettype;
+ char ifkey[VIR_IFKEY_LEN];
+ virMacAddr macaddr;
+ 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 */
+ /*
+ * 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 {
+ virSocketAddr ipAddress;
+ virSocketAddr ipServer;
+ virNWFilterSnoopReqPtr snoopReq;
+ unsigned int timeout;
+ /* timer list */
+ virNWFilterSnoopIPLeasePtr prev;
+ virNWFilterSnoopIPLeasePtr next;
+};
+
+typedef struct _virNWFilterSnoopEthHdr virNWFilterSnoopEthHdr;
+typedef virNWFilterSnoopEthHdr *virNWFilterSnoopEthHdrPtr;
+
+struct _virNWFilterSnoopEthHdr {
+ virMacAddr eh_dst;
+ virMacAddr eh_src;
+ uint16_t eh_type;
+ uint8_t eh_data[];
+} ATTRIBUTE_PACKED;
+
+typedef struct _virNWFilterSnoopDHCPHdr virNWFilterSnoopDHCPHdr;
+typedef virNWFilterSnoopDHCPHdr *virNWFilterSnoopDHCPHdrPtr;
+
+struct _virNWFilterSnoopDHCPHdr {
+ uint8_t d_op;
+ uint8_t d_htype;
+ uint8_t d_hlen;
+ uint8_t d_hops;
+ uint32_t d_xid;
+ uint16_t d_secs;
+ uint16_t d_flags;
+ uint32_t d_ciaddr;
+ uint32_t d_yiaddr;
+ uint32_t d_siaddr;
+ uint32_t d_giaddr;
+ uint8_t d_chaddr[16];
+ char d_sname[64];
+ char d_file[128];
+ uint8_t d_opts[];
+} ATTRIBUTE_PACKED;
+
+/* 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 MIN_VALID_DHCP_PKT_SIZE \
+ (offsetof(virNWFilterSnoopEthHdr, eh_data) + \
+ sizeof(struct udphdr) + \
+ offsetof(virNWFilterSnoopDHCPHdr, d_opts))
+
+# define PCAP_PBUFSIZE 576 /* >= IP/TCP/DHCP headers */
+# define PCAP_READ_MAXERRS 25 /* retries on failing device */
+# define PCAP_FLOOD_TIMEOUT_MS 10 /* ms */
+
+typedef struct _virNWFilterDHCPDecodeJob virNWFilterDHCPDecodeJob;
+typedef virNWFilterDHCPDecodeJob *virNWFilterDHCPDecodeJobPtr;
+
+struct _virNWFilterDHCPDecodeJob {
+ unsigned char packet[PCAP_PBUFSIZE];
+ int caplen;
+ bool fromVM;
+ virAtomicIntPtr qCtr;
+};
+
+# define DHCP_PKT_RATE 10 /* pkts/sec */
+# define DHCP_PKT_BURST 50 /* pkts/sec */
+# define DHCP_BURST_INTERVAL_S 10 /* sec */
+
+# define PCAP_BUFFERSIZE (DHCP_PKT_BURST * PCAP_PBUFSIZE / 2)
+
+# 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 burstRate;
+ const unsigned int burstInterval;
+};
+
+typedef struct _virNWFilterSnoopPcapConf virNWFilterSnoopPcapConf;
+typedef virNWFilterSnoopPcapConf *virNWFilterSnoopPcapConfPtr;
+
+struct _virNWFilterSnoopPcapConf {
+ pcap_t *handle;
+ const pcap_direction_t dir;
+ const char *filter;
+ virNWFilterSnoopRateLimitConf rateLimit; /* indep. rate limiters */
+ virAtomicInt qCtr; /* number of jobs in the worker's queue */
+ const unsigned int maxQSize;
+ unsigned long long penaltyTimeoutAbs;
+};
+
+/* local function prototypes */
+static int virNWFilterSnoopReqLeaseDel(virNWFilterSnoopReqPtr req,
+ virSocketAddrPtr 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(virNWFilterSnoopReqPtr req)
+{
+ char *key;
+
+ if (virAsprintf(&key, "%p-%d", req, req->ifindex) < 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();
+
+ ignore_value(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 = NULL;
+
+ 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 = NULL;
+}
+
+/*
+ * 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 *ipaddr;
+ int rc = -1;
+ virNWFilterVarValuePtr ipVar;
+ virNWFilterSnoopReqPtr req;
+
+ ipaddr = virSocketAddrFormat(&ipl->ipAddress);
+ if (!ipaddr)
+ return -1;
+
+ ipVar = virNWFilterVarValueCreateSimple(ipaddr);
+ if (!ipVar)
+ goto cleanup;
+
+ ipaddr = NULL; /* belongs to ipVar now */
+
+ req = ipl->snoopReq;
+
+ /* protect req->ifname and req->vars */
+ virNWFilterSnoopReqLock(req);
+
+ if (virNWFilterHashTablePut(req->vars, NWFILTER_VARNAME_IP,
+ ipVar, 1) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Could not add variable \""
+ NWFILTER_VARNAME_IP "\" to hashmap"));
+ virNWFilterVarValueFree(ipVar);
+ goto exit_snooprequnlock;
+ }
+
+ if (!instantiate) {
+ rc = 0;
+ goto exit_snooprequnlock;
+ }
+
+ /* instantiate the filters */
+
+ if (req->ifname)
+ rc = virNWFilterInstantiateFilterLate(NULL,
+ req->ifname,
+ req->ifindex,
+ req->linkdev,
+ req->nettype,
+ req->macaddr,
+ req->filtername,
+ req->vars,
+ req->driver);
+
+exit_snooprequnlock:
+ virNWFilterSnoopReqUnlock(req);
+
+cleanup:
+ VIR_FREE(ipaddr);
+
+ 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,
+ virSocketAddrPtr ipaddr)
+{
+ virNWFilterSnoopIPLeasePtr pl;
+
+ for (pl = start;
+ pl && !virSocketAddrEqual(&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)
+{
+ virAtomicIntInc(&req->refctr);
+}
+
+/*
+ * 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\" (%zu)"),
+ ifkey ? ifkey : "",
+ strlen(ifkey));
+ return NULL;
+ }
+
+ if (VIR_ALLOC(req) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ req->threadStatus = THREAD_STATUS_NONE;
+
+ if (virAtomicIntInit(&req->refctr) < 0 ||
+ virStrcpyStatic(req->ifkey, ifkey) == NULL ||
+ virMutexInitRecursive(&req->lock) < 0)
+ goto err_free_req;
+
+ if (virCondInit(&req->threadStatusCond) < 0)
+ goto err_destroy_mutex;
+
+ virNWFilterSnoopReqGet(req);
+
+ return req;
+
+err_destroy_mutex:
+ virMutexDestroy(&req->lock);
+
+err_free_req:
+ VIR_FREE(req);
+
+ return NULL;
+}
+
+/*
+ * 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 (virAtomicIntRead(&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);
+
+ virMutexDestroy(&req->lock);
+ ignore_value(virCondDestroy(&req->threadStatusCond));
+
+ 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 = 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();
+
+ if (virAtomicIntDec(&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)) {
+ ignore_value(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);
+
+ virAtomicIntInc(&virNWFilterSnoopState.nLeases);
+
+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,
+ virSocketAddrPtr 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);
+
+ virAtomicIntDec(&virNWFilterSnoopState.nLeases);
+
+lease_not_found:
+ virNWFilterSnoopReqUnlock(req);
+
+ return ret;
+}
+
+static int
+virNWFilterSnoopDHCPGetOpt(virNWFilterSnoopDHCPHdrPtr pd, int len,
+ uint8_t *pmtype, uint32_t *pleasetime)
+{
+ int oind, olen;
+ int oend;
+ uint32_t nwint;
+
+ 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 = 0;
+ *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 */
+ memcpy(&nwint, (char *)pd->d_opts + oind + 2, sizeof(nwint));
+ *pleasetime = ntohl(nwint);
+ 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;
+ uint8_t mtype;
+ uint32_t leasetime;
+ uint32_t nwint;
+
+ /* 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;
+ default:
+ return -2;
+ }
+
+ if (len < 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));
+
+ memcpy(&nwint, &pd->d_yiaddr, sizeof(nwint));
+ virSocketAddrSetIPv4Addr(&ipl.ipAddress, ntohl(nwint));
+
+ memcpy(&nwint, &pd->d_siaddr, sizeof(nwint));
+ virSocketAddrSetIPv4Addr(&ipl.ipServer, ntohl(nwint));
+
+ 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;
+ }
+
+ return 0;
+}
+
+static pcap_t *
+virNWFilterSnoopDHCPOpen(const char *ifname, virMacAddr *mac,
+ const char *filter, pcap_direction_t dir)
+{
+ pcap_t *handle = NULL;
+ struct bpf_program fp;
+ char pcap_errbuf[PCAP_ERRBUF_SIZE];
+ char *ext_filter = NULL;
+ char macaddr[VIR_MAC_STRING_BUFLEN];
+ const char *ext;
+
+ virMacAddrFormat((unsigned char *)mac, macaddr);
+
+ if (dir == PCAP_D_IN /* from VM */) {
+ /*
+ * don't want to hear about another VM's DHCP requests
+ *
+ * extend the filter with the macaddr of the VM; filter the
+ * more unlikely parameters first, then go for the MAC
+ */
+ ext = "and ether src";
+ } else {
+ ext = "and ether dst";
+ }
+
+ if (virAsprintf(&ext_filter, "%s %s %s", filter, ext, macaddr) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+
+ handle = pcap_create(ifname, pcap_errbuf);
+
+ if (handle == NULL) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("pcap_create failed"));
+ goto cleanup_nohandle;
+ }
+
+ if (pcap_set_snaplen(handle, PCAP_PBUFSIZE) < 0 ||
+ pcap_set_buffer_size(handle, PCAP_BUFFERSIZE) < 0 ||
+ pcap_activate(handle) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("setup of pcap handle failed"));
+ goto cleanup;
+ }
+
+ if (pcap_compile(handle, &fp, ext_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);
+ VIR_FREE(ext_filter);
+
+ return handle;
+
+cleanup_freecode:
+ pcap_freecode(&fp);
+cleanup:
+ pcap_close(handle);
+cleanup_nohandle:
+ VIR_FREE(ext_filter);
+
+ 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);
+ }
+ virAtomicIntDec(job->qCtr);
+ VIR_FREE(job);
+}
+
+/*
+ * Submit a job to the worker thread doing the time-consuming work...
+ */
+static int
+virNWFilterSnoopDHCPDecodeJobSubmit(virThreadPoolPtr pool,
+ virNWFilterSnoopEthHdrPtr pep,
+ int len, pcap_direction_t dir,
+ virAtomicIntPtr qCtr)
+{
+ virNWFilterDHCPDecodeJobPtr job;
+ int ret;
+
+ if (len <= MIN_VALID_DHCP_PKT_SIZE || 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);
+ job->qCtr = qCtr;
+
+ ret = virThreadPoolSendJob(pool, 0, job);
+
+ if (ret == 0)
+ virAtomicIntInc(qCtr);
+ else
+ VIR_FREE(job);
+
+ 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 unsigned int
+virNWFilterSnoopRateLimit(virNWFilterSnoopRateLimitConfPtr rl)
+{
+ time_t now = time(0);
+ int diff;
+# define IN_BURST(n,b) ((n)-(b) <= 1) /* bursts span 2 discrete 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->burstRate;
+ if (diff > 0)
+ return diff;
+ return 0;
+ }
+ if (rl->prev - rl->burst > rl->burstInterval) {
+ /* 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;
+}
+
+/*
+ * virNWFilterSnoopRatePenalty
+ *
+ * @pc: pointer to the virNWFilterSnoopPcapConf
+ * @diff: the amount of pkts beyond the rate, i.e., if the rate is 10
+ * and 13 pkts have been received now in one seconds, then
+ * this should be 3.
+ *
+ * Adjusts the timeout the virNWFilterSnooPcapConf will be penalized for
+ * sending too many packets.
+ */
+static void
+virNWFilterSnoopRatePenalty(virNWFilterSnoopPcapConfPtr pc,
+ unsigned int diff, unsigned int limit)
+{
+ if (diff > limit) {
+ unsigned long long now;
+
+ if (virTimeMillisNowRaw(&now) < 0) {
+ usleep(PCAP_FLOOD_TIMEOUT_MS); /* 1 ms */
+ pc->penaltyTimeoutAbs = 0;
+ } else {
+ /* don't listen to the fd for 1 ms */
+ pc->penaltyTimeoutAbs = now + PCAP_FLOOD_TIMEOUT_MS;
+ }
+ }
+}
+
+static int
+virNWFilterSnoopAdjustPoll(virNWFilterSnoopPcapConfPtr pc,
+ size_t nPc, struct pollfd *pfd,
+ int *pollTo)
+{
+ int ret = 0;
+ size_t i;
+ int tmp;
+ unsigned long long now = 0;
+
+ *pollTo = -1;
+
+ for (i = 0; i < nPc; i++) {
+ if (pc[i].penaltyTimeoutAbs != 0) {
+ if (now == 0) {
+ if (virTimeMillisNow(&now) < 0) {
+ ret = -1;
+ break;
+ }
+ }
+
+ if (now < pc[i].penaltyTimeoutAbs) {
+ /* don't listen to incoming data on the fd for some time */
+ pfd[i].events &= ~POLLIN;
+ /*
+ * calc the max. time to spend in poll() until adjustments
+ * to the pollfd array are needed again.
+ */
+ tmp = pc[i].penaltyTimeoutAbs - now;
+ if (*pollTo == -1 || tmp < *pollTo)
+ *pollTo = tmp;
+ } else {
+ /* listen again to the fd */
+ pfd[i].events |= POLLIN;
+
+ pc[i].penaltyTimeoutAbs = 0;
+ }
+ }
+ }
+
+ return ret;
+}
+
+/*
+ * 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, pollTo;
+ char *threadkey = NULL;
+ virThreadPoolPtr worker = NULL;
+ time_t last_displayed = 0, last_displayed_queue = 0;
+ virNWFilterSnoopPcapConf pcapConf[] = {
+ {
+ .dir = PCAP_D_IN, /* from VM */
+ .filter = "dst port 67 and src port 68",
+ .rateLimit = {
+ .prev = time(0),
+ .rate = DHCP_PKT_RATE,
+ .burstRate = DHCP_PKT_BURST,
+ .burstInterval = DHCP_BURST_INTERVAL_S,
+ },
+ .maxQSize = MAX_QUEUED_JOBS,
+ }, {
+ .dir = PCAP_D_OUT, /* to VM */
+ .filter = "src port 67 and dst port 68",
+ .rateLimit = {
+ .prev = time(0),
+ .rate = DHCP_PKT_RATE,
+ .burstRate = DHCP_PKT_BURST,
+ .burstInterval = DHCP_BURST_INTERVAL_S,
+ },
+ .maxQSize = MAX_QUEUED_JOBS,
+ },
+ };
+ struct pollfd fds[] = {
+ {
+ /* get a POLLERR if interface goes down or disappears */
+ .events = POLLIN | POLLERR,
+ }, {
+ .events = POLLIN | POLLERR,
+ },
+ };
+ 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, &req->macaddr,
+ pcapConf[i].filter,
+ pcapConf[i].dir);
+ if (!pcapConf[i].handle ||
+ virAtomicIntInit(&pcapConf[i].qCtr) < 0) {
+ 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) {
+ if (virNWFilterSnoopAdjustPoll(pcapConf,
+ ARRAY_CARDINALITY(pcapConf),
+ fds, &pollTo) < 0) {
+ break;
+ }
+
+ n = poll(fds, ARRAY_CARDINALITY(fds), pollTo);
+
+ if (n < 0) {
+ if (errno != EAGAIN && errno != EINTR)
+ 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, &req->macaddr,
+ pcapConf[i].filter,
+ pcapConf[i].dir);
+
+ virNWFilterSnoopReqUnlock(req);
+
+ if (!pcapConf[i].handle) {
+ error = true;
+ break;
+ }
+ }
+ continue;
+ }
+
+ errcount = 0;
+
+ if (rv) {
+ unsigned int diff;
+
+ /* submit packet to worker thread */
+ if (virAtomicIntRead(&pcapConf[i].qCtr) >
+ pcapConf[i].maxQSize) {
+ 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(&pcapConf[i].rateLimit);
+ if (diff > 0) {
+ virNWFilterSnoopRatePenalty(&pcapConf[i], diff,
+ DHCP_PKT_RATE);
+ /* 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, packet,
+ hdr->caplen,
+ pcapConf[i].dir,
+ &pcapConf[i].qCtr) < 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);
+
+ ignore_value(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);
+ }
+
+ virAtomicIntDec(&virNWFilterSnoopState.nThreads);
+
+ 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 ||
+ (linkdev != NULL && req->linkdev == NULL)) {
+ virReportOOMError();
+ goto exit_snoopreqput;
+ }
+
+ /* check that all tools are available for applying the filters (late) */
+ if ( !techdriver->canApplyBasicRules()) {
+ virNWFilterReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("IP parameter must be provided since "
+ "snooping the IP address does not work "
+ "possibly due to missing tools"));
+ 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;
+ }
+
+ virAtomicIntInc(&virNWFilterSnoopState.nThreads);
+
+ req->threadkey = virNWFilterSnoopActivate(req);
+ if (!req->threadkey) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("Activation 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 = NULL;
+ char *ipstr, *dhcpstr;
+ int len;
+ int ret = 0;
+
+ ipstr = virSocketAddrFormat(&ipl->ipAddress);
+ dhcpstr = virSocketAddrFormat(&ipl->ipServer);
+
+ if (!dhcpstr || !ipstr) {
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* time intf ip dhcpserver */
+ len = virAsprintf(&lbuf, "%u %s %s %s\n", ipl->timeout,
+ ifkey, ipstr, dhcpstr);
+
+ if (len < 0) {
+ virReportOOMError();
+ ret = -1;
+ goto cleanup;
+ }
+
+ if (safewrite(lfd, lbuf, len) != len) {
+ virReportSystemError(errno, "%s", _("lease file write failed"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ ignore_value(fsync(lfd));
+
+cleanup:
+ VIR_FREE(lbuf);
+ VIR_FREE(dhcpstr);
+ VIR_FREE(ipstr);
+
+ return ret;
+}
+
+/*
+ * 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 (virAtomicIntInc(&virNWFilterSnoopState.wLeases) >=
+ 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) && (virAtomicIntRead(&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)
+ ignore_value(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;
+
+ if (unlink(TMPLEASEFILE) < 0 && errno != ENOENT)
+ virReportSystemError(errno, _("unlink(\"%s\")"), TMPLEASEFILE);
+
+ /* lease file loaded, delete old one */
+ tfd = open(TMPLEASEFILE, O_CREAT|O_RDWR|O_TRUNC|O_EXCL, 0644);
+ if (tfd < 0) {
+ virReportSystemError(errno, _("open(\"%s\")"), TMPLEASEFILE);
+ return;
+ }
+
+ if (virNWFilterSnoopState.snoopReqs) {
+ /* clean up the requests */
+ virHashRemoveSet(virNWFilterSnoopState.snoopReqs,
+ virNWFilterSnoopPruneIter, NULL);
+ /* now save them */
+ virHashForEach(virNWFilterSnoopState.snoopReqs,
+ virNWFilterSnoopSaveIter, (void *)&tfd);
+ }
+
+ if (VIR_CLOSE(tfd) < 0) {
+ virReportSystemError(errno, _("unable to close %s"), TMPLEASEFILE);
+ /* assuming the old lease file is still better, skip the renaming */
+ goto skip_rename;
+ }
+
+ if (rename(TMPLEASEFILE, LEASEFILE) < 0) {
+ virReportSystemError(errno, _("rename(\"%s\", \"%s\")"),
+ TMPLEASEFILE, LEASEFILE);
+ ignore_value(unlink(TMPLEASEFILE));
+ }
+ virAtomicIntSet(&virNWFilterSnoopState.wLeases, 0);
+
+skip_rename:
+ 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 (virSocketAddrParseIPv4(&ipl.ipAddress, ipstr) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("line %d corrupt ipaddr \"%s\""),
+ ln, ipstr);
+ virNWFilterSnoopReqPut(req);
+ continue;
+ }
+ ignore_value(virSocketAddrParseIPv4(&ipl.ipServer, srvstr));
+ 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 request, 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) {
+ ignore_value(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;
+ }
+
+ ignore_value(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 \""
+ NWFILTER_VARNAME_CTRL_IP_LEARNING
+ "='dhcp'\" requires it."));
+ return -1;
+}
+#endif /* HAVE_LIBPCAP */
diff --git a/src/nwfilter/nwfilter_dhcpsnoop.h b/src/nwfilter/nwfilter_dhcpsnoop.h
new file mode 100644
index 0000000000..ca7b6f0547
--- /dev/null
+++ b/src/nwfilter/nwfilter_dhcpsnoop.h
@@ -0,0 +1,39 @@
+/*
+ * nwfilter_dhcpsnoop.h: support DHCP snooping for a VM on an interface
+ *
+ * Copyright (C) 2010-2012 IBM Corp.
+ * Copyright (C) 2010-2012 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
+ */
+
+#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 */
diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c
index efb9182fb4..677d03808b 100644
--- a/src/nwfilter/nwfilter_driver.c
+++ b/src/nwfilter/nwfilter_driver.c
@@ -39,6 +39,7 @@
#include "nwfilter_gentech_driver.h"
#include "configmake.h"
+#include "nwfilter_dhcpsnoop.h"
#include "nwfilter_learnipaddr.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
@@ -66,6 +67,8 @@ static int
nwfilterDriverStartup(int privileged) {
char *base = NULL;
+ if (virNWFilterDHCPSnoopInit() < 0)
+ return -1;
if (virNWFilterLearnInit() < 0)
return -1;
@@ -119,6 +122,7 @@ alloc_err_exit:
conf_init_err:
virNWFilterTechDriversShutdown();
+ virNWFilterDHCPSnoopShutdown();
virNWFilterLearnShutdown();
return -1;
@@ -141,6 +145,7 @@ nwfilterDriverReload(void) {
conn = virConnectOpen("qemu:///system");
if (conn) {
+ virNWFilterDHCPSnoopEnd(NULL);
/* shut down all threads -- they will be restarted if necessary */
virNWFilterLearnThreadsTerminate(true);
@@ -195,6 +200,7 @@ nwfilterDriverShutdown(void) {
virNWFilterConfLayerShutdown();
virNWFilterTechDriversShutdown();
+ virNWFilterDHCPSnoopShutdown();
virNWFilterLearnShutdown();
nwfilterDriverLock(driverState);
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c
index fc71e7bba0..1738f5cba1 100644
--- a/src/nwfilter/nwfilter_gentech_driver.c
+++ b/src/nwfilter/nwfilter_gentech_driver.c
@@ -32,6 +32,7 @@
#include "virterror_internal.h"
#include "nwfilter_gentech_driver.h"
#include "nwfilter_ebiptables_driver.h"
+#include "nwfilter_dhcpsnoop.h"
#include "nwfilter_learnipaddr.h"
#include "virnetdev.h"
#include "datatypes.h"
@@ -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 char *vmuuid ATTRIBUTE_UNUSED,
void **ptrs = NULL;
int instantiate = 1;
char *buf;
+ virNWFilterVarValuePtr lv;
+ const char *learning;
+ bool reportIP = false;
virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0);
if (!missing_vars) {
@@ -678,22 +684,47 @@ virNWFilterInstantiate(const unsigned char *vmuuid ATTRIBUTE_UNUSED,
if (rc < 0)
goto err_exit;
+ lv = virHashLookup(vars->hashTable, NWFILTER_VARNAME_CTRL_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 *ifname)
return -1;
}
+ virNWFilterDHCPSnoopEnd(ifname);
+
virNWFilterTerminateLearnReq(ifname);
if (virNWFilterLockIface(ifname) < 0)