Sending this as an RFC for now...
This patch adds support for the recent ipset iptables extension
to libvirt's nwfilter subsystem. Ipset allows to maintain 'sets'
of IP addresses, ports and other packet parameters and allows for
faster lookup and ('chunked') rule evaluation to achieve higher
throughput than what can be achieved with individual iptables rules.
On the command line iptables supports ipset using
iptables ... -m set --match-set <ipset name> <flags> -j ...
where 'ipset name' is the name of a previously created ipset and
flags is a comma-separated list of up to 6 flags. Flags use 'src' and
'dst'
for selecting IP addresses, ports etc. from the source or
destination part of a packet. So a concrete example may look like this:
iptables -A INPUT -m set --match-set test src,src -j ACCEPT
Since ipset management is quite complex, the idea was to leave ipset
management outside of libvirt but still allow users to reference an ipset.
The user would have to make sure the ipset is available once the VM is
started so that the iptables rules can be created.
Using XML to describe an ipset in an nwfilter rule would then look as
follows:
<rule action='accept' direction='in'>
<all ipset='test:src,src'/>
</rule>
The two parameters on the command line become one parameter in the XML
attribute 'ipset' and are separated by a colon (':').
FYI: Here is the man page for ipset:
https://ipset.netfilter.org/ipset.man.html
If you have any comments, please let me know.
Regards,
Stefan
---
docs/formatnwfilter.html.in | 34 ++++++++++
docs/schemas/nwfilter.rng | 11 +++
src/conf/nwfilter_conf.c | 98
++++++++++++++++++++++++++++++
src/conf/nwfilter_conf.h | 11 +++
src/nwfilter/nwfilter_ebiptables_driver.c | 64 ++++++++++++++++++-
tests/nwfilterxml2xmlin/ipset-test.xml | 12 +++
tests/nwfilterxml2xmlout/ipset-test.xml | 12 +++
tests/nwfilterxml2xmltest.c | 2
8 files changed, 240 insertions(+), 4 deletions(-)
Index: libvirt/src/conf/nwfilter_conf.c
===================================================================
--- libvirt.orig/src/conf/nwfilter_conf.c
+++ libvirt/src/conf/nwfilter_conf.c
@@ -183,6 +183,7 @@ static const char dstportstart_str[] = "
static const char dstportend_str[] = "dstportend";
static const char dscp_str[] = "dscp";
static const char state_str[] = "state";
+static const char ipset_str[] = "ipset";
#define SRCMACADDR srcmacaddr_str
#define SRCMACMASK srcmacmask_str
@@ -206,6 +207,7 @@ static const char state_str[] = "
#define DSTPORTEND dstportend_str
#define DSCP dscp_str
#define STATE state_str
+#define IPSET ipset_str
/**
@@ -980,6 +982,94 @@ tcpFlagsFormatter(virBufferPtr buf,
return true;
}
+static bool
+ipsetValidator(enum attrDatatype datatype ATTRIBUTE_UNUSED, union data
*val,
+ virNWFilterRuleDefPtr nwf ATTRIBUTE_UNUSED,
+ nwItemDesc *item)
+{
+ unsigned int idx;
+ const char *errmsg = NULL;
+ char *col = strrchr(val->c, ':');
+ size_t pos = col - val->c;
+
+ if (!col || pos == 0) {
+ errmsg = _("malformed ipset description");
+ goto arg_err_exit;
+ }
+
+ if (pos > sizeof(item->u.ipset.setname) - 1) {
+ errmsg = _("ipset name exceeds 31 characters");
+ goto arg_err_exit;
+ }
+
+ if (virStrncpy(item->u.ipset.setname, val->c, pos,
+ sizeof(item->u.ipset.setname)) == NULL) {
+ errmsg = _("could not copy ipset name");
+ goto err_exit;
+ }
+
+ if (item->u.ipset.setname[strspn(item->u.ipset.setname,
+ VALID_IPSETNAME)] != 0) {
+ errmsg = _("ipset name contains invalid characters");
+ goto arg_err_exit;
+ }
+
+ idx = pos + 1;
+ item->u.ipset.numFlags = 0;
+ item->u.ipset.flags = 0;
+
+ errmsg = _("malformed ipset flags");
+
+ while (item->u.ipset.numFlags < 6) {
+ if (STRCASEEQLEN(&val->c[idx], "src", 3)) {
+ item->u.ipset.flags |= (1 << item->u.ipset.numFlags);
+ } else if (!STRCASEEQLEN(&val->c[idx], "dst", 3)) {
+ goto arg_err_exit;
+ }
+ item->u.ipset.numFlags++;
+ idx += 3;
+ if (val->c[idx] != ',')
+ break;
+ idx++;
+ }
+
+ if (val->c[idx] != '\0')
+ goto arg_err_exit;
+
+ return true;
+
+arg_err_exit:
+ virNWFilterReportError(VIR_ERR_INVALID_ARG,
+ "%s", errmsg);
+ return false;
+
+err_exit:
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
+ "%s", errmsg);
+ return false;
+}
+
+static bool
+ipsetFormatter(virBufferPtr buf,
+ virNWFilterRuleDefPtr nwf ATTRIBUTE_UNUSED,
+ nwItemDesc *item)
+{
+ uint8_t ctr;
+
+ virBufferAdd(buf, item->u.ipset.setname, -1);
+ virBufferAddLit(buf, ":");
+
+ for (ctr = 0; ctr < item->u.ipset.numFlags; ctr++) {
+ if (ctr != 0)
+ virBufferAddLit(buf, ",");
+ if ((item->u.ipset.flags & (1 << ctr)))
+ virBufferAddLit(buf, "src");
+ else
+ virBufferAddLit(buf, "dst");
+ }
+
+ return true;
+}
#define COMMON_MAC_PROPS(STRUCT) \
{\
@@ -1411,6 +1501,13 @@ static const virXMLAttr2Struct ipv6Attri
.dataIdx = offsetof(virNWFilterRuleDef,
p.STRUCT.ipHdr.dataState),\
.validator = stateValidator,\
.formatter = stateFormatter,\
+ },\
+ {\
+ .name = IPSET,\
+ .datatype = DATATYPE_IPSET,\
+ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataIPSet),\
+ .validator = ipsetValidator,\
+ .formatter = ipsetFormatter,\
}
#define COMMON_PORT_PROPS(STRUCT) \
@@ -1853,6 +1950,7 @@ virNWFilterRuleDetailsParse(xmlNodePtr n
break;
case DATATYPE_STRING:
+ case DATATYPE_IPSET:
if (!validator) {
/* not supported */
rc = -1;
Index: libvirt/src/conf/nwfilter_conf.h
===================================================================
--- libvirt.orig/src/conf/nwfilter_conf.h
+++ libvirt/src/conf/nwfilter_conf.h
@@ -103,8 +103,9 @@ enum attrDatatype {
DATATYPE_BOOLEAN = (1 << 12),
DATATYPE_UINT32 = (1 << 13),
DATATYPE_UINT32_HEX = (1 << 14),
+ DATATYPE_IPSET = (1 << 15),
- DATATYPE_LAST = (1 << 15),
+ DATATYPE_LAST = (1 << 16),
};
# define NWFILTER_MAC_BGA "01:80:c2:00:00:00"
@@ -136,9 +137,16 @@ struct _nwItemDesc {
uint8_t mask;
uint8_t flags;
} tcpFlags;
+ struct {
+ char setname[32];
+ uint8_t numFlags;
+ uint8_t flags;
+ } ipset;
} u;
};
+# define VALID_IPSETNAME \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-+ "
typedef struct _ethHdrDataDef ethHdrDataDef;
typedef ethHdrDataDef *ethHdrDataDefPtr;
@@ -232,6 +240,7 @@ struct _ipHdrDataDef {
nwItemDesc dataState;
nwItemDesc dataConnlimitAbove;
nwItemDesc dataComment;
+ nwItemDesc dataIPSet;
};
Index: libvirt/src/nwfilter/nwfilter_ebiptables_driver.c
===================================================================
--- libvirt.orig/src/nwfilter/nwfilter_ebiptables_driver.c
+++ libvirt/src/nwfilter/nwfilter_ebiptables_driver.c
@@ -256,10 +256,13 @@ static int
_printDataType(virNWFilterVarCombIterPtr vars,
char *buf, int bufsize,
nwItemDescPtr item,
- bool asHex)
+ bool asHex, bool directionIn)
{
int done;
char *data;
+ uint8_t ctr;
+ virBuffer vb = VIR_BUFFER_INITIALIZER;
+ char *flags;
if (printVar(vars, buf, bufsize, item, &done) < 0)
return -1;
@@ -346,6 +349,41 @@ _printDataType(virNWFilterVarCombIterPtr
}
break;
+ case DATATYPE_IPSET:
+ for (ctr = 0; ctr < item->u.ipset.numFlags; ctr++) {
+ if (ctr != 0)
+ virBufferAddLit(&vb, ",");
+ if ((item->u.ipset.flags & (1 << ctr))) {
+ if (directionIn)
+ virBufferAddLit(&vb, "dst");
+ else
+ virBufferAddLit(&vb, "src");
+ } else {
+ if (directionIn)
+ virBufferAddLit(&vb, "src");
+ else
+ virBufferAddLit(&vb, "dst");
+ }
+ }
+
+ if (virBufferError(&vb)) {
+ virReportOOMError();
+ virBufferFreeAndReset(&vb);
+ return -1;
+ }
+
+ flags = virBufferContentAndReset(&vb);
+
+ if (snprintf(buf, bufsize, "\"%s\" %s",
+ item->u.ipset.setname, flags) >= bufsize) {
+ virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("Buffer too small for stringcopy
type"));
+ VIR_FREE(flags);
+ return -1;
+ }
+ VIR_FREE(flags);
+ break;
+
default:
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR,
_("Unhandled datatype %x"),
item->datatype);
@@ -362,16 +400,23 @@ printDataType(virNWFilterVarCombIterPtr
char *buf, int bufsize,
nwItemDescPtr item)
{
- return _printDataType(vars, buf, bufsize, item, 0);
+ return _printDataType(vars, buf, bufsize, item, 0, 0);
}
+static int
+printDataTypeDirection(virNWFilterVarCombIterPtr vars,
+ char *buf, int bufsize,
+ nwItemDescPtr item, bool directionIn)
+{
+ return _printDataType(vars, buf, bufsize, item, 0, directionIn);
+}
static int
printDataTypeAsHex(virNWFilterVarCombIterPtr vars,
char *buf, int bufsize,
nwItemDescPtr item)
{
- return _printDataType(vars, buf, bufsize, item, 1);
+ return _printDataType(vars, buf, bufsize, item, 1, 0);
}
@@ -927,6 +972,7 @@ iptablesHandleIpHdr(virBufferPtr buf,
char ipaddr[INET6_ADDRSTRLEN],
number[MAX(INT_BUFSIZE_BOUND(uint32_t),
INT_BUFSIZE_BOUND(int))];
+ char str[200];
const char *src = "--source";
const char *dst = "--destination";
const char *srcrange = "--src-range";
@@ -938,6 +984,18 @@ iptablesHandleIpHdr(virBufferPtr buf,
dstrange = "--src-range";
}
+ if (HAS_ENTRY_ITEM(&ipHdr->dataIPSet)) {
+
+ if (printDataTypeDirection(vars,
+ str, sizeof(str),
+ &ipHdr->dataIPSet, directionIn) < 0)
+ goto err_exit;
+
+ virBufferAsprintf(afterStateMatch,
+ " -m set --match-set %s",
+ str);
+ }
+
if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPAddr)) {
if (printDataType(vars,
Index: libvirt/docs/schemas/nwfilter.rng
===================================================================
--- libvirt.orig/docs/schemas/nwfilter.rng
+++ libvirt/docs/schemas/nwfilter.rng
@@ -485,6 +485,11 @@
<ref name="stateflags-type"/>
</attribute>
</optional>
+ <optional>
+ <attribute name="ipset">
+ <ref name="ipset-type"/>
+ </attribute>
+ </optional>
</interleave>
</define>
@@ -1060,4 +1065,10 @@
<param
name="pattern">((SYN|ACK|URG|PSH|FIN|RST)(,(SYN|ACK|URG|PSH|FIN|RST))*|ALL|NONE)/((SYN|ACK|URG|PSH|FIN|RST)(,(SYN|ACK|URG|PSH|FIN|RST))*|ALL|NONE)</param>
</data>
</define>
+
+ <define name='ipset-type'>
+ <data type="string">
+ <param
name="pattern">[a-zA-Z0-9_\.:\-\+]{1,31}:(src|dst)(,(src|dst)){0,5}</param>
+ </data>
+ </define>
</grammar>
Index: libvirt/tests/nwfilterxml2xmlin/ipset-test.xml
===================================================================
--- /dev/null
+++ libvirt/tests/nwfilterxml2xmlin/ipset-test.xml
@@ -0,0 +1,12 @@
+<filter name='testcase' chain='root'>
+ <uuid>5c6d49af-b071-6127-b4ec-6f8ed4b55335</uuid>
+ <rule action='accept' direction='out'>
+ <all ipset='test:src,dst' />
+ </rule>
+ <rule action='accept' direction='in'>
+ <all ipset='test:SRC,DST,SRC' />
+ </rule>
+ <rule action='accept' direction='in'>
+ <all ipset='test:_.-+:SRC,dSt,SRC' />
+ </rule>
+</filter>
Index: libvirt/tests/nwfilterxml2xmlout/ipset-test.xml
===================================================================
--- /dev/null
+++ libvirt/tests/nwfilterxml2xmlout/ipset-test.xml
@@ -0,0 +1,12 @@
+<filter name='testcase' chain='root'>
+ <uuid>5c6d49af-b071-6127-b4ec-6f8ed4b55335</uuid>
+ <rule action='accept' direction='out' priority='500'>
+ <all ipset='test:src,dst'/>
+ </rule>
+ <rule action='accept' direction='in' priority='500'>
+ <all ipset='test:src,dst,src'/>
+ </rule>
+ <rule action='accept' direction='in' priority='500'>
+ <all ipset='test:_.-+:src,dst,src'/>
+ </rule>
+</filter>
Index: libvirt/tests/nwfilterxml2xmltest.c
===================================================================
--- libvirt.orig/tests/nwfilterxml2xmltest.c
+++ libvirt/tests/nwfilterxml2xmltest.c
@@ -157,6 +157,8 @@ mymain(void)
DO_TEST("iter-test2", false);
DO_TEST("iter-test3", false);
+ DO_TEST("ipset-test", false);
+
return (ret==0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
Index: libvirt/docs/formatnwfilter.html.in
===================================================================
--- libvirt.orig/docs/formatnwfilter.html.in
+++ libvirt/docs/formatnwfilter.html.in
@@ -528,6 +528,10 @@
<li>IPV6_MASK: IPv6 mask in numbers format (FFFF:FFFF:FC00::) or CIDR
mask (0-128)</li>
<li>STRING: A string</li>
<li>BOOLEAN: 'true', 'yes', '1' or 'false',
'no', '0'</li>
+ <li>IPSET: The name of the ipset followed by a colon (':') followed
+ by up to 6 'src' or 'dst' elements selecting features from
either
+ the source or destination part of the packet header; example:
+ myipset:src,src,dst</li>
</ul>
<p>
<br/><br/>
@@ -1169,6 +1173,11 @@
<td>STRING</td>
<td>TCP-only: format of mask/flags with mask and flags each being a
comma separated list of SYN,ACK,URG,PSH,FIN,RST or NONE or ALL</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IPset managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>
@@ -1269,6 +1278,11 @@
<td>STRING</td>
<td>comma separated list of NEW,ESTABLISHED,RELATED,INVALID or NONE</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IP set managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>
@@ -1358,6 +1372,11 @@
<td>STRING</td>
<td>comma separated list of NEW,ESTABLISHED,RELATED,INVALID or NONE</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IP set managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>
@@ -1459,6 +1478,11 @@
<td>STRING</td>
<td>TCP-only: format of mask/flags with mask and flags each being a
comma separated list of SYN,ACK,URG,PSH,FIN,RST or NONE or ALL</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IP set managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>
@@ -1545,6 +1569,11 @@
<td>STRING</td>
<td>comma separated list of NEW,ESTABLISHED,RELATED,INVALID or NONE</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IP set managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>
@@ -1619,6 +1648,11 @@
<td>STRING</td>
<td>comma separated list of NEW,ESTABLISHED,RELATED,INVALID or NONE</td>
</tr>
+ <tr>
+ <td>ipset <span class="since">(Since
0.9.10)</span></td>
+ <td>IPSET</td>
+ <td>References an IP set managed outside of libvirt</td>
+ </tr>
</table>
<p>
<br/><br/>