David Stevens/Beaverton/IBM@IBMUS wrote on 03/26/2012
04:25:48 PM:
>
> This patch adds DHCP snooping support to libvirt. The learning method
for
> IP addresses is specified by setting the "ip_learning" variable
to one of
> "any" [default] (existing IP learning code), "none"
(static only addresses)
> or "dhcp" (DHCP snooping).
>
> Active leases are saved in a lease file and reloaded on restart or
HUP.
>
> Changes since v6:
> - replace pthread_cancel() with synchronous cancelation method
>
> Signed-off-by: David L Stevens <dlstevens@us.ibm.com>
> ---
[...]
> +
> +static char *
> +SnoopActivate(pthread_t thread)
> +{
> + int len;
> + char *key;
> +
> + len = sizeof(thread)*2 + 3; /* "0x"+'\0'
*/
> + if (VIR_ALLOC_N(key, len)) {
> + virReportOOMError();
> + return NULL;
> + }
> + (void) snprintf(key, len, "0x%0*lX", sizeof(thread)*2,
> + (unsigned
long int)thread);
> + key[len-1] = '\0';
Why does this string have to be closed here? You should
use virAsprintf() rather than the malloc+snprintf.
> +
> + active_lock();
> + if (virHashAddEntry(Active, key, strdup("1")))
> + VIR_FREE(key);
Check strdup() for NULL.
> +}
> +
> +/*
> + * ipl_install - install rule for a lease
> + */
> +static int
> +ipl_install(struct iplease *ipl)
> +{
> + char
ipbuf[20]; /* dotted decimal IP
> addr string */
Use INET_ADDRSTRLEN, which is 16 and corresponds to
exactly the number of bytes needed. Also you use 16 further below.
> + int
rc;
> + virNWFilterVarValuePtr ipVar;
> +
> + if (!inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipbuf,
sizeof(ipbuf))) {
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("ipl_install inet_ntop "
"failed (0x%08X)"),
Why are there two separate strings?
> +
> +/*
> + * ipl_trun - run the IP lease timeout list
> + */
> +static unsigned int
> +ipl_trun(struct virNWFilterSnoopReq *req)
> +{
> + uint32_t now;
time_t ?
> +
> +unsigned char dhcp_magic[4] = { 99, 130, 83, 99 };
static const unsigned char dhcp_magic[4] ...
> + pip = (struct iphdr *) pep->eh_data;
> + len -= sizeof(*pep);
before you used sizeof xyz and here sizeof(xyz). Can
you converge to one style? Preferably the latter.
Check len for < 0 to not step into memory that
may not have been allocated (for example pip->ihl).
> +
> +#define PBUFSIZE 576 /*
>= IP/TCP/DHCP headers */
> +#define TIMEOUT 30 /* secs */
> +
> +static pcap_t *
> +dhcpopen(const char *intf)
> +{
> + pcap_t *handle
= NULL;
> + struct bpf_program fp;
> + char
filter[64];
> + char
pcap_errbuf[PCAP_ERRBUF_SIZE];
> + time_t start;
> +
> + start = time(0);
> + while (handle == NULL && time(0) - start <
TIMEOUT)
> + handle = pcap_open_live(intf, PBUFSIZE,
0, POLL_INTERVAL,
> pcap_errbuf);
> +
> + if (handle == NULL) {
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("pcap_open_live: %s"), pcap_errbuf);
> + return 0;
> + }
> +
> + sprintf(filter, "port 67 or dst port 68");
> + if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN)
!= 0) {
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("pcap_compile: %s"), pcap_geterr(handle));
> + return 0;
> + }
> + if (pcap_setfilter(handle, &fp) != 0) {
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("pcap_setfilter: %s"), pcap_geterr(handle));
pcap_freecode()...
> +
> +static void *
> +virNWFilterDHCPSnoop(void *req0)
> +{
> + struct virNWFilterSnoopReq *req = req0;
> + pcap_t
*handle;
> + struct pcap_pkthdr *hdr;
> + struct eth
*packet;
> + int
ifindex;
> + int
errcount;
> + char
*threadkey;
> +
> + handle = dhcpopen(req->ifname);
> + if (!handle)
> + return 0;
> +
> + req->threadkey = SnoopActivate(pthread_self());
> + threadkey = strdup(req->threadkey);
Check for NULL.
> +
> +int
> +virNWFilterDHCPSnoopReq(virNWFilterTechDriverPtr techdriver,
> +
const char *ifname,
> +
const char *linkdev,
> +
enum virDomainNetType nettype,
> +
const unsigned char *vmuuid,
> +
const unsigned char *macaddr,
> +
const char *filtername,
> +
virNWFilterHashTablePtr filterparams,
> +
virNWFilterDriverStatePtr driver)
> +{
> + struct virNWFilterSnoopReq *req;
> + bool
isnewreq;
> + char
ifkey[VIR_IFKEY_LEN];
> + pthread_t
thread;
> +
> + ifkeyFormat(ifkey, vmuuid, macaddr);
> + snoop_lock();
> + req = virHashLookup(SnoopReqs, ifkey);
> + isnewreq = req == NULL;
> + if (!isnewreq) {
> + if (req->threadkey) {
> + snoop_unlock();
> + return 0;
> + }
> + } else {
> + req = newreq(ifkey);
> + if (!req) {
> + snoop_unlock();
> + return 1;
All functions now return -1 on error.
> + }
> + }
> +
> + req->techdriver = techdriver;
> + req->ifindex = if_nametoindex(ifname);
> + req->linkdev = linkdev ? strdup(linkdev) : NULL;
> + req->nettype = nettype;
> + req->ifname = strdup(ifname);
> + memcpy(req->macaddr, macaddr, sizeof(req->macaddr));
> + req->filtername = strdup(filtername);
> + if (req->filtername == NULL) {
Also check req->ifname for NULL.
> + snoop_unlock();
> + snoopReqFree(req);
> + virReportOOMError();
> + return 1;
> + }
return -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);
Does this have to be a global variable?
> +}
> +
> +int
> +virNWFilterDHCPSnoopInit(void)
> +{
> + if (SnoopReqs)
> + return 0;
> +
> + snoop_lock();
> + IfnameToKey = virHashCreate(0, NULL);
> + SnoopReqs = virHashCreate(0, freeReq);
> + if (!SnoopReqs) {
> + snoop_unlock();
free IfnameToKey? Have a common exit for error cases
.
> + virReportOOMError();
> + return -1;
> + }
> + lease_load();
> + lease_open();
> +
> + snoop_unlock();
> +
> + active_lock();
> + Active = virHashCreate(0, 0);
> + if (!Active) {
> + active_unlock();
> + virReportOOMError();
free hash tables above?
> + return -1;
> + }
> + active_unlock();
> + return 0;
> +}
> +
> +static int
> +lease_write(int lfd, const char *ifkey, struct iplease *ipl)
> +{
> + char lbuf[256],ipstr[16],dhcpstr[16];
> +
See above regarding the '16'. Space after comma.
> +
> +static struct virNWFilterSnoopReq *
> +newreq(const char *ifkey)
> +{
> + struct virNWFilterSnoopReq *req;
> +
> + if (VIR_ALLOC(req) < 0) {
> + virReportOOMError();
> + return NULL;
> + }
> + strncpy(req->ifkey, ifkey, sizeof req->ifkey);
?? req->ifkey is NULL.
Also see Hacking for usage of strncpy. Do not use
it.
> +
> + return req;
> +}
> +
> +static void
> +SaveSnoopReqIter(void *payload,
> + const void
*name ATTRIBUTE_UNUSED,
> + void *data)
> +{
> + struct virNWFilterSnoopReq *req = payload;
> + int tfd = (int)data;
This gives the case of different size error (on 64bit)
as I had said in previous patch reviews already.
Use *(int *)data.
> + struct iplease *ipl;
> +
> + /* clean up orphaned, expired leases */
> + if (!req->threadkey) {
> + uint32_t now;
time_t
> +
> + now = time(0);
> + for (ipl = req->start; ipl; ipl =
ipl->ipl_next)
> + if (ipl->ipl_timeout
< now)
> + ipl_del(req,
ipl->ipl_ipaddr , 0);
> + if (!req->start) {
> + snoopReqFree(req);
> + return;
> + }
> + }
> + for (ipl = req->start; ipl; ipl = ipl->ipl_next)
> + (void) lease_write(tfd, req->ifkey,
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);
Use VIR_FORCE_CLOSE as already shown in previous reviews.
> + 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], ifkey[VIR_IFKEY_LEN], ipstr[16], srvstr[16];
Same comment as above.
> + 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 %dcorrupt"),
> +
ln);
> + break;
> + }
> + ln++;
> + /* key len 55 = "VMUUID"+'-'+"MAC"
*/
> + if (sscanf(line, "%u %55s %16s %16s",
&ipl.ipl_timeout,
> + ifkey,
ipstr, srvstr) < 4) {
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("lease_load lease
file line %dcorrupt"),
> +
ln);
> + break;;
Remove ';'
> + }
> + if (ipl.ipl_timeout && ipl.ipl_timeout
< now)
> + continue;
> + req = virHashLookup(SnoopReqs, ifkey);
> + if (!req) {
> + req = newreq(ifkey);
> + if (!req)
> + break;
> + if (virHashAddEntry(SnoopReqs,
ifkey, req)) {
> + snoopReqFree(req);
> + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
> +
_("lease_load
req add failed on "
> +
"interface
\"%s\""), ifkey);
> + 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();
> +}
> +
Stefan