This patch adds support for saving DHCP snooping leases to an on-disk
file and restoring saved leases that are still active on restart.
Signed-off-by: David L Stevens <dlstevens(a)us.ibm.com>
---
src/nwfilter/nwfilter_dhcpsnoop.c | 270 +++++++++++++++++++++++++++++++++----
1 files changed, 243 insertions(+), 27 deletions(-)
diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c
index 8a37a6f..918ad7b 100644
--- a/src/nwfilter/nwfilter_dhcpsnoop.c
+++ b/src/nwfilter/nwfilter_dhcpsnoop.c
@@ -55,10 +55,18 @@
#include "nwfilter_gentech_driver.h"
#include "nwfilter_ebiptables_driver.h"
#include "nwfilter_dhcpsnoop.h"
+#include "virfile.h"
+#include "configmake.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
#ifdef HAVE_LIBPCAP
+
+#define LEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.leases"
+#define TMPLEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.ltmp"
+static int lease_fd = -1;
+static int nleases = 0; /* number of active leases */
+static int wleases = 0; /* number of written leases */
static virHashTablePtr SnoopReqs;
static pthread_mutex_t SnoopLock;
@@ -76,6 +84,7 @@ struct virNWFilterSnoopReq {
const char *filtername;
virNWFilterHashTablePtr vars;
virNWFilterDriverStatePtr driver;
+ bool running;
/* start and end of lease list, ordered by lease time */
struct iplease *start;
struct iplease *end;
@@ -96,7 +105,15 @@ struct iplease {
static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr);
static void ipl_update(struct iplease *pl, uint32_t timeout);
+
+static struct virNWFilterSnoopReq *newreq(const char *ifname);
+static void lease_open(void);
+static void lease_close(void);
+static void lease_load(void);
+static void lease_save(struct iplease *ipl);
+static void lease_restore(struct virNWFilterSnoopReq *req);
+static void lease_refresh(void);
/*
* ipl_ladd - add an IP lease to a list
@@ -187,7 +204,7 @@ ipl_install(struct iplease *ipl)
* ipl_add - create or update an IP lease
*/
static void
-ipl_add(struct iplease *plnew)
+ipl_add(struct iplease *plnew, bool update_leasefile)
{
struct iplease *pl;
struct virNWFilterSnoopReq *req = plnew->ipl_req;
@@ -195,6 +212,8 @@ ipl_add(struct iplease *plnew)
pl = ipl_getbyip(req->start, plnew->ipl_ipaddr);
if (pl) {
ipl_update(pl, plnew->ipl_timeout);
+ if (update_leasefile)
+ lease_save(pl);
return;
}
/* support for multiple addresses requires the ability to add filters
@@ -212,11 +231,14 @@ ipl_add(struct iplease *plnew)
}
*pl = *plnew;
- if (ipl_install(pl) < 0) {
+ if (req->running && ipl_install(pl) < 0) {
VIR_FREE(pl);
return;
}
ipl_tadd(pl);
+ nleases++;
+ if (update_leasefile)
+ lease_save(pl);
}
/*
@@ -252,7 +274,7 @@ ipl_tdel(struct iplease *ipl)
* ipl_del - delete an IP lease
*/
static void
-ipl_del(struct virNWFilterSnoopReq *req, uint32_t ipaddr)
+ipl_del(struct virNWFilterSnoopReq *req, uint32_t ipaddr, bool update_leasefile)
{
struct iplease *ipl;
@@ -262,13 +284,18 @@ ipl_del(struct virNWFilterSnoopReq *req, uint32_t ipaddr)
ipl_tdel(ipl);
- /* for multiple address support, this needs to remove those rules
- * referencing "IP" with ipl's ip value.
- */
- if (req->techdriver->applyDHCPOnlyRules(req->ifname, req->macaddr, NULL))
{
- virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "ipl_ldel failed");
+ if (update_leasefile) {
+ lease_save(ipl);
+
+ /*
+ * for multiple address support, this needs to remove those rules
+ * referencing "IP" with ipl's ip value.
+ */
+ if
(req->techdriver->applyDHCPOnlyRules(req->ifname,req->macaddr,NULL))
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "ipl_ldel failed");
}
VIR_FREE(ipl);
+ nleases--;
}
/*
@@ -308,7 +335,7 @@ ipl_trun(struct virNWFilterSnoopReq *req)
now = time(0);
while (req->start && req->start->ipl_timeout <= now)
- ipl_del(req, req->start->ipl_ipaddr);
+ ipl_del(req, req->start->ipl_ipaddr, 1);
return 0;
}
@@ -467,11 +494,11 @@ dhcpdecode(struct virNWFilterSnoopReq *req, struct eth *pep, int
len)
switch (mtype) {
case DHCPACK:
- ipl_add(&ipl);
+ ipl_add(&ipl, 1);
break;
case DHCPDECLINE:
case DHCPRELEASE:
- ipl_del(req, ipl.ipl_ipaddr);
+ ipl_del(req, ipl.ipl_ipaddr, 1);
break;
default:
break;
@@ -521,7 +548,7 @@ snoopReqFree(struct virNWFilterSnoopReq *req)
/* free all leases */
snoop_lock();
for (ipl = req->start; ipl; ipl = req->start)
- ipl_del(req, ipl->ipl_ipaddr);
+ ipl_del(req, ipl->ipl_ipaddr, 0);
snoop_unlock();
/* free all req data */
@@ -547,6 +574,8 @@ virNWFilterDHCPSnoop(void *req0)
ifindex = if_nametoindex(req->ifname);
+ lease_restore(req);
+
while (1) {
if (req->die)
break;
@@ -583,23 +612,22 @@ virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver,
{
struct virNWFilterSnoopReq *req;
pthread_t thread;
+ bool isnewreq;
snoop_lock();
req = virHashLookup(SnoopReqs, ifname);
- snoop_unlock();
- if (req)
- return 0;
-
- if (VIR_ALLOC(req) < 0) {
- virReportOOMError();
- return 1;
- }
-
- req->ifname = strdup(ifname);
- if (req->ifname == NULL) {
- snoopReqFree(req);
- virReportOOMError();
- return 1;
+ isnewreq = req == NULL;
+ if (!isnewreq) {
+ if (req->running) {
+ snoop_unlock();
+ return 0;
+ }
+ snoop_unlock();
+ } else {
+ snoop_unlock();
+ req = newreq(ifname);
+ if (!req)
+ return 1;
}
req->techdriver = techdriver;
@@ -634,8 +662,10 @@ virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver,
}
req->driver = driver;
+ req->running = 1;
+
snoop_lock();
- if (virHashAddEntry(SnoopReqs, ifname, req)) {
+ if (isnewreq && virHashAddEntry(SnoopReqs, ifname, req)) {
snoop_unlock();
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
_("virNWFilterDHCPSnoopReq req add failed on "
@@ -669,6 +699,20 @@ freeReq(void *req0, const void *name ATTRIBUTE_UNUSED)
req->die = 1;
}
+static void
+lease_close(void)
+{
+ VIR_FORCE_CLOSE(lease_fd);
+}
+
+static void
+lease_open(void)
+{
+ lease_close();
+
+ lease_fd = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND, 0644);
+}
+
int
virNWFilterDHCPSnoopInit(void)
{
@@ -687,6 +731,8 @@ virNWFilterDHCPSnoopInit(void)
virReportOOMError();
return -1;
}
+ lease_load();
+ lease_open();
snoop_unlock();
return 0;
}
@@ -709,10 +755,180 @@ virNWFilterDHCPSnoopEnd(const char *ifname)
virReportOOMError();
return;
}
+ lease_load();
}
+ lease_close();
snoop_unlock();
}
+static int
+lease_write(int lfd, const char *ifname, struct iplease *ipl)
+{
+ char lbuf[256],ipstr[16],dhcpstr[16];
+
+ if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipstr, sizeof ipstr) == 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("inet_ntop(0x%08X) failed"),
ipl->ipl_ipaddr);
+ return -1;
+ }
+ if (inet_ntop(AF_INET, &ipl->ipl_server, dhcpstr, sizeof dhcpstr) == 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("inet_ntop(0x%08X) failed"),
ipl->ipl_server);
+ return -1;
+ }
+ /* time intf ip dhcpserver */
+ snprintf(lbuf, sizeof(lbuf), "%u %s %s %s\n", ipl->ipl_timeout,
+ ifname, ipstr, dhcpstr);
+ if (write(lfd, lbuf, strlen(lbuf)) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("lease file write failed: %s"),
+ strerror(errno));
+ return -1;
+ }
+ (void) fsync(lfd);
+ return 0;
+}
+
+static void
+lease_save(struct iplease *ipl)
+{
+ struct virNWFilterSnoopReq *req = ipl->ipl_req;
+
+ if (lease_fd < 0)
+ lease_open();
+ if (lease_write(lease_fd, req->ifname, ipl) < 0)
+ return;
+ /* keep dead leases at < ~95% of file size */
+ if (++wleases >= nleases*20)
+ lease_load(); /* load & refresh lease file */
+}
+
+static struct virNWFilterSnoopReq *
+newreq(const char *ifname)
+{
+ struct virNWFilterSnoopReq *req;
+
+ if (VIR_ALLOC(req) < 0) {
+ virReportOOMError();
+ return NULL;
+ }
+ req->ifname = strdup(ifname);
+
+ return req;
+}
+
+static void
+SaveSnoopReqIter(void *payload,
+ const void *name ATTRIBUTE_UNUSED,
+ void *data)
+{
+ struct virNWFilterSnoopReq *req = payload;
+ int tfd = (int)data;
+ struct iplease *ipl;
+
+ for (ipl = req->start; ipl; ipl = ipl->ipl_next)
+ (void) lease_write(tfd, req->ifname, ipl);
+}
+
+static void
+lease_refresh(void)
+{
+ int tfd;
+
+ (void) unlink(TMPLEASEFILE);
+ /* lease file loaded, delete old one */
+ tfd = open(TMPLEASEFILE, O_CREAT|O_RDWR|O_TRUNC|O_EXCL, 0644);
+ if (tfd < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("open(\"%s\"): %s"),
+ TMPLEASEFILE, strerror(errno));
+ return;
+ }
+ if (SnoopReqs)
+ virHashForEach(SnoopReqs, SaveSnoopReqIter, (void *)tfd);
+ (void) close(tfd);
+ if (rename(TMPLEASEFILE, LEASEFILE) < 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("rename(\"%s\", \"%s\"):
%s"),
+ TMPLEASEFILE, LEASEFILE, strerror(errno));
+ (void) unlink(TMPLEASEFILE);
+ }
+ wleases = 0;
+ lease_open();
+}
+
+
+static void
+lease_load(void)
+{
+ char line[256], ifname[16], ipstr[16], srvstr[16];
+ struct iplease ipl;
+ struct virNWFilterSnoopReq *req;
+ time_t now;
+ FILE *fp;
+ int ln = 0;
+
+ fp = fopen(LEASEFILE, "r");
+ time(&now);
+ while (fp && fgets(line, sizeof(line), fp)) {
+ if (line[strlen(line)-1] != '\n') {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("lease_load lease file line %d corrupt"),
+ ln);
+ break;
+ }
+ ln++;
+ if (sscanf(line, "%u %16s %16s %16s", &ipl.ipl_timeout,
+ ifname, ipstr, srvstr) < 4) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("lease_load lease file line %d corrupt"),
+ ln);
+ break;;
+ }
+ if (ipl.ipl_timeout && ipl.ipl_timeout < now)
+ continue;
+ req = virHashLookup(SnoopReqs, ifname);
+ if (!req) {
+ req = newreq(ifname);
+ if (!req)
+ break;
+ if (virHashAddEntry(SnoopReqs, ifname, req)) {
+ snoopReqFree(req);
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("lease_load req add failed on "
+ "interface \"%s\""), ifname);
+ continue;
+ }
+ }
+
+ if (inet_pton(AF_INET, ipstr, &ipl.ipl_ipaddr) <= 0) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ _("line %d corrupt ipaddr
\"%s\""),
+ ln, ipstr);
+ continue;
+ }
+ (void) inet_pton(AF_INET, srvstr, &ipl.ipl_server);
+ ipl.ipl_req = req;
+
+ if (ipl.ipl_timeout)
+ ipl_add(&ipl, 0);
+ else
+ ipl_del(req, ipl.ipl_ipaddr, 0);
+ }
+ if (fp != NULL)
+ (void) fclose(fp);
+ lease_refresh();
+}
+
+static void
+lease_restore(struct virNWFilterSnoopReq *req)
+{
+ struct iplease *ipl;
+
+ for (ipl=req->start; ipl; ipl=ipl->ipl_next)
+ (void) ipl_install(ipl);
+}
+
#else /* HAVE_LIBPCAP */
int
virNWFilterDHCPSnoopInit(void)
--
1.7.6.4