libvirt/src/nwfilter/nwfilter_dhcpsnoop.c

2233 lines
62 KiB
C
Raw Normal View History

/*
* nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM
* on an interface
*
maint: don't permit format strings without % Any time we have a string with no % passed through gettext, a translator can inject a % to cause a stack overread. When there is nothing to format, it's easier to ask for a string that cannot be used as a formatter, by using a trivial "%s" format instead. In the past, we have used --disable-nls to catch some of the offenders, but that doesn't get run very often, and many more uses have crept in. Syntax check to the rescue! The syntax check can catch uses such as virReportError(code, _("split " "string")); by using a sed script to fold context lines into one pattern space before checking for a string without %. This patch is just mechanical insertion of %s; there are probably several messages touched by this patch where we would be better off giving the user more information than a fixed string. * cfg.mk (sc_prohibit_diagnostic_without_format): New rule. * src/datatypes.c (virUnrefConnect, virGetDomain) (virUnrefDomain, virGetNetwork, virUnrefNetwork, virGetInterface) (virUnrefInterface, virGetStoragePool, virUnrefStoragePool) (virGetStorageVol, virUnrefStorageVol, virGetNodeDevice) (virGetSecret, virUnrefSecret, virGetNWFilter, virUnrefNWFilter) (virGetDomainSnapshot, virUnrefDomainSnapshot): Add %s wrapper. * src/lxc/lxc_driver.c (lxcDomainSetBlkioParameters) (lxcDomainGetBlkioParameters): Likewise. * src/conf/domain_conf.c (virSecurityDeviceLabelDefParseXML) (virDomainDiskDefParseXML, virDomainGraphicsDefParseXML): Likewise. * src/conf/network_conf.c (virNetworkDNSHostsDefParseXML) (virNetworkDefParseXML): Likewise. * src/conf/nwfilter_conf.c (virNWFilterIsValidChainName): Likewise. * src/conf/nwfilter_params.c (virNWFilterVarValueCreateSimple) (virNWFilterVarAccessParse): Likewise. * src/libvirt.c (virDomainSave, virDomainSaveFlags) (virDomainRestore, virDomainRestoreFlags) (virDomainSaveImageGetXMLDesc, virDomainSaveImageDefineXML) (virDomainCoreDump, virDomainGetXMLDesc) (virDomainMigrateVersion1, virDomainMigrateVersion2) (virDomainMigrateVersion3, virDomainMigrate, virDomainMigrate2) (virStreamSendAll, virStreamRecvAll) (virDomainSnapshotGetXMLDesc): Likewise. * src/nwfilter/nwfilter_dhcpsnoop.c (virNWFilterSnoopReqLeaseDel) (virNWFilterDHCPSnoopReq): Likewise. * src/openvz/openvz_driver.c (openvzUpdateDevice): Likewise. * src/openvz/openvz_util.c (openvzKBPerPages): Likewise. * src/qemu/qemu_cgroup.c (qemuSetupCgroup): Likewise. * src/qemu/qemu_command.c (qemuBuildHubDevStr, qemuBuildChrChardevStr) (qemuBuildCommandLine): Likewise. * src/qemu/qemu_driver.c (qemuDomainGetPercpuStats): Likewise. * src/qemu/qemu_hotplug.c (qemuDomainAttachNetDevice): Likewise. * src/rpc/virnetsaslcontext.c (virNetSASLSessionGetIdentity): Likewise. * src/rpc/virnetsocket.c (virNetSocketNewConnectUNIX) (virNetSocketSendFD, virNetSocketRecvFD): Likewise. * src/storage/storage_backend_disk.c (virStorageBackendDiskBuildPool): Likewise. * src/storage/storage_backend_fs.c (virStorageBackendFileSystemProbe) (virStorageBackendFileSystemBuild): Likewise. * src/storage/storage_backend_rbd.c (virStorageBackendRBDOpenRADOSConn): Likewise. * src/storage/storage_driver.c (storageVolumeResize): Likewise. * src/test/test_driver.c (testInterfaceChangeBegin) (testInterfaceChangeCommit, testInterfaceChangeRollback): Likewise. * src/vbox/vbox_tmpl.c (vboxListAllDomains): Likewise. * src/xenxs/xen_sxpr.c (xenFormatSxprDisk, xenFormatSxpr): Likewise. * src/xenxs/xen_xm.c (xenXMConfigGetUUID, xenFormatXMDisk) (xenFormatXM): Likewise.
2012-07-23 20:33:08 +00:00
* Copyright (C) 2012 Red Hat, Inc.
* Copyright (C) 2011,2012 IBM Corp.
*
* Authors:
* David L Stevens <dlstevens@us.ibm.com>
* Stefan Berger <stefanb@linux.vnet.ibm.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Based in part on work by Stefan Berger <stefanb@us.ibm.com>
*/
/*
* Note about testing:
* On the host run in a shell:
* while :; do kill -SIGHUP `pidof libvirtd` ; echo "HUP $RANDOM"; sleep 20; done
*
* Inside a couple of VMs that for example use the 'clean-traffic' filter:
* while :; do kill -SIGTERM `pidof dhclient`; dhclient eth0 ; ifconfig eth0; done
*
* On the host check the lease file and that it's periodically shortened:
* cat /var/run/libvirt/network/nwfilter.leases ; date +%s
*
* On the host also check that the ebtables rules 'look' ok:
* ebtables -t nat -L
*/
#include <config.h>
#ifdef HAVE_LIBPCAP
# include <pcap.h>
#endif
#include <fcntl.h>
#include <poll.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <net/if.h>
#include "memory.h"
#include "logging.h"
#include "datatypes.h"
#include "virterror_internal.h"
#include "conf/domain_conf.h"
#include "nwfilter_gentech_driver.h"
#include "nwfilter_dhcpsnoop.h"
#include "nwfilter_ipaddrmap.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;
int nLeases; /* number of active leases */
int wLeases; /* number of written leases */
int 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
*/
int 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;
int *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 */
int 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,
bool instantiate);
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;
virNWFilterSnoopReqPtr req;
ipaddr = virSocketAddrFormat(&ipl->ipAddress);
if (!ipaddr)
return -1;
req = ipl->snoopReq;
/* protect req->ifname */
virNWFilterSnoopReqLock(req);
if (virNWFilterIPAddrMapAddIPAddr(req->ifname, ipaddr) < 0)
goto exit_snooprequnlock;
/* ipaddr now belongs to the map */
ipaddr = NULL;
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);
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);
bool is_last = false;
/* protect req->start */
virNWFilterSnoopReqLock(req);
while (req->start && req->start->timeout <= now) {
if (req->start->next == NULL ||
req->start->next->timeout > now)
is_last = true;
virNWFilterSnoopReqLeaseDel(req, &req->start->ipAddress, true,
is_last);
}
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) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("virNWFilterSnoopReqNew called with invalid "
"key \"%s\" (%zu)"),
ifkey ? ifkey : "",
ifkey ? strlen(ifkey) : 0);
return NULL;
}
if (VIR_ALLOC(req) < 0) {
virReportOOMError();
return NULL;
}
req->threadStatus = THREAD_STATUS_NONE;
if (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 (virAtomicIntGet(&req->refctr) != 0)
return;
/* free all leases */
for (ipl = req->start; ipl; ipl = req->start)
virNWFilterSnoopReqLeaseDel(req, &ipl->ipAddress, false, 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 (virAtomicIntDecAndTest(&req->refctr)) {
/*
* 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);
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.
*
* @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.
*
* Returns 0 on success, -1 if the instantiation of the rules failed
*/
static int
virNWFilterSnoopReqLeaseDel(virNWFilterSnoopReqPtr req,
virSocketAddrPtr ipaddr, bool update_leasefile,
bool instantiate)
{
int ret = 0;
virNWFilterSnoopIPLeasePtr ipl;
char *ipstr = NULL;
int ipAddrLeft;
/* protect req->start, req->ifname and the lease */
virNWFilterSnoopReqLock(req);
ipl = virNWFilterSnoopIPLeaseGetByIP(req->start, ipaddr);
if (ipl == NULL)
goto lease_not_found;
ipstr = virSocketAddrFormat(&ipl->ipAddress);
if (!ipstr) {
ret = -1;
goto lease_not_found;
}
virNWFilterSnoopIPLeaseTimerDel(ipl);
/* lease is off the list now */
if (update_leasefile)
virNWFilterSnoopLeaseFileSave(ipl);
ipAddrLeft = virNWFilterIPAddrMapDelIPAddr(req->ifname, ipstr);
if (!req->threadkey || !instantiate)
goto skip_instantiate;
if (ipAddrLeft) {
ret = virNWFilterInstantiateFilterLate(NULL,
req->ifname,
req->ifindex,
req->linkdev,
req->nettype,
&req->macaddr,
req->filtername,
req->vars,
req->driver);
} else {
const virNWFilterVarValuePtr dhcpsrvrs =
virHashLookup(req->vars->hashTable, NWFILTER_VARNAME_DHCPSERVER);
if (req->techdriver &&
req->techdriver->applyDHCPOnlyRules(req->ifname, &req->macaddr,
dhcpsrvrs, false) < 0) {
maint: don't permit format strings without % Any time we have a string with no % passed through gettext, a translator can inject a % to cause a stack overread. When there is nothing to format, it's easier to ask for a string that cannot be used as a formatter, by using a trivial "%s" format instead. In the past, we have used --disable-nls to catch some of the offenders, but that doesn't get run very often, and many more uses have crept in. Syntax check to the rescue! The syntax check can catch uses such as virReportError(code, _("split " "string")); by using a sed script to fold context lines into one pattern space before checking for a string without %. This patch is just mechanical insertion of %s; there are probably several messages touched by this patch where we would be better off giving the user more information than a fixed string. * cfg.mk (sc_prohibit_diagnostic_without_format): New rule. * src/datatypes.c (virUnrefConnect, virGetDomain) (virUnrefDomain, virGetNetwork, virUnrefNetwork, virGetInterface) (virUnrefInterface, virGetStoragePool, virUnrefStoragePool) (virGetStorageVol, virUnrefStorageVol, virGetNodeDevice) (virGetSecret, virUnrefSecret, virGetNWFilter, virUnrefNWFilter) (virGetDomainSnapshot, virUnrefDomainSnapshot): Add %s wrapper. * src/lxc/lxc_driver.c (lxcDomainSetBlkioParameters) (lxcDomainGetBlkioParameters): Likewise. * src/conf/domain_conf.c (virSecurityDeviceLabelDefParseXML) (virDomainDiskDefParseXML, virDomainGraphicsDefParseXML): Likewise. * src/conf/network_conf.c (virNetworkDNSHostsDefParseXML) (virNetworkDefParseXML): Likewise. * src/conf/nwfilter_conf.c (virNWFilterIsValidChainName): Likewise. * src/conf/nwfilter_params.c (virNWFilterVarValueCreateSimple) (virNWFilterVarAccessParse): Likewise. * src/libvirt.c (virDomainSave, virDomainSaveFlags) (virDomainRestore, virDomainRestoreFlags) (virDomainSaveImageGetXMLDesc, virDomainSaveImageDefineXML) (virDomainCoreDump, virDomainGetXMLDesc) (virDomainMigrateVersion1, virDomainMigrateVersion2) (virDomainMigrateVersion3, virDomainMigrate, virDomainMigrate2) (virStreamSendAll, virStreamRecvAll) (virDomainSnapshotGetXMLDesc): Likewise. * src/nwfilter/nwfilter_dhcpsnoop.c (virNWFilterSnoopReqLeaseDel) (virNWFilterDHCPSnoopReq): Likewise. * src/openvz/openvz_driver.c (openvzUpdateDevice): Likewise. * src/openvz/openvz_util.c (openvzKBPerPages): Likewise. * src/qemu/qemu_cgroup.c (qemuSetupCgroup): Likewise. * src/qemu/qemu_command.c (qemuBuildHubDevStr, qemuBuildChrChardevStr) (qemuBuildCommandLine): Likewise. * src/qemu/qemu_driver.c (qemuDomainGetPercpuStats): Likewise. * src/qemu/qemu_hotplug.c (qemuDomainAttachNetDevice): Likewise. * src/rpc/virnetsaslcontext.c (virNetSASLSessionGetIdentity): Likewise. * src/rpc/virnetsocket.c (virNetSocketNewConnectUNIX) (virNetSocketSendFD, virNetSocketRecvFD): Likewise. * src/storage/storage_backend_disk.c (virStorageBackendDiskBuildPool): Likewise. * src/storage/storage_backend_fs.c (virStorageBackendFileSystemProbe) (virStorageBackendFileSystemBuild): Likewise. * src/storage/storage_backend_rbd.c (virStorageBackendRBDOpenRADOSConn): Likewise. * src/storage/storage_driver.c (storageVolumeResize): Likewise. * src/test/test_driver.c (testInterfaceChangeBegin) (testInterfaceChangeCommit, testInterfaceChangeRollback): Likewise. * src/vbox/vbox_tmpl.c (vboxListAllDomains): Likewise. * src/xenxs/xen_sxpr.c (xenFormatSxprDisk, xenFormatSxpr): Likewise. * src/xenxs/xen_xm.c (xenXMConfigGetUUID, xenFormatXMDisk) (xenFormatXM): Likewise.
2012-07-23 20:33:08 +00:00
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("virNWFilterSnoopListDel failed"));
ret = -1;
}
}
skip_instantiate:
VIR_FREE(ipl);
virAtomicIntDecAndTest(&virNWFilterSnoopState.nLeases);
lease_not_found:
VIR_FREE(ipstr);
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 */
/*
* some DHCP servers send their responses as MAC broadcast replies
* filter messages from the server also by the destination MAC
* inside the DHCP response
*/
if (!fromVM) {
if (virMacAddrCmpRaw(&req->macaddr,
(unsigned char *)&pd->d_chaddr) != 0)
return -2;
}
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, 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];
virMacAddrFormat(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
*/
if (virAsprintf(&ext_filter,
"%s and ether src %s", filter, macaddr) < 0) {
virReportOOMError();
return NULL;
}
} else {
/*
* Some DHCP servers respond via MAC broadcast; we rely on later
* filtering of responses by comparing the MAC address inside the
* DHCP response against the one of the VM. Assuming that the
* bridge learns the VM's MAC address quickly this should not
* generate much more traffic than if we filtered by VM and
* braodcast MAC as well
*/
if (virAsprintf(&ext_filter, "%s", filter) < 0) {
virReportOOMError();
return NULL;
}
}
handle = pcap_create(ifname, pcap_errbuf);
if (handle == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("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) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("setup of pcap handle failed"));
goto cleanup;
}
if (pcap_compile(handle, &fp, ext_filter, 1, PCAP_NETMASK_UNKNOWN) != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("pcap_compile: %s"), pcap_geterr(handle));
goto cleanup;
}
if (pcap_setfilter(handle, &fp) != 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("pcap_setfilter: %s"), pcap_geterr(handle));
goto cleanup_freecode;
}
if (pcap_setdirection(handle, dir) < 0) {
virReportError(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;
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Instantiation of rules failed on "
"interface '%s'"), req->ifname);
}
virAtomicIntDecAndTest(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,
int *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) {
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);
virReportError(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 (virAtomicIntGet(&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) {
virReportError(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);
}
virAtomicIntDecAndTest(&virNWFilterSnoopState.nThreads);
return;
}
static void
virNWFilterSnoopIFKeyFMT(char *ifkey, const unsigned char *vmuuid,
const virMacAddrPtr 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 virMacAddrPtr 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);
virMacAddrSet(&req->macaddr, 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()) {
virReportError(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) {
maint: don't permit format strings without % Any time we have a string with no % passed through gettext, a translator can inject a % to cause a stack overread. When there is nothing to format, it's easier to ask for a string that cannot be used as a formatter, by using a trivial "%s" format instead. In the past, we have used --disable-nls to catch some of the offenders, but that doesn't get run very often, and many more uses have crept in. Syntax check to the rescue! The syntax check can catch uses such as virReportError(code, _("split " "string")); by using a sed script to fold context lines into one pattern space before checking for a string without %. This patch is just mechanical insertion of %s; there are probably several messages touched by this patch where we would be better off giving the user more information than a fixed string. * cfg.mk (sc_prohibit_diagnostic_without_format): New rule. * src/datatypes.c (virUnrefConnect, virGetDomain) (virUnrefDomain, virGetNetwork, virUnrefNetwork, virGetInterface) (virUnrefInterface, virGetStoragePool, virUnrefStoragePool) (virGetStorageVol, virUnrefStorageVol, virGetNodeDevice) (virGetSecret, virUnrefSecret, virGetNWFilter, virUnrefNWFilter) (virGetDomainSnapshot, virUnrefDomainSnapshot): Add %s wrapper. * src/lxc/lxc_driver.c (lxcDomainSetBlkioParameters) (lxcDomainGetBlkioParameters): Likewise. * src/conf/domain_conf.c (virSecurityDeviceLabelDefParseXML) (virDomainDiskDefParseXML, virDomainGraphicsDefParseXML): Likewise. * src/conf/network_conf.c (virNetworkDNSHostsDefParseXML) (virNetworkDefParseXML): Likewise. * src/conf/nwfilter_conf.c (virNWFilterIsValidChainName): Likewise. * src/conf/nwfilter_params.c (virNWFilterVarValueCreateSimple) (virNWFilterVarAccessParse): Likewise. * src/libvirt.c (virDomainSave, virDomainSaveFlags) (virDomainRestore, virDomainRestoreFlags) (virDomainSaveImageGetXMLDesc, virDomainSaveImageDefineXML) (virDomainCoreDump, virDomainGetXMLDesc) (virDomainMigrateVersion1, virDomainMigrateVersion2) (virDomainMigrateVersion3, virDomainMigrate, virDomainMigrate2) (virStreamSendAll, virStreamRecvAll) (virDomainSnapshotGetXMLDesc): Likewise. * src/nwfilter/nwfilter_dhcpsnoop.c (virNWFilterSnoopReqLeaseDel) (virNWFilterDHCPSnoopReq): Likewise. * src/openvz/openvz_driver.c (openvzUpdateDevice): Likewise. * src/openvz/openvz_util.c (openvzKBPerPages): Likewise. * src/qemu/qemu_cgroup.c (qemuSetupCgroup): Likewise. * src/qemu/qemu_command.c (qemuBuildHubDevStr, qemuBuildChrChardevStr) (qemuBuildCommandLine): Likewise. * src/qemu/qemu_driver.c (qemuDomainGetPercpuStats): Likewise. * src/qemu/qemu_hotplug.c (qemuDomainAttachNetDevice): Likewise. * src/rpc/virnetsaslcontext.c (virNetSASLSessionGetIdentity): Likewise. * src/rpc/virnetsocket.c (virNetSocketNewConnectUNIX) (virNetSocketSendFD, virNetSocketRecvFD): Likewise. * src/storage/storage_backend_disk.c (virStorageBackendDiskBuildPool): Likewise. * src/storage/storage_backend_fs.c (virStorageBackendFileSystemProbe) (virStorageBackendFileSystemBuild): Likewise. * src/storage/storage_backend_rbd.c (virStorageBackendRBDOpenRADOSConn): Likewise. * src/storage/storage_driver.c (storageVolumeResize): Likewise. * src/test/test_driver.c (testInterfaceChangeBegin) (testInterfaceChangeCommit, testInterfaceChangeRollback): Likewise. * src/vbox/vbox_tmpl.c (vboxListAllDomains): Likewise. * src/xenxs/xen_sxpr.c (xenFormatSxprDisk, xenFormatSxpr): Likewise. * src/xenxs/xen_xm.c (xenXMConfigGetUUID, xenFormatXMDisk) (xenFormatXM): Likewise.
2012-07-23 20:33:08 +00:00
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("applyDHCPOnlyRules "
"failed - spoofing not protected!"));
goto exit_snoopreqput;
}
if (virNWFilterHashTablePutAll(filterparams, req->vars) < 0) {
virReportError(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) {
virReportError(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) {
virReportError(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) {
virReportError(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) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Activation of snoop request failed on "
"interface '%s'"), req->ifname);
goto exit_snoopreq_unlock;
}
if (virNWFilterSnoopReqRestore(req) < 0) {
virReportError(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) >=
virAtomicIntGet(&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) && (virAtomicIntGet(&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') {
virReportError(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) {
virReportError(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);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("virNWFilterSnoopLeaseFileLoad req add"
" failed on interface \"%s\""), ifkey);
continue;
}
}
if (virSocketAddrParseIPv4(&ipl.ipAddress, ipstr) < 0) {
virReportError(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, false);
virNWFilterSnoopReqPut(req);
}
VIR_FORCE_FCLOSE(fp);
virNWFilterSnoopLeaseFileRefresh();
virNWFilterSnoopUnlock();
}
/*
* Wait until all threads have ended.
*/
static void
virNWFilterSnoopJoinThreads(void)
{
while (virAtomicIntGet(&virNWFilterSnoopState.nThreads) != 0) {
VIR_WARN("Waiting for snooping threads to terminate: %u\n",
virAtomicIntGet(&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));
/*
* Remove all IP addresses known to be associated with this
* interface so that a new thread will be started on this
* interface
*/
virNWFilterIPAddrMapDelIPAddr(req->ifname, NULL);
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;
VIR_DEBUG("Initializing DHCP snooping");
if (virMutexInitRecursive(&virNWFilterSnoopState.snoopLock) < 0 ||
virMutexInit(&virNWFilterSnoopState.activeLock) < 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) {
virReportError(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) {
virReportError(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)
{
VIR_DEBUG("No DHCP snooping support available");
return 0;
}
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 virMacAddrPtr macaddr ATTRIBUTE_UNUSED,
const char *filtername ATTRIBUTE_UNUSED,
virNWFilterHashTablePtr filterparams ATTRIBUTE_UNUSED,
virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED)
{
virReportError(VIR_ERR_INTERNAL_ERROR,
_("libvirt was not compiled with libpcap and \""
NWFILTER_VARNAME_CTRL_IP_LEARNING
"='dhcp'\" requires it."));
return -1;
}
#endif /* HAVE_LIBPCAP */