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: 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 Signed-off-by: Stefan Berger --- 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 | 1940 +++++++++++++++++++++++++++++++++ src/nwfilter/nwfilter_dhcpsnoop.h | 39 src/nwfilter/nwfilter_driver.c | 6 src/nwfilter/nwfilter_gentech_driver.c | 59 - 7 files changed, 2151 insertions(+), 43 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'.

+ +

Automatic IP address detection

+

+ The detection of IP addresses used on a virtual machine's interface + is automatically activated if the variable IP is referenced + but not value has been assign to it. + Since 0.9.12 + the variable 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 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 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 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). +

+ 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 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 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='ip_learning' value='dhcp'/>
+      </filterref>
+    </interface>
+
+ +

Reserved Variables

+

+ 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
ip_learning The choice of the IP address detection mode
+

Element and attribute overview

@@ -1629,6 +1741,7 @@ The following sections discuss advanced filter configuration topics.

+

Connection tracking

The network filtering subsystem (on Linux) makes use of the connection @@ -2161,36 +2274,6 @@ filtering subsystem.

-

IP Address Detection

-

- 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

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,1940 @@ +/* + * 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" + +#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_OPEN_TIMEOUT 30 /* secs */ +#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; +}; + +#define DHCP_PKT_RATE 5 /* pkts/sec */ +#define DHCP_PKT_BURST 20 /* pkts/sec */ +#define DHCP_BURST_INTERVAL_S 10 /* sec */ + +#define MAX_QUEUED_JOBS (DHCP_PKT_BURST + 2 * DHCP_PKT_RATE) + + +/* 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; +} + +/* + * 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++; +} + +/* + * 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) +{ + 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; + + 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; + + switch (mtype) { + case DHCPACK: + /* + * Sometimes, after re-plugging interfaces from one bridge to another, + * DHCP server responses (from dnsmasq) not meant for this interface + * end up here as well. Filter them. + */ + if (memcmp(req->macaddr, pep->eh_dst, 6) != 0) + return -2; + 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 *intf, unsigned int timeout_s) +{ + pcap_t *handle = NULL; + struct bpf_program fp; + char filter[64]; + char pcap_errbuf[PCAP_ERRBUF_SIZE]; + time_t start; + + start = time(0); + + while (handle == NULL && time(0) - start < timeout_s) + handle = pcap_open_live(intf, PCAP_PBUFSIZE, 0, PCAP_POLL_INTERVAL_MS, + pcap_errbuf); + + if (handle == NULL) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_open_live: %s"), pcap_errbuf); + return NULL; + } + + snprintf(filter, sizeof(filter), "port 67 or dst port 68"); + + if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_compile: %s"), pcap_geterr(handle)); + return NULL; + } + + if (pcap_setfilter(handle, &fp) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_setfilter: %s"), pcap_geterr(handle)); + pcap_freecode(&fp); + return NULL; + } + + pcap_freecode(&fp); + + return handle; +} + +/* + * 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) == -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) +{ + 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; + + 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. + * @*now: Pointer to the time this function was last invoked + * @*pkt_ctr: Pointer to the counter of packets in the last (discrete) second + * @*burst: Pointer to the time when the last burst happened + * @rate: The normal rate of packets per (discrete) second + * @burst_rate: The burst rate (> rate!) of packets during a burts + * @burst_interval: The interval between bursts + * + * 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(time_t *now, + unsigned int *pkt_ctr, + time_t *burst, + unsigned int rate, + unsigned int burst_rate, + time_t burst_interval) +{ + time_t new_now = time(0); + int diff; +#define IN_BURST(n,b) ((n)-(b) <= 1) /* bursts span 2 discreet seconds */ + + if (*now != new_now && !IN_BURST(*now, *burst)) { + *now = new_now; + *pkt_ctr = 1; + } else { + (*pkt_ctr)++; + if (*pkt_ctr >= rate) { + if (IN_BURST(*now, *burst)) { + /* in a burst */ + diff = *pkt_ctr - burst_rate; + if (diff > 0) + return diff; + return 0; + } + if (*now - *burst > burst_interval) { + /* this second will start a new burst */ + *burst = *now; + return 0; + } + /* previous burst is too close */ + return *pkt_ctr - 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; + pcap_t *handle = NULL; + struct pcap_pkthdr *hdr; + virNWFilterSnoopEthHdrPtr packet; + int ifindex = 0; + int errcount = 0; + int tmp = -1; + char *threadkey = NULL; + virThreadPoolPtr worker = NULL; + unsigned int pkt_ctr = 0; + time_t now = time(0), burst = 0; + time_t last_displayed = 0, last_displayed_queue = 0; + + /* whoever started us increased the reference counter for the req for us */ + + /* protect req->ifname & req->threadkey */ + virNWFilterSnoopReqLock(req); + + if (req->ifname && req->threadkey) { + handle = virNWFilterSnoopDHCPOpen(req->ifname, PCAP_OPEN_TIMEOUT); + tmp = virNetDevGetIndex(req->ifname, &ifindex); + threadkey = strdup(req->threadkey); + worker = virThreadPoolNew(1, 1, 0, + virNWFilterDHCPDecodeWorker, + req); + } + + /* let creator know how well we initialized */ + if (!handle || !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 (1) { + int rv; + + virNWFilterSnoopReqLeaseTimerRun(req); + + rv = pcap_next_ex(handle, &hdr, (const u_char **)&packet); + + /* + * Check whether we were cancelled or whether + * a previously submitted job failed. + */ + if (!virNWFilterSnoopIsActive(threadkey) || + req->jobCompletionStatus != 0) + goto exit; + + 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) + break; + + if (++errcount > PCAP_READ_MAXERRS) { + pcap_close(handle); + handle = NULL; + + /* protect req->ifname */ + virNWFilterSnoopReqLock(req); + + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ifname \"%s\" failing; reopening"), + req->ifname); + if (req->ifname) + handle = virNWFilterSnoopDHCPOpen(req->ifname, + 1 /* sec */); + + virNWFilterSnoopReqUnlock(req); + + if (!handle) + 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(&now, &pkt_ctr, &burst, + DHCP_PKT_RATE, + DHCP_PKT_BURST, + DHCP_BURST_INTERVAL_S); + if (diff > 0) { + if (diff > DHCP_PKT_RATE) { + /* twice the allowed rate -- serious flooding */ + usleep(10 * 1000); /* 10 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) < 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("Job submission failed on " + "interface '%s'"), req->ifname); + break; + } + } + } + /* 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); + + if (handle) + pcap_close(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 == NULL || + req->filtername == NULL || + req->vars == NULL || + 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 thread is running + * on it + */ + del_req = ((req->start == NULL) && (!req->threadkey)); + + 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; +} +#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 + */ + +#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" @@ -42,6 +43,8 @@ #define NWFILTER_STD_VAR_MAC "MAC" #define NWFILTER_STD_VAR_IP "IP" +#define NWFILTER_DFLT_LEARN "any" + static int _virNWFilterTeardownFilter(const char *ifname); @@ -662,6 +665,9 @@ virNWFilterInstantiate(const unsigned 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,