libvirt/src/nwfilter/nwfilter_dhcpsnoop.c
Eric Blake 89cf363061 nwfilter: drop dead code
Commit cb022152 went overboard and introduced a dead conditional
while trying to get rid of a potential NULL dereference.

* src/nwfilter/nwfilter_dhcpsnoop.c (virNWFilterSnoopReqNew):
Remove redundant conditional.
2012-11-28 09:21:33 -07:00

2233 lines
62 KiB
C

/*
* nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM
* on an interface
*
* 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) {
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) {
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 */