[libvirt] [RFC PATCHv2 9/9] add leasefile support

David L Stevens dlstevens at us.ibm.com
Wed Oct 5 15:08:54 UTC 2011


	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 at us.ibm.com>
---
 src/nwfilter/nwfilter_dhcpsnoop.c |  370 +++++++++++++++++++++++++++++++++++--
 1 files changed, 353 insertions(+), 17 deletions(-)

diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c
index f784a29..eedf550 100644
--- a/src/nwfilter/nwfilter_dhcpsnoop.c
+++ b/src/nwfilter/nwfilter_dhcpsnoop.c
@@ -56,10 +56,21 @@
 #include "nwfilter_gentech_driver.h"
 #include "nwfilter_ebiptables_driver.h"
 #include "nwfilter_dhcpsnoop.h"
+#include "configmake.h"
 
 #define VIR_FROM_THIS VIR_FROM_NWFILTER
 
+#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;
+
+#define snoop_lock()    { pthread_mutex_lock(&SnoopLock); }
+#define snoop_unlock()  { pthread_mutex_unlock(&SnoopLock); }
 
 struct virNWFilterSnoopReq {
     virConnectPtr             conn;
@@ -90,7 +101,14 @@ 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 iflease *getiflease(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_refresh(void);
+static void lease_restore(struct virNWFilterSnoopReq *req);
 
 /*
  * ipl_ladd - add an IP lease to a list
@@ -150,6 +168,7 @@ ipl_add(struct iplease *plnew)
         ipl_update(pl, plnew->ipl_timeout);
         return;
     }
+    nleases++;
     if (VIR_ALLOC(pl) < 0) {
         virReportOOMError();
         return;
@@ -184,6 +203,7 @@ ipl_add(struct iplease *plnew)
         return;
     }
     ipl_tadd(pl);
+    lease_save(pl);
 }
 
 /*
@@ -231,6 +251,7 @@ ipl_del(struct iplease *ipl)
     req = ipl->ipl_req;
 
     ipl_tdel(ipl);
+    lease_save(ipl);
 
     if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) {
         ipstr = strdup(ipbuf);
@@ -248,6 +269,7 @@ ipl_del(struct iplease *ipl)
                                _("ipl_del inet_ntop " "failed (0x%08X)"),
                                ipl->ipl_ipaddr);
     VIR_FREE(ipl);
+    nleases--;
 }
 
 /*
@@ -259,6 +281,7 @@ ipl_update(struct iplease *ipl, uint32_t timeout)
     ipl_tdel(ipl);
     ipl->ipl_timeout = timeout;
     ipl_tadd(ipl);
+    lease_save(ipl);
     return;
 }
 
@@ -275,8 +298,6 @@ ipl_getbyip(struct iplease *start, uint32_t ipaddr)
     return pl;
 }
 
-#define GRACE   5
-
 /*
  * ipl_trun - run the IP lease timeout list
  */
@@ -465,6 +486,19 @@ dhcpopen(const char *intf)
     return handle;
 }
 
+/*
+ * snoopReqFree - release a snoop Req
+ */
+static void
+snoopReqFree(struct virNWFilterSnoopReq *req)
+{
+    if (req->ifname)
+        VIR_FREE(req->ifname);
+    if (req->vars)
+        virNWFilterHashTableFree(req->vars);
+    VIR_FREE(req);
+}
+
 static void *
 virNWFilterDHCPSnoop(void *req0)
 {
@@ -479,12 +513,19 @@ virNWFilterDHCPSnoop(void *req0)
     if (!handle)
         return 0;
 
+    /* restore any saved leases for this interface */
+    snoop_lock();
+    lease_restore(req);
+    snoop_unlock();
+
     ifindex = if_nametoindex(req->ifname);
 
     while (1) {
         if (req->die)
             break;
+        snoop_lock();
         ipl_trun(req);
+        snoop_unlock();
 
         packet = (struct eth *) pcap_next(handle, &hdr);
 
@@ -494,16 +535,18 @@ virNWFilterDHCPSnoop(void *req0)
             continue;
         }
 
+        snoop_lock();
         dhcpdecode(req, packet, hdr.caplen);
+        snoop_unlock();
     }
+
+    snoop_lock();
     /* free all leases */
     for (ipl = req->start; ipl; ipl = req->start)
         ipl_del(ipl);
+    snoop_unlock();
 
-    /* free all req data */
-    VIR_FREE(req->ifname);
-    virNWFilterHashTableFree(req->vars);
-    VIR_FREE(req);
+    snoopReqFree(req);
     return 0;
 }
 
@@ -518,9 +561,12 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn,
 {
     struct virNWFilterSnoopReq *req;
 
+    snoop_lock();
     req = virHashLookup(SnoopReqs, ifname);
-    if (req)
+    snoop_unlock();
+    if (req) {
         return 0;
+    }
     if (VIR_ALLOC(req) < 0) {
         virReportOOMError();
         return 1;
@@ -533,28 +579,30 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn,
     req->ifname = strdup(ifname);
     req->vars = virNWFilterHashTableCreate(0);
     if (!req->vars) {
+        snoopReqFree(req);
         virReportOOMError();
         return 1;
     }
     if (virNWFilterHashTablePutAll(vars, req->vars)) {
         virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
-                               _("virNWFilterDHCPSnoopReq: can't copy variables"
-                                " on if %s"), ifname);
+                               _("virNWFilterDHCPSnoopReq: can't copy "
+                                 "variables on if %s"), ifname);
+        snoopReqFree(req);
         return 1;
     }
     req->driver = driver;
     req->start = req->end = 0;
 
     if (virHashAddEntry(SnoopReqs, ifname, req)) {
-        VIR_FREE(req);
+        snoopReqFree(req);
         virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
-                               _("virNWFilterDHCPSnoopReq req add failed on"
-                                "interface \"%s\""), ifname);
+                               _("virNWFilterDHCPSnoopReq req add "
+                                 "failed oninterface \"%s\""), ifname);
         return 1;
     }
     if (pthread_create(&req->thread, NULL, virNWFilterDHCPSnoop, req) != 0) {
         (void) virHashRemoveEntry(SnoopReqs, ifname);
-        VIR_FREE(req);
+        (void) snoopReqFree(req);
         virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
                                _("virNWFilterDHCPSnoopReq pthread_create failed"
                                 " oninterface \"%s\""), ifname);
@@ -577,12 +625,41 @@ freeReq(void *req0, const void *name ATTRIBUTE_UNUSED)
     req->die = 1;
 }
 
+static void
+lease_close(void)
+{
+    (void) close(lease_fd);
+    lease_fd = -1;
+}
+
+static void
+lease_open(void)
+{
+    lease_close();
+
+    lease_fd = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND, 0644);
+}
+
 int
 virNWFilterDHCPSnoopInit(void)
 {
     if (SnoopReqs)
         return 0;
+
+    if (pthread_mutex_init(&SnoopLock, 0) < 0)
+        virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("pthread_mutex_init: %s"),
+                               strerror(errno));
+    snoop_lock();
+
+    lease_load();
+
+    lease_open();
+
     SnoopReqs = virHashCreate(0, freeReq);
+
+    snoop_unlock();
+
     if (!SnoopReqs) {
         virReportOOMError();
         return -1;
@@ -595,8 +672,267 @@ virNWFilterDHCPSnoopEnd(const char *ifname)
 {
     if (!SnoopReqs)
         return;
-    if (ifname)
-        virHashRemoveEntry(SnoopReqs, ifname);
-    else                        /* free all of them */
+    snoop_lock();
+    if (!ifname) {     /* free all of them */
         virHashFree(SnoopReqs);
+        lease_refresh();
+    } else
+        virHashRemoveEntry(SnoopReqs, ifname);
+    lease_close();
+    snoop_unlock();
+}
+
+
+/* lease file handling */
+
+struct iflease {
+    char           *ifl_ifname;
+    struct iplease *ifl_start;
+    struct iplease *ifl_end;
+    struct iflease *ifl_prev;
+    struct iflease *ifl_next;
+};
+
+struct iflease *leases;
+
+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;
+    struct iflease *ifl;
+
+    /* add to the lease file list */
+    ifl = getiflease(ipl->ipl_req->ifname);
+    if (ifl) {
+        struct iplease *ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr);
+
+        if (ifipl) {
+            if (ipl->ipl_timeout) {
+                ifipl->ipl_timeout = ipl->ipl_timeout;
+                ifipl->ipl_server = ipl->ipl_server;
+            } else {
+                ipl_ldel(ifipl, &ifl->ifl_start, &ifl->ifl_end);
+                VIR_FREE(ifipl);
+            }
+        } else if (!VIR_ALLOC(ifipl)) {
+            ifipl->ipl_ipaddr = ipl->ipl_ipaddr;
+            ifipl->ipl_server = ipl->ipl_server;
+            ifipl->ipl_timeout = ipl->ipl_timeout;
+            ipl_ladd(ifipl, &ifl->ifl_start, &ifl->ifl_end);
+        }
+    }
+    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 void
+lease_restore(struct virNWFilterSnoopReq *req)
+{
+    struct iflease *ifl;
+    struct iplease *ipl;
+
+    ifl = getiflease(req->ifname);
+    if (!ifl)
+        return;
+    for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next) {
+        ipl->ipl_req = req;
+        ipl_add(ipl);
+    }
+}
+
+static struct iflease *
+getiflease(const char *ifname)
+{
+    struct iflease *ifl;
+
+    for (ifl=leases; ifl; ifl=ifl->ifl_next)
+         if (strcmp(ifname, ifl->ifl_ifname) == 0)
+             return ifl;
+    if (VIR_ALLOC(ifl)) {
+        virReportOOMError();
+        return 0;
+    }
+    ifl->ifl_ifname = strdup(ifname);
+    ifl->ifl_next = leases;
+    leases = ifl;
+    return ifl;
+}
+
+static void
+lease_refresh(void)
+{
+    struct iflease *ifl;
+    struct iplease *ipl;
+    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;
+    }
+    for (ifl=leases; ifl; ifl=ifl->ifl_next)
+        for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next)
+             if (lease_write(tfd, ifl->ifl_ifname, ipl) < 0)
+                 break;
+    (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
+LoadSnoopReqIter(void *payload,
+                 const void *name ATTRIBUTE_UNUSED,
+                 void *data ATTRIBUTE_UNUSED)
+{
+    struct virNWFilterSnoopReq *req = payload;
+    struct iplease *ipl, *ifipl;
+    struct iflease *ifl;
+
+    ifl = getiflease(req->ifname);
+    if (!ifl)
+        return;
+    for (ipl = req->start; ipl; ipl = ipl->ipl_next) {
+        ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr);
+        if (ifipl) {
+            if (ifipl->ipl_timeout < ipl->ipl_timeout) {
+                ifipl->ipl_timeout = ipl->ipl_timeout;
+                ifipl->ipl_server = ipl->ipl_server;
+            }
+            continue;
+        }
+        if (VIR_ALLOC(ifipl)) {
+            virReportOOMError();
+            continue;
+        }
+        ifipl->ipl_ipaddr = ipl->ipl_ipaddr;
+        ifipl->ipl_server = ipl->ipl_server;
+        ifipl->ipl_timeout = ipl->ipl_timeout;
+        ipl_ladd(ifipl, &ifl->ifl_start, &ifl->ifl_end);
+    }
+}
+
+static void
+lease_load(void)
+{
+    char line[256], ifname[16], ipstr[16], srvstr[16];
+    uint32_t ipaddr, svaddr;
+    FILE *fp;
+    int ln = 0;
+    time_t timeout, now;
+    struct iflease *ifl;
+    struct iplease *ipl;
+
+    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, "%lu %16s %16s %16s", (unsigned long *)&timeout,
+                   ifname, ipstr, srvstr) < 4) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                   _("lease_load lease file line %d corrupt"),
+                                   ln);
+            break;;
+        }
+        if (timeout && timeout < now)
+            continue;
+        ifl = getiflease(ifname);
+        if (!ifl)
+            break;
+
+        if (inet_pton(AF_INET, ipstr, &ipaddr) <= 0) {
+            virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("line %d corrupt ipaddr \"%s\""),
+                                  ln, ipstr);
+            VIR_FREE(ipl);
+            continue;
+        }
+        (void) inet_pton(AF_INET, srvstr, &svaddr);
+
+        ipl = ipl_getbyip(ifl->ifl_start, ipaddr);
+        if (ipl) {
+            if (timeout && timeout < ipl->ipl_timeout)
+                continue; /* out of order lease? skip. */
+            ipl->ipl_timeout = timeout;
+            ipl->ipl_server = svaddr;
+            continue;
+        }
+        if (!timeout)
+            continue; /* don't add new lease deletions */
+        if (VIR_ALLOC(ipl)) {
+            virReportOOMError();
+            break;
+        }
+        ipl->ipl_ipaddr = ipaddr;
+        ipl->ipl_server = svaddr;
+        ipl->ipl_timeout = timeout;
+        ipl_ladd(ipl, &ifl->ifl_start, &ifl->ifl_end);
+    }
+    /* also load any active leases from memory, in case lease writes may
+     * have failed.
+     */
+    if (SnoopReqs)
+        virHashForEach(SnoopReqs, LoadSnoopReqIter, 0);
+    /* remove any deleted leases */
+    for (ifl = leases; ifl; ifl = ifl->ifl_next) {
+        struct iplease *iplnext;
+
+        for (ipl = ifl->ifl_start; ipl; ipl = iplnext) {
+            iplnext = ipl->ipl_next;
+            if (ipl->ipl_timeout == 0) {
+               ipl_ldel(ipl, &ifl->ifl_start, &ifl->ifl_end);
+               VIR_FREE(ipl);
+            }
+        }
+    }
+
+    lease_refresh();
 }
-- 
1.7.6.4




More information about the libvir-list mailing list