On 10/24/2011 07:12 PM, David L Stevens wrote:
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 | 312 ++++++++++++++++++++++++++++++++++++- 1 files changed, 306 insertions(+), 6 deletions(-) diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c index b77e2b0..b2e6326 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; @@ -97,7 +105,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 @@ -209,6 +224,8 @@ ipl_add(struct iplease *plnew) return; } ipl_tadd(pl); + nleases++; + lease_save(pl); } /* @@ -254,6 +271,7 @@ ipl_del(struct iplease *ipl) req = ipl->ipl_req; ipl_tdel(ipl); + lease_save(ipl); /* for multiple address support, this needs to remove those rules * referencing "IP" with ipl's ip value. @@ -262,6 +280,7 @@ ipl_del(struct iplease *ipl) virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "ipl_ldel failed"); } VIR_FREE(ipl); + nleases--; } /* @@ -273,6 +292,7 @@ ipl_update(struct iplease *ipl, uint32_t timeout) ipl_tdel(ipl); ipl->ipl_timeout = timeout; ipl_tadd(ipl); + lease_save(ipl); return; } @@ -289,8 +309,6 @@ ipl_getbyip(struct iplease *start, uint32_t ipaddr) return pl; } -#define GRACE 5 - /* * ipl_trun - run the IP lease timeout list */ @@ -484,6 +502,9 @@ snoopReqFree(struct virNWFilterSnoopReq *req) { struct iplease *ipl; + if (!req) + return; + /* free all leases */ snoop_lock(); for (ipl = req->start; ipl; ipl = req->start) @@ -514,6 +535,11 @@ virNWFilterDHCPSnoop(void *req0) handle = dhcpopen(req->ifname); if (!handle) return 0; + + /* restore any saved leases for this interface */ + snoop_lock(); + lease_restore(req); + snoop_unlock(); ifindex = if_nametoindex(req->ifname); @@ -637,6 +663,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) { @@ -654,6 +694,8 @@ virNWFilterDHCPSnoopInit(void) virReportOOMError(); return -1; } + lease_load(); + lease_open(); snoop_unlock(); return 0; } @@ -666,13 +708,271 @@ virNWFilterDHCPSnoopEnd(const char *ifname) snoop_unlock(); return; } - if (ifname) - virHashRemoveEntry(SnoopReqs, ifname); - else /* free all of them */ + if (!ifname) { 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)) {
check for VIR_ALLOC() < 0 as done everywhere else.
+ 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);
VIR_CLOSE() I commented that on already on the previous submission.
+ 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)) {
check via (VIR_ALLOC() < 0)
+ 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)) {
Check return value of fgets().
+ 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)) {
VIR_ALLOC() < 0
+ virReportOOMError(); + break; + } + ipl->ipl_ipaddr = ipaddr; + ipl->ipl_server = svaddr; + ipl->ipl_timeout = timeout; + ipl_ladd(ipl,&ifl->ifl_start,&ifl->ifl_end); + } + (void) fclose(fp); + /* 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(); +} + #else /* HAVE_LIBPCAP */ int virNWFilterDHCPSnoopInit(void)
Stefan -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list