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@xxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list