nwfilter: add DHCP snooping

This patch adds DHCP snooping support to libvirt. The learning method for
IP addresses is specified by setting the "CTRL_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.

The following interface XML activates and uses the DHCP snooping:

    <interface type='bridge'>
      <source bridge='virbr0'/>
      <filterref filter='clean-traffic'>
        <parameter name='CTRL_IP_LEARNING' value='dhcp'/>
      </filterref>
    </interface>

All filters containing the variable 'IP' are automatically adjusted when
the VM receives an IP address via DHCP. However, multiple IP addresses per
interface are silently ignored in this patch, thus only supporting one IP
address per interface. Multiple IP address support is added in a later
patch in this series.

Signed-off-by: David L Stevens <dlstevens@us.ibm.com>
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
This commit is contained in:
Stefan Berger 2012-06-01 19:32:06 -04:00 committed by Stefan Berger
parent 195fa214b6
commit cec281fcaa
8 changed files with 2398 additions and 45 deletions

View File

@ -371,6 +371,118 @@
Further, the notation of $VARIABLE is short-hand for $VARIABLE[@0]. The
former notation always assumes the iterator with Id '0'.
<p>
<h3><a name="nwfelemsRulesAdvIPAddrDetection">Automatic IP address detection</a></h3>
<p>
The detection of IP addresses used on a virtual machine's interface
is automatically activated if the variable <code>IP</code> is referenced
but no value has been assigned to it.
<span class="since">Since 0.9.13</span>
the variable <code>CTRL_IP_LEARNING</code> can be used to specify
the IP address learning method to use. Valid values are <code>any</code>,
<code>dhcp</code>, or <code>none</code>.
<br/><br/>
The value <code>any</code> means that libvirt may use any packet to
determine the address in use by a virtual machine, which is the default
behavior if the variable <code>CTRL_IP_LEARNING</code> is not set. This method
will only detect a single IP address on an interface.
Once a VM's IP address has been detected, its IP network traffic
will be locked to that address, if for example IP address spoofing
is prevented by one of its filters. In that case the user of the VM
will not be able to change the IP address on the interface inside
the VM, which would be considered IP address spoofing.
When a VM is migrated to another host or resumed after a suspend operation,
the first packet sent by the VM will again determine the IP address it can
use on a particular interface.
<br/><br>
A value of <code>dhcp</code> specifies that libvirt should only honor DHCP
server-assigned addresses with valid leases. This method supports the detection
and usage of multiple IP address per interface.
When a VM is resumed after a suspend operation, still valid IP address leases
are applied to its filters. Otherwise the VM is expected to again use DHCP to obtain new
IP addresses. The migration of a VM to another physical host requires that
the VM again runs the DHCP protocol.
<br/><br/>
Use of <code>CTRL_IP_LEARNING=dhcp</code> (DHCP snooping) provides additional
anti-spoofing security, especially when combined with a filter allowing
only trusted DHCP servers to assign addresses. To enable this, set the
variable <code>DHCPSERVER</code> to the IP address of a valid DHCP server
and provide filters that use this variable to filter incoming DHCP responses.
<br/><br/>
When DHCP snooping is enabled and the DHCP lease expires,
the VM will no longer be able to use the IP address until it acquires a
new, valid lease from a DHCP server. If the VM is migrated, it must get
a new valid DHCP lease to use an IP address (e.g., by
bringing the VM interface down and up again).
<br/><br/>
Note that automatic DHCP detection listens to the DHCP traffic
the VM exchanges with the DHCP server of the infrastructure. To avoid
denial-of-service attacks on libvirt, the evaluation of those packets
is rate-limited, meaning that a VM sending an excessive number of DHCP
packets per second on an interface will not have all of those packets
evaluated and thus filters may not get adapted. Normal DHCP client
behavior is assumed to send a low number of DHCP packets per second.
Further, it is important to setup appropriate filters on all VMs in
the infrastructure to avoid them being able to send DHCP
packets. Therefore VMs must either be prevented from sending UDP and TCP
traffic from port 67 to port 68 or the <code>DHCPSERVER</code>
variable should be used on all VMs to restrict DHCP server messages to
only be allowed to originate from trusted DHCP servers. At the same
time anti-spoofing prevention must be enabled on all VMs in the subnet.
<br/><br/>
If <code>CTRL_IP_LEARNING</code> is set to <code>none</code>, libvirt does not do
IP address learning and referencing <code>IP</code> without assigning it an
explicit value is an error.
<br/><br/>
The following XML provides an example for the activation of IP address learning
using the DHCP snooping method:
</p>
<pre>
&lt;interface type='bridge'&gt;
&lt;source bridge='virbr0'/&gt;
&lt;filterref filter='clean-traffic'&gt;
&lt;parameter name='CTRL_IP_LEARNING' value='dhcp'/&gt;
&lt;/filterref&gt;
&lt;/interface&gt;
</pre>
<h3><a name="nwfelemsReservedVars">Reserved Variables</a></h3>
<p>
The following table lists reserved variables in use by libvirt.
</p>
<table class="top_table">
<tr>
<th> Variable Name </th>
<th> Semantics </th>
</tr>
<tr>
<td> MAC </td>
<td> The MAC address of the interface </td>
</tr>
<tr>
<td> IP </td>
<td> The list of IP addresses in use by an interface </td>
</tr>
<tr>
<td> IPV6 </td>
<td> Not currently implemented:
the list of IPV6 addresses in use by an interface </td>
</tr>
<tr>
<td> DHCPSERVER </td>
<td> The list of IP addresses of trusted DHCP servers</td>
</tr>
<tr>
<td> DHCPSERVERV6 </td>
<td> Not currently implemented:
The list of IPv6 addresses of trusted DHCP servers</td>
</tr>
<tr>
<td> CTRL_IP_LEARNING </td>
<td> The choice of the IP address detection mode </td>
</tr>
</table>
<h2><a name="nwfelems">Element and attribute overview</a></h2>
<p>
@ -1694,6 +1806,7 @@
The following sections discuss advanced filter configuration
topics.
</p>
<h4><a name="nwfelemsRulesAdvTracking">Connection tracking</a></h4>
<p>
The network filtering subsystem (on Linux) makes use of the connection
@ -2226,36 +2339,6 @@
filtering subsystem.
</p>
<h3><a name="nwflimitsIP">IP Address Detection</a></h3>
<p>
In case a network filter references the variable
<i>IP</i> and no variable was defined in any higher layer
references to the filter, IP address detection will automatically
be started when the filter is to be instantiated (VM start, interface
hotplug event). Only IPv4
addresses can be detected and only a single IP address
legitimately in use by a VM on a single interface will be detected.
In case a VM was to use multiple IP address on a single interface
(IP aliasing),
the IP addresses would have to be provided explicitly either
in the network filter itself or as variables used in attributes'
values. These
variables must then be defined in a higher level reference to the filter
and each assigned the value of the IP address that the VM is expected
to be using.
Different IP addresses in use by multiple interfaces of a VM
(one IP address each) will be independently detected.
<br/><br/>
Once a VM's IP address has been detected, its IP network traffic
may be locked to that address, if for example IP address spoofing
is prevented by one of its filters. In that case the user of the VM
will not be able to change the IP address on the interface inside
the VM, which would be considered IP address spoofing.
<br/><br/>
In case a VM is resumed after suspension or migrated, IP address
detection will be restarted.
</p>
<h3><a name="nwflimitsmigr">VM Migration</a></h3>
<p>
VM migration is only supported if the whole filter tree

View File

@ -54,6 +54,7 @@ src/node_device/node_device_hal.c
src/node_device/node_device_linux_sysfs.c
src/node_device/node_device_udev.c
src/nodeinfo.c
src/nwfilter/nwfilter_dhcpsnoop.c
src/nwfilter/nwfilter_driver.c
src/nwfilter/nwfilter_ebiptables_driver.c
src/nwfilter/nwfilter_gentech_driver.c

View File

@ -514,6 +514,8 @@ NWFILTER_DRIVER_SOURCES = \
nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \
nwfilter/nwfilter_gentech_driver.c \
nwfilter/nwfilter_gentech_driver.h \
nwfilter/nwfilter_dhcpsnoop.c \
nwfilter/nwfilter_dhcpsnoop.h \
nwfilter/nwfilter_ebiptables_driver.c \
nwfilter/nwfilter_ebiptables_driver.h \
nwfilter/nwfilter_learnipaddr.c \

View File

@ -91,6 +91,11 @@ int virNWFilterHashTablePutAll(virNWFilterHashTablePtr src,
# define VALID_VARVALUE \
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:"
# define NWFILTER_VARNAME_IP "IP"
# define NWFILTER_VARNAME_MAC "MAC"
# define NWFILTER_VARNAME_CTRL_IP_LEARNING "CTRL_IP_LEARNING"
# define NWFILTER_VARNAME_DHCPSERVER "DHCPSERVER"
enum virNWFilterVarAccessType {
VIR_NWFILTER_VAR_ACCESS_ELEMENT = 0,
VIR_NWFILTER_VAR_ACCESS_ITERATOR = 1,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
/*
* nwfilter_dhcpsnoop.h: support DHCP snooping for a VM on an interface
*
* Copyright (C) 2010-2012 IBM Corp.
* Copyright (C) 2010-2012 David L Stevens
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Author: David L Stevens <dlstevens@us.ibm.com>
*/
#ifndef __NWFILTER_DHCPSNOOP_H
# define __NWFILTER_DHCPSNOOP_H
int virNWFilterDHCPSnoopInit(void);
void virNWFilterDHCPSnoopShutdown(void);
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);
void virNWFilterDHCPSnoopEnd(const char *ifname);
#endif /* __NWFILTER_DHCPSNOOP_H */

View File

@ -39,6 +39,7 @@
#include "nwfilter_gentech_driver.h"
#include "configmake.h"
#include "nwfilter_dhcpsnoop.h"
#include "nwfilter_learnipaddr.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
@ -66,6 +67,8 @@ static int
nwfilterDriverStartup(int privileged) {
char *base = NULL;
if (virNWFilterDHCPSnoopInit() < 0)
return -1;
if (virNWFilterLearnInit() < 0)
return -1;
@ -119,6 +122,7 @@ alloc_err_exit:
conf_init_err:
virNWFilterTechDriversShutdown();
virNWFilterDHCPSnoopShutdown();
virNWFilterLearnShutdown();
return -1;
@ -141,6 +145,7 @@ nwfilterDriverReload(void) {
conn = virConnectOpen("qemu:///system");
if (conn) {
virNWFilterDHCPSnoopEnd(NULL);
/* shut down all threads -- they will be restarted if necessary */
virNWFilterLearnThreadsTerminate(true);
@ -195,6 +200,7 @@ nwfilterDriverShutdown(void) {
virNWFilterConfLayerShutdown();
virNWFilterTechDriversShutdown();
virNWFilterDHCPSnoopShutdown();
virNWFilterLearnShutdown();
nwfilterDriverLock(driverState);

View File

@ -32,6 +32,7 @@
#include "virterror_internal.h"
#include "nwfilter_gentech_driver.h"
#include "nwfilter_ebiptables_driver.h"
#include "nwfilter_dhcpsnoop.h"
#include "nwfilter_learnipaddr.h"
#include "virnetdev.h"
#include "datatypes.h"
@ -39,8 +40,10 @@
#define VIR_FROM_THIS VIR_FROM_NWFILTER
#define NWFILTER_STD_VAR_MAC "MAC"
#define NWFILTER_STD_VAR_IP "IP"
#define NWFILTER_STD_VAR_MAC NWFILTER_VARNAME_MAC
#define NWFILTER_STD_VAR_IP NWFILTER_VARNAME_IP
#define NWFILTER_DFLT_LEARN "any"
static int _virNWFilterTeardownFilter(const char *ifname);
@ -662,6 +665,9 @@ virNWFilterInstantiate(const unsigned char *vmuuid ATTRIBUTE_UNUSED,
void **ptrs = NULL;
int instantiate = 1;
char *buf;
virNWFilterVarValuePtr lv;
const char *learning;
bool reportIP = false;
virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0);
if (!missing_vars) {
@ -678,22 +684,47 @@ virNWFilterInstantiate(const unsigned char *vmuuid ATTRIBUTE_UNUSED,
if (rc < 0)
goto err_exit;
lv = virHashLookup(vars->hashTable, NWFILTER_VARNAME_CTRL_IP_LEARNING);
if (lv)
learning = virNWFilterVarValueGetNthValue(lv, 0);
else
learning = NULL;
if (learning == NULL)
learning = NWFILTER_DFLT_LEARN;
if (virHashSize(missing_vars->hashTable) == 1) {
if (virHashLookup(missing_vars->hashTable,
NWFILTER_STD_VAR_IP) != NULL) {
if (virNWFilterLookupLearnReq(ifindex) == NULL) {
rc = virNWFilterLearnIPAddress(techdriver,
ifname,
ifindex,
linkdev,
nettype, macaddr,
filter->name,
vars, driver,
DETECT_DHCP|DETECT_STATIC);
if (STRCASEEQ(learning, "none")) { /* no learning */
reportIP = true;
goto err_unresolvable_vars;
}
goto err_exit;
}
goto err_unresolvable_vars;
if (STRCASEEQ(learning, "dhcp")) {
rc = virNWFilterDHCPSnoopReq(techdriver, ifname, linkdev,
nettype, vmuuid, macaddr,
filter->name, vars, driver);
goto err_exit;
} else if (STRCASEEQ(learning, "any")) {
if (virNWFilterLookupLearnReq(ifindex) == NULL) {
rc = virNWFilterLearnIPAddress(techdriver,
ifname,
ifindex,
linkdev,
nettype, macaddr,
filter->name,
vars, driver,
DETECT_DHCP|DETECT_STATIC);
}
goto err_exit;
} else {
rc = -1;
virNWFilterReportError(VIR_ERR_PARSE_FAILED, _("filter '%s' "
"learning value '%s' invalid."),
filter->name, learning);
}
} else
goto err_unresolvable_vars;
} else if (virHashSize(missing_vars->hashTable) > 1) {
goto err_unresolvable_vars;
} else if (!forceWithPendingReq &&
@ -761,7 +792,7 @@ err_exit:
err_unresolvable_vars:
buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, false);
buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, reportIP);
if (buf) {
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot instantiate filter due to unresolvable "
@ -1092,6 +1123,8 @@ _virNWFilterTeardownFilter(const char *ifname)
return -1;
}
virNWFilterDHCPSnoopEnd(ifname);
virNWFilterTerminateLearnReq(ifname);
if (virNWFilterLockIface(ifname) < 0)