/*
* nwfilter_ebiptables_driver.c: driver for ebtables/iptables on tap devices
*
* Copyright (C) 2011-2012 Red Hat, Inc.
* Copyright (C) 2010-2012 IBM Corp.
* Copyright (C) 2010-2012 Stefan Berger
*
* 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, see
* .
*
* Author: Stefan Berger
*/
#include
#include
#include
#include
#include "internal.h"
#include "buf.h"
#include "memory.h"
#include "logging.h"
#include "virterror_internal.h"
#include "domain_conf.h"
#include "nwfilter_conf.h"
#include "nwfilter_driver.h"
#include "nwfilter_gentech_driver.h"
#include "nwfilter_ebiptables_driver.h"
#include "virfile.h"
#include "command.h"
#include "configmake.h"
#include "intprops.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
#define EBTABLES_CHAIN_INCOMING "PREROUTING"
#define EBTABLES_CHAIN_OUTGOING "POSTROUTING"
#define CHAINPREFIX_HOST_IN 'I'
#define CHAINPREFIX_HOST_OUT 'O'
#define CHAINPREFIX_HOST_IN_TEMP 'J'
#define CHAINPREFIX_HOST_OUT_TEMP 'P'
/* This file generates a temporary shell script. Since ebiptables is
Linux-specific, we can be reasonably certain that /bin/sh is more
or less POSIX-compliant, so we can use $() and $(()). However, we
cannot assume that /bin/sh is bash, so stick to POSIX syntax. */
#define CMD_SEPARATOR "\n"
#define CMD_DEF_PRE "cmd='"
#define CMD_DEF_POST "'"
#define CMD_DEF(X) CMD_DEF_PRE X CMD_DEF_POST
#define CMD_EXEC "eval res=\\$\\(\"${cmd} 2>&1\"\\)" CMD_SEPARATOR
#define CMD_STOPONERR(X) \
X ? "if [ $? -ne 0 ]; then" \
" echo \"Failure to execute command '${cmd}' : '${res}'.\";" \
" exit 1;" \
"fi" CMD_SEPARATOR \
: ""
#define PROC_BRIDGE_NF_CALL_IPTABLES \
"/proc/sys/net/bridge/bridge-nf-call-iptables"
#define PROC_BRIDGE_NF_CALL_IP6TABLES \
"/proc/sys/net/bridge/bridge-nf-call-ip6tables"
#define BRIDGE_NF_CALL_ALERT_INTERVAL 10 /* seconds */
static char *ebtables_cmd_path;
static char *iptables_cmd_path;
static char *ip6tables_cmd_path;
static char *grep_cmd_path;
#define PRINT_ROOT_CHAIN(buf, prefix, ifname) \
snprintf(buf, sizeof(buf), "libvirt-%c-%s", prefix, ifname)
#define PRINT_CHAIN(buf, prefix, ifname, suffix) \
snprintf(buf, sizeof(buf), "%c-%s-%s", prefix, ifname, suffix)
/* The collect_chains() script recursively determines all names
* of ebtables (nat) chains that are 'children' of a given 'root' chain.
* The typical output of an ebtables call is as follows:
*
* #> ebtables -t nat -L libvirt-I-tck-test205002
* Bridge table: nat
*
* Bridge chain: libvirt-I-tck-test205002, entries: 5, policy: ACCEPT
* -p IPv4 -j I-tck-test205002-ipv4
* -p ARP -j I-tck-test205002-arp
* -p 0x8035 -j I-tck-test205002-rarp
* -p 0x835 -j ACCEPT
* -j DROP
*/
static const char ebtables_script_func_collect_chains[] =
"collect_chains()\n"
"{\n"
" for tmp2 in $*; do\n"
" for tmp in $($EBT -t nat -L $tmp2 | \\\n"
" sed -n \"/Bridge chain/,\\$ s/.*-j \\\\([%s]-.*\\\\)/\\\\1/p\");\n"
" do\n"
" echo $tmp\n"
" collect_chains $tmp\n"
" done\n"
" done\n"
"}\n";
static const char ebiptables_script_func_rm_chains[] =
"rm_chains()\n"
"{\n"
" for tmp in $*; do $EBT -t nat -F $tmp; done\n"
" for tmp in $*; do $EBT -t nat -X $tmp; done\n"
"}\n";
static const char ebiptables_script_func_rename_chains[] =
"rename_chain()\n"
"{\n"
" $EBT -t nat -F $2\n"
" $EBT -t nat -X $2\n"
" $EBT -t nat -E $1 $2\n"
"}\n"
"rename_chains()\n"
"{\n"
" for tmp in $*; do\n"
" case $tmp in\n"
" %c*) rename_chain $tmp %c${tmp#?} ;;\n"
" %c*) rename_chain $tmp %c${tmp#?} ;;\n"
" esac\n"
" done\n"
"}\n";
static const char ebiptables_script_set_ifs[] =
"tmp='\n'\n"
"IFS=' ''\t'$tmp\n";
#define NWFILTER_FUNC_COLLECT_CHAINS ebtables_script_func_collect_chains
#define NWFILTER_FUNC_RM_CHAINS ebiptables_script_func_rm_chains
#define NWFILTER_FUNC_RENAME_CHAINS ebiptables_script_func_rename_chains
#define NWFILTER_FUNC_SET_IFS ebiptables_script_set_ifs
#define NWFILTER_SET_EBTABLES_SHELLVAR(BUFPTR) \
virBufferAsprintf(BUFPTR, "EBT=\"%s\"\n", ebtables_cmd_path);
#define NWFILTER_SET_IPTABLES_SHELLVAR(BUFPTR) \
virBufferAsprintf(BUFPTR, "IPT=\"%s\"\n", iptables_cmd_path);
#define NWFILTER_SET_IP6TABLES_SHELLVAR(BUFPTR) \
virBufferAsprintf(BUFPTR, "IPT=\"%s\"\n", ip6tables_cmd_path);
#define VIRT_IN_CHAIN "libvirt-in"
#define VIRT_OUT_CHAIN "libvirt-out"
#define VIRT_IN_POST_CHAIN "libvirt-in-post"
#define HOST_IN_CHAIN "libvirt-host-in"
#define PRINT_IPT_ROOT_CHAIN(buf, prefix, ifname) \
snprintf(buf, sizeof(buf), "%c%c-%s", prefix[0], prefix[1], ifname)
#define PHYSDEV_IN "--physdev-in"
#define PHYSDEV_OUT "--physdev-out"
static const char *m_state_out_str = "-m state --state NEW,ESTABLISHED";
static const char *m_state_in_str = "-m state --state ESTABLISHED";
static const char *m_physdev_in_str = "-m physdev " PHYSDEV_IN;
static const char *m_physdev_out_str = "-m physdev " PHYSDEV_OUT;
#define MATCH_STATE_OUT m_state_out_str
#define MATCH_STATE_IN m_state_in_str
#define MATCH_PHYSDEV_IN m_physdev_in_str
#define MATCH_PHYSDEV_OUT m_physdev_out_str
#define COMMENT_VARNAME "comment"
static int ebtablesRemoveBasicRules(const char *ifname);
static int ebiptablesDriverInit(bool privileged);
static void ebiptablesDriverShutdown(void);
static int ebtablesCleanAll(const char *ifname);
static int ebiptablesAllTeardown(const char *ifname);
static virMutex execCLIMutex;
struct ushort_map {
unsigned short attr;
const char *val;
};
enum l3_proto_idx {
L3_PROTO_IPV4_IDX = 0,
L3_PROTO_IPV6_IDX,
L3_PROTO_ARP_IDX,
L3_PROTO_RARP_IDX,
L2_PROTO_MAC_IDX,
L2_PROTO_VLAN_IDX,
L2_PROTO_STP_IDX,
L3_PROTO_LAST_IDX
};
#define USHORTMAP_ENTRY_IDX(IDX, ATT, VAL) [IDX] = { .attr = ATT, .val = VAL }
/* A lookup table for translating ethernet protocol IDs to human readable
* strings. None of the human readable strings must be found as a prefix
* in another entry here (example 'ab' would be found in 'abc') to allow
* for prefix matching.
*/
static const struct ushort_map l3_protocols[] = {
USHORTMAP_ENTRY_IDX(L3_PROTO_IPV4_IDX, ETHERTYPE_IP , "ipv4"),
USHORTMAP_ENTRY_IDX(L3_PROTO_IPV6_IDX, ETHERTYPE_IPV6 , "ipv6"),
USHORTMAP_ENTRY_IDX(L3_PROTO_ARP_IDX , ETHERTYPE_ARP , "arp"),
USHORTMAP_ENTRY_IDX(L3_PROTO_RARP_IDX, ETHERTYPE_REVARP, "rarp"),
USHORTMAP_ENTRY_IDX(L2_PROTO_VLAN_IDX, ETHERTYPE_VLAN , "vlan"),
USHORTMAP_ENTRY_IDX(L2_PROTO_STP_IDX, 0 , "stp"),
USHORTMAP_ENTRY_IDX(L2_PROTO_MAC_IDX, 0 , "mac"),
USHORTMAP_ENTRY_IDX(L3_PROTO_LAST_IDX, 0 , NULL),
};
static int
printVar(virNWFilterVarCombIterPtr vars,
char *buf, int bufsize,
nwItemDescPtr item,
int *done)
{
*done = 0;
if ((item->flags & NWFILTER_ENTRY_ITEM_FLAG_HAS_VAR)) {
const char *val;
val = virNWFilterVarCombIterGetVarValue(vars, item->varAccess);
if (!val) {
/* error has been reported */
return -1;
}
if (!virStrcpy(buf, val, bufsize)) {
const char *varName;
varName = virNWFilterVarAccessGetVarName(item->varAccess);
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Buffer too small to print variable "
"'%s' into"), varName);
return -1;
}
*done = 1;
}
return 0;
}
static int
_printDataType(virNWFilterVarCombIterPtr vars,
char *buf, int bufsize,
nwItemDescPtr item,
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;
if (done)
return 0;
switch (item->datatype) {
case DATATYPE_IPADDR:
data = virSocketAddrFormat(&item->u.ipaddr);
if (!data)
return -1;
if (snprintf(buf, bufsize, "%s", data) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("buffer too small for IP address"));
VIR_FREE(data);
return -1;
}
VIR_FREE(data);
break;
case DATATYPE_IPV6ADDR:
data = virSocketAddrFormat(&item->u.ipaddr);
if (!data)
return -1;
if (snprintf(buf, bufsize, "%s", data) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("buffer too small for IPv6 address"));
VIR_FREE(data);
return -1;
}
VIR_FREE(data);
break;
case DATATYPE_MACADDR:
case DATATYPE_MACMASK:
if (bufsize < VIR_MAC_STRING_BUFLEN) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for MAC address"));
return -1;
}
virMacAddrFormat(&item->u.macaddr, buf);
break;
case DATATYPE_IPV6MASK:
case DATATYPE_IPMASK:
if (snprintf(buf, bufsize, "%d",
item->u.u8) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for uint8 type"));
return -1;
}
break;
case DATATYPE_UINT32:
case DATATYPE_UINT32_HEX:
if (snprintf(buf, bufsize, asHex ? "0x%x" : "%u",
item->u.u32) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for uint32 type"));
return -1;
}
break;
case DATATYPE_UINT16:
case DATATYPE_UINT16_HEX:
if (snprintf(buf, bufsize, asHex ? "0x%x" : "%d",
item->u.u16) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for uint16 type"));
return -1;
}
break;
case DATATYPE_UINT8:
case DATATYPE_UINT8_HEX:
if (snprintf(buf, bufsize, asHex ? "0x%x" : "%d",
item->u.u8) >= bufsize) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for uint8 type"));
return -1;
}
break;
case DATATYPE_IPSETNAME:
if (virStrcpy(buf, item->u.ipset.setname, bufsize) == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer to small for ipset name"));
return -1;
}
break;
case DATATYPE_IPSETFLAGS:
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 (virStrcpy(buf, flags, bufsize) == NULL) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Buffer too small for IPSETFLAGS type"));
VIR_FREE(flags);
return -1;
}
VIR_FREE(flags);
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Unhandled datatype %x"), item->datatype);
return -1;
break;
}
return 0;
}
static int
printDataType(virNWFilterVarCombIterPtr vars,
char *buf, int bufsize,
nwItemDescPtr item)
{
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, 0);
}
static void
printCommentVar(virBufferPtr dest, const char *buf)
{
size_t i, len = strlen(buf);
virBufferAddLit(dest, COMMENT_VARNAME "='");
if (len > IPTABLES_MAX_COMMENT_LENGTH)
len = IPTABLES_MAX_COMMENT_LENGTH;
for (i = 0; i < len; i++) {
if (buf[i] == '\'')
virBufferAddLit(dest, "'\\''");
else
virBufferAddChar(dest, buf[i]);
}
virBufferAddLit(dest,"'" CMD_SEPARATOR);
}
static void
ebiptablesRuleInstFree(ebiptablesRuleInstPtr inst)
{
if (!inst)
return;
VIR_FREE(inst->commandTemplate);
VIR_FREE(inst);
}
static int
ebiptablesAddRuleInst(virNWFilterRuleInstPtr res,
char *commandTemplate,
const char *neededChain,
virNWFilterChainPriority chainPriority,
char chainprefix,
virNWFilterRulePriority priority,
enum RuleType ruleType)
{
ebiptablesRuleInstPtr inst;
if (VIR_ALLOC(inst) < 0) {
virReportOOMError();
return -1;
}
inst->commandTemplate = commandTemplate;
inst->neededProtocolChain = neededChain;
inst->chainPriority = chainPriority;
inst->chainprefix = chainprefix;
inst->priority = priority;
inst->ruleType = ruleType;
return virNWFilterRuleInstAddData(res, inst);
}
static int
ebtablesHandleEthHdr(virBufferPtr buf,
virNWFilterVarCombIterPtr vars,
ethHdrDataDefPtr ethHdr,
bool reverse)
{
char macaddr[VIR_MAC_STRING_BUFLEN];
if (HAS_ENTRY_ITEM(ðHdr->dataSrcMACAddr)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
ðHdr->dataSrcMACAddr) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
reverse ? "-d" : "-s",
ENTRY_GET_NEG_SIGN(ðHdr->dataSrcMACAddr),
macaddr);
if (HAS_ENTRY_ITEM(ðHdr->dataSrcMACMask)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
ðHdr->dataSrcMACMask) < 0)
goto err_exit;
virBufferAsprintf(buf,
"/%s",
macaddr);
}
}
if (HAS_ENTRY_ITEM(ðHdr->dataDstMACAddr)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
ðHdr->dataDstMACAddr) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
reverse ? "-s" : "-d",
ENTRY_GET_NEG_SIGN(ðHdr->dataDstMACAddr),
macaddr);
if (HAS_ENTRY_ITEM(ðHdr->dataDstMACMask)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
ðHdr->dataDstMACMask) < 0)
goto err_exit;
virBufferAsprintf(buf,
"/%s",
macaddr);
}
}
return 0;
err_exit:
virBufferFreeAndReset(buf);
return -1;
}
/************************ iptables support ************************/
static int iptablesLinkIPTablesBaseChain(virBufferPtr buf,
const char *udchain,
const char *syschain,
unsigned int pos,
int stopOnError)
{
virBufferAsprintf(buf,
"res=$($IPT -L %s -n --line-number | %s '%s')\n"
"if [ $? -ne 0 ]; then\n"
" $IPT -I %s %d -j %s\n"
"else\n"
" set dummy $res; r=$2\n"
" if [ \"${r}\" != \"%d\" ]; then\n"
" " CMD_DEF("$IPT -I %s %d -j %s") CMD_SEPARATOR
" " CMD_EXEC
" %s"
" r=$(( $r + 1 ))\n"
" " CMD_DEF("$IPT -D %s ${r}") CMD_SEPARATOR
" " CMD_EXEC
" %s"
" fi\n"
"fi\n",
syschain, grep_cmd_path, udchain,
syschain, pos, udchain,
pos,
syschain, pos, udchain,
CMD_STOPONERR(stopOnError),
syschain,
CMD_STOPONERR(stopOnError));
return 0;
}
static int iptablesCreateBaseChains(virBufferPtr buf)
{
virBufferAddLit(buf, "$IPT -N " VIRT_IN_CHAIN CMD_SEPARATOR
"$IPT -N " VIRT_OUT_CHAIN CMD_SEPARATOR
"$IPT -N " VIRT_IN_POST_CHAIN CMD_SEPARATOR
"$IPT -N " HOST_IN_CHAIN CMD_SEPARATOR);
iptablesLinkIPTablesBaseChain(buf,
VIRT_IN_CHAIN , "FORWARD", 1, 1);
iptablesLinkIPTablesBaseChain(buf,
VIRT_OUT_CHAIN , "FORWARD", 2, 1);
iptablesLinkIPTablesBaseChain(buf,
VIRT_IN_POST_CHAIN, "FORWARD", 3, 1);
iptablesLinkIPTablesBaseChain(buf,
HOST_IN_CHAIN , "INPUT" , 1, 1);
return 0;
}
static int
iptablesCreateTmpRootChain(virBufferPtr buf,
char prefix,
int incoming, const char *ifname,
int stopOnError)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix[2] = {
prefix,
(incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP
};
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
CMD_DEF("$IPT -N %s") CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
CMD_STOPONERR(stopOnError));
return 0;
}
static int
iptablesCreateTmpRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesCreateTmpRootChain(buf, 'F', 0, ifname, 1);
iptablesCreateTmpRootChain(buf, 'F', 1, ifname, 1);
iptablesCreateTmpRootChain(buf, 'H', 1, ifname, 1);
return 0;
}
static int
_iptablesRemoveRootChain(virBufferPtr buf,
char prefix,
int incoming, const char *ifname,
int isTempChain)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix[2] = {
prefix,
};
if (isTempChain)
chainPrefix[1] = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
else
chainPrefix[1] = (incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT;
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
"$IPT -F %s" CMD_SEPARATOR
"$IPT -X %s" CMD_SEPARATOR,
chain,
chain);
return 0;
}
static int
iptablesRemoveRootChain(virBufferPtr buf,
char prefix,
int incoming,
const char *ifname)
{
return _iptablesRemoveRootChain(buf, prefix, incoming, ifname, 0);
}
static int
iptablesRemoveTmpRootChain(virBufferPtr buf,
char prefix,
int incoming,
const char *ifname)
{
return _iptablesRemoveRootChain(buf, prefix,
incoming, ifname, 1);
}
static int
iptablesRemoveTmpRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesRemoveTmpRootChain(buf, 'F', 0, ifname);
iptablesRemoveTmpRootChain(buf, 'F', 1, ifname);
iptablesRemoveTmpRootChain(buf, 'H', 1, ifname);
return 0;
}
static int
iptablesRemoveRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesRemoveRootChain(buf, 'F', 0, ifname);
iptablesRemoveRootChain(buf, 'F', 1, ifname);
iptablesRemoveRootChain(buf, 'H', 1, ifname);
return 0;
}
static int
iptablesLinkTmpRootChain(virBufferPtr buf,
const char *basechain,
char prefix,
int incoming, const char *ifname,
int stopOnError)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix[2] = {
prefix,
(incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP
};
const char *match = (incoming) ? MATCH_PHYSDEV_IN
: MATCH_PHYSDEV_OUT;
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
CMD_DEF("$IPT -A %s "
"%s %s -g %s") CMD_SEPARATOR
CMD_EXEC
"%s",
basechain,
match, ifname, chain,
CMD_STOPONERR(stopOnError));
return 0;
}
static int
iptablesLinkTmpRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesLinkTmpRootChain(buf, VIRT_OUT_CHAIN, 'F', 0, ifname, 1);
iptablesLinkTmpRootChain(buf, VIRT_IN_CHAIN , 'F', 1, ifname, 1);
iptablesLinkTmpRootChain(buf, HOST_IN_CHAIN , 'H', 1, ifname, 1);
return 0;
}
static int
iptablesSetupVirtInPost(virBufferPtr buf,
const char *ifname)
{
const char *match = MATCH_PHYSDEV_IN;
virBufferAsprintf(buf,
"res=$($IPT -n -L " VIRT_IN_POST_CHAIN
" | grep \"\\%s %s\")\n"
"if [ \"${res}\" = \"\" ]; then "
CMD_DEF("$IPT"
" -A " VIRT_IN_POST_CHAIN
" %s %s -j ACCEPT") CMD_SEPARATOR
CMD_EXEC
"%s"
"fi\n",
PHYSDEV_IN, ifname,
match, ifname,
CMD_STOPONERR(1));
return 0;
}
static int
iptablesClearVirtInPost(virBufferPtr buf,
const char *ifname)
{
const char *match = MATCH_PHYSDEV_IN;
virBufferAsprintf(buf,
"$IPT -D " VIRT_IN_POST_CHAIN
" %s %s -j ACCEPT" CMD_SEPARATOR,
match, ifname);
return 0;
}
static int
_iptablesUnlinkRootChain(virBufferPtr buf,
const char *basechain,
char prefix,
int incoming, const char *ifname,
int isTempChain)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix[2] = {
prefix,
};
if (isTempChain)
chainPrefix[1] = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
else
chainPrefix[1] = (incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT;
const char *match = (incoming) ? MATCH_PHYSDEV_IN
: MATCH_PHYSDEV_OUT;
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
"$IPT -D %s "
"%s %s -g %s" CMD_SEPARATOR,
basechain,
match, ifname, chain);
return 0;
}
static int
iptablesUnlinkRootChain(virBufferPtr buf,
const char *basechain,
char prefix,
int incoming, const char *ifname)
{
return _iptablesUnlinkRootChain(buf,
basechain, prefix, incoming, ifname, 0);
}
static int
iptablesUnlinkTmpRootChain(virBufferPtr buf,
const char *basechain,
char prefix,
int incoming, const char *ifname)
{
return _iptablesUnlinkRootChain(buf,
basechain, prefix, incoming, ifname, 1);
}
static int
iptablesUnlinkRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesUnlinkRootChain(buf, VIRT_OUT_CHAIN, 'F', 0, ifname);
iptablesUnlinkRootChain(buf, VIRT_IN_CHAIN , 'F', 1, ifname);
iptablesUnlinkRootChain(buf, HOST_IN_CHAIN , 'H', 1, ifname);
return 0;
}
static int
iptablesUnlinkTmpRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesUnlinkTmpRootChain(buf, VIRT_OUT_CHAIN, 'F', 0, ifname);
iptablesUnlinkTmpRootChain(buf, VIRT_IN_CHAIN , 'F', 1, ifname);
iptablesUnlinkTmpRootChain(buf, HOST_IN_CHAIN , 'H', 1, ifname);
return 0;
}
static int
iptablesRenameTmpRootChain(virBufferPtr buf,
char prefix,
int incoming,
const char *ifname)
{
char tmpchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH];
char tmpChainPrefix[2] = {
prefix,
(incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP
};
char chainPrefix[2] = {
prefix,
(incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT
};
PRINT_IPT_ROOT_CHAIN(tmpchain, tmpChainPrefix, ifname);
PRINT_IPT_ROOT_CHAIN( chain, chainPrefix, ifname);
virBufferAsprintf(buf,
"$IPT -E %s %s" CMD_SEPARATOR,
tmpchain,
chain);
return 0;
}
static int
iptablesRenameTmpRootChains(virBufferPtr buf,
const char *ifname)
{
iptablesRenameTmpRootChain(buf, 'F', 0, ifname);
iptablesRenameTmpRootChain(buf, 'F', 1, ifname);
iptablesRenameTmpRootChain(buf, 'H', 1, ifname);
return 0;
}
static void
iptablesInstCommand(virBufferPtr buf,
const char *templ, char cmd, int pos,
int stopOnError)
{
char position[10] = { 0 };
if (pos >= 0)
snprintf(position, sizeof(position), "%d", pos);
virBufferAsprintf(buf, templ, cmd, position);
virBufferAsprintf(buf, CMD_SEPARATOR "%s",
CMD_STOPONERR(stopOnError));
}
static int
iptablesHandleSrcMacAddr(virBufferPtr buf,
virNWFilterVarCombIterPtr vars,
nwItemDescPtr srcMacAddr,
int directionIn,
bool *srcmacskipped)
{
char macaddr[VIR_MAC_STRING_BUFLEN];
*srcmacskipped = false;
if (HAS_ENTRY_ITEM(srcMacAddr)) {
if (directionIn) {
*srcmacskipped = true;
return 0;
}
if (printDataType(vars,
macaddr, sizeof(macaddr),
srcMacAddr) < 0)
goto err_exit;
virBufferAsprintf(buf,
" -m mac %s --mac-source %s",
ENTRY_GET_NEG_SIGN(srcMacAddr),
macaddr);
}
return 0;
err_exit:
virBufferFreeAndReset(buf);
return -1;
}
static int
iptablesHandleIpHdr(virBufferPtr buf,
virBufferPtr afterStateMatch,
virNWFilterVarCombIterPtr vars,
ipHdrDataDefPtr ipHdr,
int directionIn,
bool *skipRule, bool *skipMatch,
virBufferPtr prefix)
{
char ipaddr[INET6_ADDRSTRLEN],
number[MAX(INT_BUFSIZE_BOUND(uint32_t),
INT_BUFSIZE_BOUND(int))];
char str[MAX_IPSET_NAME_LENGTH];
const char *src = "--source";
const char *dst = "--destination";
const char *srcrange = "--src-range";
const char *dstrange = "--dst-range";
if (directionIn) {
src = "--destination";
dst = "--source";
srcrange = "--dst-range";
dstrange = "--src-range";
}
if (HAS_ENTRY_ITEM(&ipHdr->dataIPSet) &&
HAS_ENTRY_ITEM(&ipHdr->dataIPSetFlags)) {
if (printDataType(vars,
str, sizeof(str),
&ipHdr->dataIPSet) < 0)
goto err_exit;
virBufferAsprintf(afterStateMatch,
" -m set --match-set \"%s\" ",
str);
if (printDataTypeDirection(vars,
str, sizeof(str),
&ipHdr->dataIPSetFlags, directionIn) < 0)
goto err_exit;
virBufferAdd(afterStateMatch, str, -1);
}
if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataSrcIPAddr) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataSrcIPAddr),
src,
ipaddr);
if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&ipHdr->dataSrcIPMask) < 0)
goto err_exit;
virBufferAsprintf(buf,
"/%s",
number);
}
} else if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPFrom)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataSrcIPFrom) < 0)
goto err_exit;
virBufferAsprintf(buf,
" -m iprange %s %s %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataSrcIPFrom),
srcrange,
ipaddr);
if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPTo)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataSrcIPTo) < 0)
goto err_exit;
virBufferAsprintf(buf,
"-%s",
ipaddr);
}
}
if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataDstIPAddr) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataDstIPAddr),
dst,
ipaddr);
if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&ipHdr->dataDstIPMask) < 0)
goto err_exit;
virBufferAsprintf(buf,
"/%s",
number);
}
} else if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPFrom)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataDstIPFrom) < 0)
goto err_exit;
virBufferAsprintf(buf,
" -m iprange %s %s %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataDstIPFrom),
dstrange,
ipaddr);
if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPTo)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&ipHdr->dataDstIPTo) < 0)
goto err_exit;
virBufferAsprintf(buf,
"-%s",
ipaddr);
}
}
if (HAS_ENTRY_ITEM(&ipHdr->dataDSCP)) {
if (printDataType(vars,
number, sizeof(number),
&ipHdr->dataDSCP) < 0)
goto err_exit;
virBufferAsprintf(buf,
" -m dscp %s --dscp %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataDSCP),
number);
}
if (HAS_ENTRY_ITEM(&ipHdr->dataConnlimitAbove)) {
if (directionIn) {
/* only support for limit in outgoing dir. */
*skipRule = true;
} else {
if (printDataType(vars,
number, sizeof(number),
&ipHdr->dataConnlimitAbove) < 0)
goto err_exit;
/* place connlimit after potential -m state --state ...
since this is the most useful order */
virBufferAsprintf(afterStateMatch,
" -m connlimit %s --connlimit-above %s",
ENTRY_GET_NEG_SIGN(&ipHdr->dataConnlimitAbove),
number);
*skipMatch = true;
}
}
if (HAS_ENTRY_ITEM(&ipHdr->dataComment)) {
printCommentVar(prefix, ipHdr->dataComment.u.string);
/* keep comments behind everything else -- they are packet eval.
no-ops */
virBufferAddLit(afterStateMatch,
" -m comment --comment \"$" COMMENT_VARNAME "\"");
}
return 0;
err_exit:
virBufferFreeAndReset(buf);
virBufferFreeAndReset(afterStateMatch);
return -1;
}
static int
iptablesHandlePortData(virBufferPtr buf,
virNWFilterVarCombIterPtr vars,
portDataDefPtr portData,
int directionIn)
{
char portstr[20];
const char *sport = "--sport";
const char *dport = "--dport";
if (directionIn) {
sport = "--dport";
dport = "--sport";
}
if (HAS_ENTRY_ITEM(&portData->dataSrcPortStart)) {
if (printDataType(vars,
portstr, sizeof(portstr),
&portData->dataSrcPortStart) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
ENTRY_GET_NEG_SIGN(&portData->dataSrcPortStart),
sport,
portstr);
if (HAS_ENTRY_ITEM(&portData->dataSrcPortEnd)) {
if (printDataType(vars,
portstr, sizeof(portstr),
&portData->dataSrcPortEnd) < 0)
goto err_exit;
virBufferAsprintf(buf,
":%s",
portstr);
}
}
if (HAS_ENTRY_ITEM(&portData->dataDstPortStart)) {
if (printDataType(vars,
portstr, sizeof(portstr),
&portData->dataDstPortStart) < 0)
goto err_exit;
virBufferAsprintf(buf,
" %s %s %s",
ENTRY_GET_NEG_SIGN(&portData->dataDstPortStart),
dport,
portstr);
if (HAS_ENTRY_ITEM(&portData->dataDstPortEnd)) {
if (printDataType(vars,
portstr, sizeof(portstr),
&portData->dataDstPortEnd) < 0)
goto err_exit;
virBufferAsprintf(buf,
":%s",
portstr);
}
}
return 0;
err_exit:
return -1;
}
static void
iptablesEnforceDirection(int directionIn,
virNWFilterRuleDefPtr rule,
virBufferPtr buf)
{
if (rule->tt != VIR_NWFILTER_RULE_DIRECTION_INOUT)
virBufferAsprintf(buf, " -m conntrack --ctdir %s",
(directionIn) ? "Original"
: "Reply");
}
/*
* _iptablesCreateRuleInstance:
* @chainPrefix : The prefix to put in front of the name of the chain
* @nwfilter : The filter
* @rule: The rule of the filter to convert
* @ifname : The name of the interface to apply the rule to
* @vars : A map containing the variables to resolve
* @res : The data structure to store the result(s) into
* @match : optional string for state match
* @accept_target : where to jump to on accepted traffic, i.e., "RETURN"
* "ACCEPT"
* @isIPv6 : Whether this is an IPv6 rule
* @maySkipICMP : whether this rule may under certain circumstances skip
* the ICMP rule from being created
*
* Convert a single rule into its representation for later instantiation
*
* Returns 0 in case of success with the result stored in the data structure
* pointed to by res, != 0 otherwise.
*/
static int
_iptablesCreateRuleInstance(int directionIn,
const char *chainPrefix,
virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterVarCombIterPtr vars,
virNWFilterRuleInstPtr res,
const char *match, bool defMatch,
const char *accept_target,
bool isIPv6,
bool maySkipICMP)
{
char chain[MAX_CHAINNAME_LENGTH];
char number[MAX(INT_BUFSIZE_BOUND(uint32_t),
INT_BUFSIZE_BOUND(int))];
virBuffer prefix = VIR_BUFFER_INITIALIZER;
virBuffer buf = VIR_BUFFER_INITIALIZER;
virBuffer afterStateMatch = VIR_BUFFER_INITIALIZER;
virBufferPtr final = NULL;
const char *target;
const char *iptables_cmd = (isIPv6) ? ip6tables_cmd_path
: iptables_cmd_path;
unsigned int bufUsed;
bool srcMacSkipped = false;
bool skipRule = false;
bool skipMatch = false;
bool hasICMPType = false;
if (!iptables_cmd) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot create rule since %s tool is "
"missing."),
isIPv6 ? "ip6tables" : "iptables");
goto err_exit;
}
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
switch (rule->prtclType) {
case VIR_NWFILTER_RULE_PROTOCOL_TCP:
case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p tcp");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.tcpHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.tcpHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
if (HAS_ENTRY_ITEM(&rule->p.tcpHdrFilter.dataTCPFlags)) {
virBufferAsprintf(&buf, " %s --tcp-flags ",
ENTRY_GET_NEG_SIGN(&rule->p.tcpHdrFilter.dataTCPFlags));
virNWFilterPrintTCPFlags(&buf,
rule->p.tcpHdrFilter.dataTCPFlags.u.tcpFlags.mask,
' ',
rule->p.tcpHdrFilter.dataTCPFlags.u.tcpFlags.flags);
}
if (iptablesHandlePortData(&buf,
vars,
&rule->p.tcpHdrFilter.portData,
directionIn) < 0)
goto err_exit;
if (HAS_ENTRY_ITEM(&rule->p.tcpHdrFilter.dataTCPOption)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.tcpHdrFilter.dataTCPOption) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s --tcp-option %s",
ENTRY_GET_NEG_SIGN(&rule->p.tcpHdrFilter.dataTCPOption),
number);
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_UDP:
case VIR_NWFILTER_RULE_PROTOCOL_UDPoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p udp");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.udpHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.udpHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
if (iptablesHandlePortData(&buf,
vars,
&rule->p.udpHdrFilter.portData,
directionIn) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_UDPLITE:
case VIR_NWFILTER_RULE_PROTOCOL_UDPLITEoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p udplite");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.udpliteHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.udpliteHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_ESP:
case VIR_NWFILTER_RULE_PROTOCOL_ESPoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p esp");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.espHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.espHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_AH:
case VIR_NWFILTER_RULE_PROTOCOL_AHoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p ah");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.ahHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.ahHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_SCTP:
case VIR_NWFILTER_RULE_PROTOCOL_SCTPoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p sctp");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.sctpHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.sctpHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
if (iptablesHandlePortData(&buf,
vars,
&rule->p.sctpHdrFilter.portData,
directionIn) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_ICMP:
case VIR_NWFILTER_RULE_PROTOCOL_ICMPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
if (rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ICMP)
virBufferAddLit(&buf, " -p icmp");
else
virBufferAddLit(&buf, " -p icmpv6");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.icmpHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.icmpHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
if (HAS_ENTRY_ITEM(&rule->p.icmpHdrFilter.dataICMPType)) {
const char *parm;
hasICMPType = true;
if (maySkipICMP)
goto exit_no_error;
if (rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ICMP)
parm = "--icmp-type";
else
parm = "--icmpv6-type";
if (printDataType(vars,
number, sizeof(number),
&rule->p.icmpHdrFilter.dataICMPType) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.icmpHdrFilter.dataICMPType),
parm,
number);
if (HAS_ENTRY_ITEM(&rule->p.icmpHdrFilter.dataICMPCode)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.icmpHdrFilter.dataICMPCode) < 0)
goto err_exit;
virBufferAsprintf(&buf,
"/%s",
number);
}
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_IGMP:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p igmp");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.igmpHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.igmpHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
break;
case VIR_NWFILTER_RULE_PROTOCOL_ALL:
case VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$IPT -%%c %s %%s",
chain);
virBufferAddLit(&buf, " -p all");
bufUsed = virBufferUse(&buf);
if (iptablesHandleSrcMacAddr(&buf,
vars,
&rule->p.allHdrFilter.dataSrcMACAddr,
directionIn,
&srcMacSkipped) < 0)
goto err_exit;
if (iptablesHandleIpHdr(&buf,
&afterStateMatch,
vars,
&rule->p.allHdrFilter.ipHdr,
directionIn,
&skipRule, &skipMatch,
&prefix) < 0)
goto err_exit;
break;
default:
return -1;
}
if ((srcMacSkipped && bufUsed == virBufferUse(&buf)) ||
skipRule) {
virBufferFreeAndReset(&buf);
virBufferFreeAndReset(&prefix);
return 0;
}
if (rule->action == VIR_NWFILTER_RULE_ACTION_ACCEPT)
target = accept_target;
else {
target = virNWFilterJumpTargetTypeToString(rule->action);
skipMatch = defMatch;
}
if (match && !skipMatch)
virBufferAsprintf(&buf, " %s", match);
if (defMatch && match != NULL && !skipMatch && !hasICMPType)
iptablesEnforceDirection(directionIn,
rule,
&buf);
if (virBufferError(&afterStateMatch)) {
virBufferFreeAndReset(&buf);
virBufferFreeAndReset(&prefix);
virBufferFreeAndReset(&afterStateMatch);
virReportOOMError();
return -1;
}
if (virBufferUse(&afterStateMatch)) {
char *s = virBufferContentAndReset(&afterStateMatch);
virBufferAdd(&buf, s, -1);
VIR_FREE(s);
}
virBufferAsprintf(&buf,
" -j %s" CMD_DEF_POST CMD_SEPARATOR
CMD_EXEC,
target);
if (virBufferError(&buf) || virBufferError(&prefix)) {
virBufferFreeAndReset(&buf);
virBufferFreeAndReset(&prefix);
virReportOOMError();
return -1;
}
if (virBufferUse(&prefix)) {
char *s = virBufferContentAndReset(&buf);
virBufferAdd(&prefix, s, -1);
VIR_FREE(s);
final = &prefix;
if (virBufferError(&prefix)) {
virBufferFreeAndReset(&prefix);
virReportOOMError();
return -1;
}
} else
final = &buf;
return ebiptablesAddRuleInst(res,
virBufferContentAndReset(final),
nwfilter->chainsuffix,
nwfilter->chainPriority,
'\0',
rule->priority,
(isIPv6) ? RT_IP6TABLES : RT_IPTABLES);
err_exit:
virBufferFreeAndReset(&buf);
virBufferFreeAndReset(&prefix);
virBufferFreeAndReset(&afterStateMatch);
return -1;
exit_no_error:
virBufferFreeAndReset(&buf);
virBufferFreeAndReset(&prefix);
virBufferFreeAndReset(&afterStateMatch);
return 0;
}
static int
printStateMatchFlags(int32_t flags, char **bufptr)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
virNWFilterPrintStateMatchFlags(&buf,
"-m state --state ",
flags,
false);
if (virBufferError(&buf)) {
virBufferFreeAndReset(&buf);
virReportOOMError();
return -1;
}
*bufptr = virBufferContentAndReset(&buf);
return 0;
}
static int
iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterVarCombIterPtr vars,
virNWFilterRuleInstPtr res,
bool isIPv6)
{
int rc;
int directionIn = 0;
char chainPrefix[2];
bool maySkipICMP, inout = false;
char *matchState = NULL;
bool create;
if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) ||
(rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT)) {
directionIn = 1;
inout = (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT);
}
chainPrefix[0] = 'F';
maySkipICMP = directionIn || inout;
create = true;
matchState = NULL;
if (directionIn && !inout) {
if ((rule->flags & IPTABLES_STATE_FLAGS))
create = false;
}
if (create && (rule->flags & IPTABLES_STATE_FLAGS)) {
if (printStateMatchFlags(rule->flags, &matchState) < 0)
return -1;
}
chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP;
if (create) {
rc = _iptablesCreateRuleInstance(directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, false,
"RETURN",
isIPv6,
maySkipICMP);
VIR_FREE(matchState);
if (rc < 0)
return rc;
}
maySkipICMP = !directionIn || inout;
create = true;
if (!directionIn) {
if ((rule->flags & IPTABLES_STATE_FLAGS))
create = false;
}
if (create && (rule->flags & IPTABLES_STATE_FLAGS)) {
if (printStateMatchFlags(rule->flags, &matchState) < 0)
return -1;
}
chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP;
if (create) {
rc = _iptablesCreateRuleInstance(!directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, false,
"ACCEPT",
isIPv6,
maySkipICMP);
VIR_FREE(matchState);
if (rc < 0)
return rc;
}
maySkipICMP = directionIn;
create = true;
if (directionIn && !inout) {
if ((rule->flags & IPTABLES_STATE_FLAGS))
create = false;
} else {
if ((rule->flags & IPTABLES_STATE_FLAGS)) {
if (printStateMatchFlags(rule->flags, &matchState) < 0)
return -1;
}
}
if (create) {
chainPrefix[0] = 'H';
chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP;
rc = _iptablesCreateRuleInstance(directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, false,
"RETURN",
isIPv6,
maySkipICMP);
VIR_FREE(matchState);
}
return rc;
}
static int
iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterVarCombIterPtr vars,
virNWFilterRuleInstPtr res,
bool isIPv6)
{
int rc;
int directionIn = 0;
char chainPrefix[2];
int needState = 1;
bool maySkipICMP, inout = false;
const char *matchState;
if (!(rule->flags & RULE_FLAG_NO_STATEMATCH) &&
(rule->flags & IPTABLES_STATE_FLAGS)) {
return iptablesCreateRuleInstanceStateCtrl(nwfilter,
rule,
ifname,
vars,
res,
isIPv6);
}
if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) ||
(rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT)) {
directionIn = 1;
inout = (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT);
if (inout)
needState = 0;
}
if ((rule->flags & RULE_FLAG_NO_STATEMATCH))
needState = 0;
chainPrefix[0] = 'F';
maySkipICMP = directionIn || inout;
if (needState)
matchState = directionIn ? MATCH_STATE_IN : MATCH_STATE_OUT;
else
matchState = NULL;
chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP;
rc = _iptablesCreateRuleInstance(directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, true,
"RETURN",
isIPv6,
maySkipICMP);
if (rc < 0)
return rc;
maySkipICMP = !directionIn || inout;
if (needState)
matchState = directionIn ? MATCH_STATE_OUT : MATCH_STATE_IN;
else
matchState = NULL;
chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP;
rc = _iptablesCreateRuleInstance(!directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, true,
"ACCEPT",
isIPv6,
maySkipICMP);
if (rc < 0)
return rc;
maySkipICMP = directionIn;
if (needState)
matchState = directionIn ? MATCH_STATE_IN : MATCH_STATE_OUT;
else
matchState = NULL;
chainPrefix[0] = 'H';
chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP;
rc = _iptablesCreateRuleInstance(directionIn,
chainPrefix,
nwfilter,
rule,
ifname,
vars,
res,
matchState, true,
"RETURN",
isIPv6,
maySkipICMP);
return rc;
}
/*
* ebtablesCreateRuleInstance:
* @chainPrefix : The prefix to put in front of the name of the chain
* @nwfilter : The filter
* @rule: The rule of the filter to convert
* @ifname : The name of the interface to apply the rule to
* @vars : A map containing the variables to resolve
* @res : The data structure to store the result(s) into
* @reverse : Whether to reverse src and dst attributes
*
* Convert a single rule into its representation for later instantiation
*
* Returns 0 in case of success with the result stored in the data structure
* pointed to by res, != 0 otherwise.
*/
static int
ebtablesCreateRuleInstance(char chainPrefix,
virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterVarCombIterPtr vars,
virNWFilterRuleInstPtr res,
bool reverse)
{
char macaddr[VIR_MAC_STRING_BUFLEN],
ipaddr[INET_ADDRSTRLEN],
ipv6addr[INET6_ADDRSTRLEN],
number[MAX(INT_BUFSIZE_BOUND(uint32_t),
INT_BUFSIZE_BOUND(int))],
field[MAX(VIR_MAC_STRING_BUFLEN, INET6_ADDRSTRLEN)];
char chain[MAX_CHAINNAME_LENGTH];
virBuffer buf = VIR_BUFFER_INITIALIZER;
const char *target;
if (!ebtables_cmd_path) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create rule since ebtables tool is "
"missing."));
goto err_exit;
}
if (STREQ(nwfilter->chainsuffix,
virNWFilterChainSuffixTypeToString(
VIR_NWFILTER_CHAINSUFFIX_ROOT)))
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
else
PRINT_CHAIN(chain, chainPrefix, ifname,
nwfilter->chainsuffix);
switch (rule->prtclType) {
case VIR_NWFILTER_RULE_PROTOCOL_MAC:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.ethHdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
if (HAS_ENTRY_ITEM(&rule->p.ethHdrFilter.dataProtocolID)) {
if (printDataTypeAsHex(vars,
number, sizeof(number),
&rule->p.ethHdrFilter.dataProtocolID) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" -p %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.ethHdrFilter.dataProtocolID),
number);
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_VLAN:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.vlanHdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
virBufferAddLit(&buf,
" -p 0x8100");
#define INST_ITEM(STRUCT, ITEM, CLI) \
if (HAS_ENTRY_ITEM(&rule->p.STRUCT.ITEM)) { \
if (printDataType(vars, \
field, sizeof(field), \
&rule->p.STRUCT.ITEM) < 0) \
goto err_exit; \
virBufferAsprintf(&buf, \
" " CLI " %s %s", \
ENTRY_GET_NEG_SIGN(&rule->p.STRUCT.ITEM), \
field); \
}
#define INST_ITEM_2PARMS(STRUCT, ITEM, ITEM_HI, CLI, SEP) \
if (HAS_ENTRY_ITEM(&rule->p.STRUCT.ITEM)) { \
if (printDataType(vars, \
field, sizeof(field), \
&rule->p.STRUCT.ITEM) < 0) \
goto err_exit; \
virBufferAsprintf(&buf, \
" " CLI " %s %s", \
ENTRY_GET_NEG_SIGN(&rule->p.STRUCT.ITEM), \
field); \
if (HAS_ENTRY_ITEM(&rule->p.STRUCT.ITEM_HI)) { \
if (printDataType(vars, \
field, sizeof(field), \
&rule->p.STRUCT.ITEM_HI) < 0) \
goto err_exit; \
virBufferAsprintf(&buf, SEP "%s", field); \
} \
}
#define INST_ITEM_RANGE(S, I, I_HI, C) \
INST_ITEM_2PARMS(S, I, I_HI, C, ":")
#define INST_ITEM_MASK(S, I, MASK, C) \
INST_ITEM_2PARMS(S, I, MASK, C, "/")
INST_ITEM(vlanHdrFilter, dataVlanID, "--vlan-id")
INST_ITEM(vlanHdrFilter, dataVlanEncap, "--vlan-encap")
break;
case VIR_NWFILTER_RULE_PROTOCOL_STP:
/* cannot handle inout direction with srcmask set in reverse dir.
since this clashes with -d below... */
if (reverse &&
HAS_ENTRY_ITEM(&rule->p.stpHdrFilter.ethHdr.dataSrcMACAddr)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("STP filtering in %s direction with "
"source MAC address set is not supported"),
virNWFilterRuleDirectionTypeToString(
VIR_NWFILTER_RULE_DIRECTION_INOUT));
return -1;
}
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.stpHdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
virBufferAddLit(&buf, " -d " NWFILTER_MAC_BGA);
INST_ITEM(stpHdrFilter, dataType, "--stp-type")
INST_ITEM(stpHdrFilter, dataFlags, "--stp-flags")
INST_ITEM_RANGE(stpHdrFilter, dataRootPri, dataRootPriHi,
"--stp-root-pri");
INST_ITEM_MASK( stpHdrFilter, dataRootAddr, dataRootAddrMask,
"--stp-root-addr");
INST_ITEM_RANGE(stpHdrFilter, dataRootCost, dataRootCostHi,
"--stp-root-cost");
INST_ITEM_RANGE(stpHdrFilter, dataSndrPrio, dataSndrPrioHi,
"--stp-sender-prio");
INST_ITEM_MASK( stpHdrFilter, dataSndrAddr, dataSndrAddrMask,
"--stp-sender-addr");
INST_ITEM_RANGE(stpHdrFilter, dataPort, dataPortHi, "--stp-port");
INST_ITEM_RANGE(stpHdrFilter, dataAge, dataAgeHi, "--stp-msg-age");
INST_ITEM_RANGE(stpHdrFilter, dataMaxAge, dataMaxAgeHi,
"--stp-max-age");
INST_ITEM_RANGE(stpHdrFilter, dataHelloTime, dataHelloTimeHi,
"--stp-hello-time");
INST_ITEM_RANGE(stpHdrFilter, dataFwdDelay, dataFwdDelayHi,
"--stp-forward-delay");
break;
case VIR_NWFILTER_RULE_PROTOCOL_ARP:
case VIR_NWFILTER_RULE_PROTOCOL_RARP:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.arpHdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
virBufferAsprintf(&buf, " -p 0x%x",
(rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ARP)
? l3_protocols[L3_PROTO_ARP_IDX].attr
: l3_protocols[L3_PROTO_RARP_IDX].attr);
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataHWType)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.arpHdrFilter.dataHWType) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --arp-htype %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataHWType),
number);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataOpcode)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.arpHdrFilter.dataOpcode) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --arp-opcode %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataOpcode),
number);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataProtocolType)) {
if (printDataTypeAsHex(vars,
number, sizeof(number),
&rule->p.arpHdrFilter.dataProtocolType) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --arp-ptype %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataProtocolType),
number);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&rule->p.arpHdrFilter.dataARPSrcIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--arp-ip-dst" : "--arp-ip-src",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataARPSrcIPAddr),
ipaddr);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPDstIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&rule->p.arpHdrFilter.dataARPDstIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--arp-ip-src" : "--arp-ip-dst",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataARPDstIPAddr),
ipaddr);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcMACAddr)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
&rule->p.arpHdrFilter.dataARPSrcMACAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--arp-mac-dst" : "--arp-mac-src",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataARPSrcMACAddr),
macaddr);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPDstMACAddr)) {
if (printDataType(vars,
macaddr, sizeof(macaddr),
&rule->p.arpHdrFilter.dataARPDstMACAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--arp-mac-src" : "--arp-mac-dst",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataARPDstMACAddr),
macaddr);
}
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataGratuitousARP) &&
rule->p.arpHdrFilter.dataGratuitousARP.u.boolean) {
virBufferAsprintf(&buf,
" %s --arp-gratuitous",
ENTRY_GET_NEG_SIGN(&rule->p.arpHdrFilter.dataGratuitousARP));
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_IP:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.ipHdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
virBufferAddLit(&buf,
" -p ipv4");
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip-destination" : "--ip-source",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr),
ipaddr);
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataSrcIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.ipHdr.dataSrcIPMask)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
"/%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDstIPAddr)) {
if (printDataType(vars,
ipaddr, sizeof(ipaddr),
&rule->p.ipHdrFilter.ipHdr.dataDstIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip-source" : "--ip-destination",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataDstIPAddr),
ipaddr);
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDstIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.ipHdr.dataDstIPMask)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
"/%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataProtocolID)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.ipHdr.dataProtocolID) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --ip-protocol %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataProtocolID),
number);
}
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataSrcPortStart)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.portData.dataSrcPortStart)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip-destination-port" : "--ip-source-port",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.portData.dataSrcPortStart),
number);
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataSrcPortEnd)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.portData.dataSrcPortEnd)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
":%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataDstPortStart)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.portData.dataDstPortStart)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip-source-port" : "--ip-destination-port",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.portData.dataDstPortStart),
number);
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataDstPortEnd)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.portData.dataDstPortEnd)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
":%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDSCP)) {
if (printDataTypeAsHex(vars,
number, sizeof(number),
&rule->p.ipHdrFilter.ipHdr.dataDSCP) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --ip-tos %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataDSCP),
number);
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_IPV6:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
if (ebtablesHandleEthHdr(&buf,
vars,
&rule->p.ipv6HdrFilter.ethHdr,
reverse) < 0)
goto err_exit;
virBufferAddLit(&buf,
" -p ipv6");
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr)) {
if (printDataType(vars,
ipv6addr, sizeof(ipv6addr),
&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip6-destination" : "--ip6-source",
ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr),
ipv6addr);
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
"/%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr)) {
if (printDataType(vars,
ipv6addr, sizeof(ipv6addr),
&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip6-source" : "--ip6-destination",
ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr),
ipv6addr);
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
"/%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID) < 0)
goto err_exit;
virBufferAsprintf(&buf,
" --ip6-protocol %s %s",
ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID),
number);
}
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.portData.dataSrcPortStart)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip6-destination-port" : "--ip6-source-port",
ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart),
number);
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortEnd)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.portData.dataSrcPortEnd)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
":%s",
number);
}
}
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortStart)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.portData.dataDstPortStart)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
" %s %s %s",
reverse ? "--ip6-source-port" : "--ip6-destination-port",
ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataDstPortStart),
number);
if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortEnd)) {
if (printDataType(vars,
number, sizeof(number),
&rule->p.ipv6HdrFilter.portData.dataDstPortEnd)
< 0)
goto err_exit;
virBufferAsprintf(&buf,
":%s",
number);
}
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_NONE:
virBufferAsprintf(&buf,
CMD_DEF_PRE "$EBT -t nat -%%c %s %%s",
chain);
break;
default:
return -1;
}
switch (rule->action) {
case VIR_NWFILTER_RULE_ACTION_REJECT:
/* REJECT not supported */
target = virNWFilterJumpTargetTypeToString(
VIR_NWFILTER_RULE_ACTION_DROP);
break;
default:
target = virNWFilterJumpTargetTypeToString(rule->action);
}
virBufferAsprintf(&buf,
" -j %s" CMD_DEF_POST CMD_SEPARATOR
CMD_EXEC,
target);
if (virBufferError(&buf)) {
virBufferFreeAndReset(&buf);
virReportOOMError();
return -1;
}
return ebiptablesAddRuleInst(res,
virBufferContentAndReset(&buf),
nwfilter->chainsuffix,
nwfilter->chainPriority,
chainPrefix,
rule->priority,
RT_EBTABLES);
err_exit:
virBufferFreeAndReset(&buf);
return -1;
}
/*
* ebiptablesCreateRuleInstance:
* @nwfilter : The filter
* @rule: The rule of the filter to convert
* @ifname : The name of the interface to apply the rule to
* @vars : A map containing the variables to resolve
* @res : The data structure to store the result(s) into
*
* Convert a single rule into its representation for later instantiation
*
* Returns 0 in case of success with the result stored in the data structure
* pointed to by res, -1 otherwise
*/
static int
ebiptablesCreateRuleInstance(enum virDomainNetType nettype ATTRIBUTE_UNUSED,
virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterVarCombIterPtr vars,
virNWFilterRuleInstPtr res)
{
int rc = 0;
bool isIPv6;
switch (rule->prtclType) {
case VIR_NWFILTER_RULE_PROTOCOL_IP:
case VIR_NWFILTER_RULE_PROTOCOL_MAC:
case VIR_NWFILTER_RULE_PROTOCOL_VLAN:
case VIR_NWFILTER_RULE_PROTOCOL_STP:
case VIR_NWFILTER_RULE_PROTOCOL_ARP:
case VIR_NWFILTER_RULE_PROTOCOL_RARP:
case VIR_NWFILTER_RULE_PROTOCOL_NONE:
case VIR_NWFILTER_RULE_PROTOCOL_IPV6:
if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_OUT ||
rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) {
rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_IN_TEMP,
nwfilter,
rule,
ifname,
vars,
res,
rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT);
if (rc < 0)
return rc;
}
if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN ||
rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) {
rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_OUT_TEMP,
nwfilter,
rule,
ifname,
vars,
res,
false);
}
break;
case VIR_NWFILTER_RULE_PROTOCOL_TCP:
case VIR_NWFILTER_RULE_PROTOCOL_UDP:
case VIR_NWFILTER_RULE_PROTOCOL_UDPLITE:
case VIR_NWFILTER_RULE_PROTOCOL_ESP:
case VIR_NWFILTER_RULE_PROTOCOL_AH:
case VIR_NWFILTER_RULE_PROTOCOL_SCTP:
case VIR_NWFILTER_RULE_PROTOCOL_ICMP:
case VIR_NWFILTER_RULE_PROTOCOL_IGMP:
case VIR_NWFILTER_RULE_PROTOCOL_ALL:
isIPv6 = 0;
rc = iptablesCreateRuleInstance(nwfilter,
rule,
ifname,
vars,
res,
isIPv6);
break;
case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_UDPoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_UDPLITEoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_ESPoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_AHoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_SCTPoIPV6:
case VIR_NWFILTER_RULE_PROTOCOL_ICMPV6:
case VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6:
isIPv6 = 1;
rc = iptablesCreateRuleInstance(nwfilter,
rule,
ifname,
vars,
res,
isIPv6);
break;
case VIR_NWFILTER_RULE_PROTOCOL_LAST:
virReportError(VIR_ERR_OPERATION_FAILED,
"%s", _("illegal protocol type"));
rc = -1;
break;
}
return rc;
}
static int
ebiptablesCreateRuleInstanceIterate(
enum virDomainNetType nettype ATTRIBUTE_UNUSED,
virNWFilterDefPtr nwfilter,
virNWFilterRuleDefPtr rule,
const char *ifname,
virNWFilterHashTablePtr vars,
virNWFilterRuleInstPtr res)
{
int rc = 0;
virNWFilterVarCombIterPtr vciter;
/* rule->vars holds all the variables names that this rule will access.
* iterate over all combinations of the variables' values and instantiate
* the filtering rule with each combination.
*/
vciter = virNWFilterVarCombIterCreate(vars,
rule->varAccess, rule->nVarAccess);
if (!vciter)
return -1;
do {
rc = ebiptablesCreateRuleInstance(nettype,
nwfilter,
rule,
ifname,
vciter,
res);
if (rc < 0)
break;
vciter = virNWFilterVarCombIterNext(vciter);
} while (vciter != NULL);
virNWFilterVarCombIterFree(vciter);
return rc;
}
static int
ebiptablesFreeRuleInstance(void *_inst)
{
ebiptablesRuleInstFree((ebiptablesRuleInstPtr)_inst);
return 0;
}
static int
ebiptablesDisplayRuleInstance(void *_inst)
{
ebiptablesRuleInstPtr inst = (ebiptablesRuleInstPtr)_inst;
VIR_INFO("Command Template: '%s', Needed protocol: '%s'",
inst->commandTemplate,
inst->neededProtocolChain);
return 0;
}
/**
* ebiptablesExecCLI:
* @buf : pointer to virBuffer containing the string with the commands to
* execute.
* @status: Pointer to an integer for returning the WEXITSTATUS of the
* commands executed via the script the was run.
* @outbuf: Optional pointer to a string that will hold the buffer with
* output of the executed command. The actual buffer holding
* the message will be newly allocated by this function and
* any passed in buffer freed first.
*
* Returns 0 in case of success, < 0 in case of an error. The returned
* value is NOT the result of running the commands inside the shell
* script.
*
* Execute a sequence of commands (held in the given buffer) as a /bin/sh
* script and return the status of the execution in *status (if status is
* NULL, then the script must exit with status 0).
*/
static int
ebiptablesExecCLI(virBufferPtr buf,
int *status, char **outbuf)
{
int rc = -1;
virCommandPtr cmd;
if (status)
*status = 0;
if (!virBufferError(buf) && !virBufferUse(buf))
return 0;
if (outbuf)
VIR_FREE(*outbuf);
cmd = virCommandNewArgList("/bin/sh", "-c", NULL);
virCommandAddArgBuffer(cmd, buf);
if (outbuf)
virCommandSetOutputBuffer(cmd, outbuf);
virMutexLock(&execCLIMutex);
rc = virCommandRun(cmd, status);
virMutexUnlock(&execCLIMutex);
virCommandFree(cmd);
return rc;
}
static int
ebtablesCreateTmpRootChain(virBufferPtr buf,
int incoming, const char *ifname,
int stopOnError)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
CMD_DEF("$EBT -t nat -N %s") CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
CMD_STOPONERR(stopOnError));
return 0;
}
static int
ebtablesLinkTmpRootChain(virBufferPtr buf,
int incoming, const char *ifname,
int stopOnError)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
char iodev = (incoming) ? 'i' : 'o';
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
CMD_DEF("$EBT -t nat -A %s -%c %s -j %s") CMD_SEPARATOR
CMD_EXEC
"%s",
(incoming) ? EBTABLES_CHAIN_INCOMING
: EBTABLES_CHAIN_OUTGOING,
iodev, ifname, chain,
CMD_STOPONERR(stopOnError));
return 0;
}
static int
_ebtablesRemoveRootChain(virBufferPtr buf,
int incoming, const char *ifname,
int isTempChain)
{
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix;
if (isTempChain)
chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
else
chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT;
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
"$EBT -t nat -F %s" CMD_SEPARATOR
"$EBT -t nat -X %s" CMD_SEPARATOR,
chain,
chain);
return 0;
}
static int
ebtablesRemoveRootChain(virBufferPtr buf,
int incoming, const char *ifname)
{
return _ebtablesRemoveRootChain(buf, incoming, ifname, 0);
}
static int
ebtablesRemoveTmpRootChain(virBufferPtr buf,
int incoming, const char *ifname)
{
return _ebtablesRemoveRootChain(buf, incoming, ifname, 1);
}
static int
_ebtablesUnlinkRootChain(virBufferPtr buf,
int incoming, const char *ifname,
int isTempChain)
{
char chain[MAX_CHAINNAME_LENGTH];
char iodev = (incoming) ? 'i' : 'o';
char chainPrefix;
if (isTempChain) {
chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
} else {
chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT;
}
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(buf,
"$EBT -t nat -D %s -%c %s -j %s" CMD_SEPARATOR,
(incoming) ? EBTABLES_CHAIN_INCOMING
: EBTABLES_CHAIN_OUTGOING,
iodev, ifname, chain);
return 0;
}
static int
ebtablesUnlinkRootChain(virBufferPtr buf,
int incoming, const char *ifname)
{
return _ebtablesUnlinkRootChain(buf, incoming, ifname, 0);
}
static int
ebtablesUnlinkTmpRootChain(virBufferPtr buf,
int incoming, const char *ifname)
{
return _ebtablesUnlinkRootChain(buf, incoming, ifname, 1);
}
static int
ebtablesCreateTmpSubChain(ebiptablesRuleInstPtr *inst,
int *nRuleInstances,
int incoming,
const char *ifname,
enum l3_proto_idx protoidx,
const char *filtername,
int stopOnError,
virNWFilterChainPriority priority)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
ebiptablesRuleInstPtr tmp = *inst;
size_t count = *nRuleInstances;
char rootchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH];
char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
char *protostr = NULL;
PRINT_ROOT_CHAIN(rootchain, chainPrefix, ifname);
PRINT_CHAIN(chain, chainPrefix, ifname,
(filtername) ? filtername : l3_protocols[protoidx].val);
switch (protoidx) {
case L2_PROTO_MAC_IDX:
protostr = strdup("");
break;
case L2_PROTO_STP_IDX:
ignore_value(virAsprintf(&protostr, "-d " NWFILTER_MAC_BGA " "));
break;
default:
ignore_value(virAsprintf(&protostr, "-p 0x%04x ",
l3_protocols[protoidx].attr));
break;
}
if (!protostr) {
virReportOOMError();
return -1;
}
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -F %s") CMD_SEPARATOR
CMD_EXEC
CMD_DEF("$EBT -t nat -X %s") CMD_SEPARATOR
CMD_EXEC
CMD_DEF("$EBT -t nat -N %s") CMD_SEPARATOR
CMD_EXEC
"%s"
CMD_DEF("$EBT -t nat -%%c %s %%s %s-j %s")
CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
chain,
chain,
CMD_STOPONERR(stopOnError),
rootchain, protostr, chain,
CMD_STOPONERR(stopOnError));
VIR_FREE(protostr);
if (virBufferError(&buf) ||
VIR_EXPAND_N(tmp, count, 1) < 0) {
virReportOOMError();
virBufferFreeAndReset(&buf);
return -1;
}
*nRuleInstances = count;
*inst = tmp;
tmp[*nRuleInstances - 1].priority = priority;
tmp[*nRuleInstances - 1].commandTemplate =
virBufferContentAndReset(&buf);
tmp[*nRuleInstances - 1].neededProtocolChain =
virNWFilterChainSuffixTypeToString(VIR_NWFILTER_CHAINSUFFIX_ROOT);
return 0;
}
static int
_ebtablesRemoveSubChains(virBufferPtr buf,
const char *ifname,
const char *chains)
{
char rootchain[MAX_CHAINNAME_LENGTH];
unsigned i;
NWFILTER_SET_EBTABLES_SHELLVAR(buf);
virBufferAsprintf(buf, NWFILTER_FUNC_COLLECT_CHAINS,
chains);
virBufferAdd(buf, NWFILTER_FUNC_RM_CHAINS, -1);
virBufferAsprintf(buf, NWFILTER_FUNC_SET_IFS);
virBufferAddLit(buf, "chains=\"$(collect_chains");
for (i = 0; chains[i] != 0; i++) {
PRINT_ROOT_CHAIN(rootchain, chains[i], ifname);
virBufferAsprintf(buf, " %s", rootchain);
}
virBufferAddLit(buf, ")\"\n");
for (i = 0; chains[i] != 0; i++) {
PRINT_ROOT_CHAIN(rootchain, chains[i], ifname);
virBufferAsprintf(buf,
"$EBT -t nat -F %s\n",
rootchain);
}
virBufferAddLit(buf, "rm_chains $chains\n");
return 0;
}
static int
ebtablesRemoveSubChains(virBufferPtr buf,
const char *ifname)
{
char chains[3] = {
CHAINPREFIX_HOST_IN,
CHAINPREFIX_HOST_OUT,
0
};
return _ebtablesRemoveSubChains(buf, ifname, chains);
}
static int
ebtablesRemoveTmpSubChains(virBufferPtr buf,
const char *ifname)
{
char chains[3] = {
CHAINPREFIX_HOST_IN_TEMP,
CHAINPREFIX_HOST_OUT_TEMP,
0
};
return _ebtablesRemoveSubChains(buf, ifname, chains);
}
static int
ebtablesRenameTmpSubChain(virBufferPtr buf,
int incoming,
const char *ifname,
const char *protocol)
{
char tmpchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH];
char tmpChainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP
: CHAINPREFIX_HOST_OUT_TEMP;
char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN
: CHAINPREFIX_HOST_OUT;
if (protocol) {
PRINT_CHAIN(tmpchain, tmpChainPrefix, ifname, protocol);
PRINT_CHAIN( chain, chainPrefix, ifname, protocol);
} else {
PRINT_ROOT_CHAIN(tmpchain, tmpChainPrefix, ifname);
PRINT_ROOT_CHAIN( chain, chainPrefix, ifname);
}
virBufferAsprintf(buf,
"$EBT -t nat -E %s %s" CMD_SEPARATOR,
tmpchain, chain);
return 0;
}
static int
ebtablesRenameTmpRootChain(virBufferPtr buf,
int incoming,
const char *ifname)
{
return ebtablesRenameTmpSubChain(buf, incoming, ifname, NULL);
}
static int
ebtablesRenameTmpSubAndRootChains(virBufferPtr buf,
const char *ifname)
{
char rootchain[MAX_CHAINNAME_LENGTH];
unsigned i;
char chains[3] = {
CHAINPREFIX_HOST_IN_TEMP,
CHAINPREFIX_HOST_OUT_TEMP,
0};
NWFILTER_SET_EBTABLES_SHELLVAR(buf);
virBufferAsprintf(buf, NWFILTER_FUNC_COLLECT_CHAINS,
chains);
virBufferAsprintf(buf, NWFILTER_FUNC_RENAME_CHAINS,
CHAINPREFIX_HOST_IN_TEMP,
CHAINPREFIX_HOST_IN,
CHAINPREFIX_HOST_OUT_TEMP,
CHAINPREFIX_HOST_OUT);
virBufferAsprintf(buf, NWFILTER_FUNC_SET_IFS);
virBufferAddLit(buf, "chains=\"$(collect_chains");
for (i = 0; chains[i] != 0; i++) {
PRINT_ROOT_CHAIN(rootchain, chains[i], ifname);
virBufferAsprintf(buf, " %s", rootchain);
}
virBufferAddLit(buf, ")\"\n");
virBufferAddLit(buf, "rename_chains $chains\n");
ebtablesRenameTmpRootChain(buf, 1, ifname);
ebtablesRenameTmpRootChain(buf, 0, ifname);
return 0;
}
static void
ebiptablesInstCommand(virBufferPtr buf,
const char *templ, char cmd, int pos,
int stopOnError)
{
char position[10] = { 0 };
if (pos >= 0)
snprintf(position, sizeof(position), "%d", pos);
virBufferAsprintf(buf, templ, cmd, position);
virBufferAsprintf(buf, CMD_SEPARATOR "%s",
CMD_STOPONERR(stopOnError));
}
/**
* ebiptablesCanApplyBasicRules
*
* Determine whether this driver can apply the basic rules, meaning
* run ebtablesApplyBasicRules and ebtablesApplyDHCPOnlyRules.
* In case of this driver we need the ebtables tool available.
*/
static int
ebiptablesCanApplyBasicRules(void) {
return ebtables_cmd_path != NULL;
}
/**
* ebtablesApplyBasicRules
*
* @ifname: name of the backend-interface to which to apply the rules
* @macaddr: MAC address the VM is using in packets sent through the
* interface
*
* Returns 0 on success, -1 on failure with the rules removed
*
* Apply basic filtering rules on the given interface
* - filtering for MAC address spoofing
* - allowing IPv4 & ARP traffic
*/
static int
ebtablesApplyBasicRules(const char *ifname,
const virMacAddrPtr macaddr)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char chain[MAX_CHAINNAME_LENGTH];
char chainPrefix = CHAINPREFIX_HOST_IN_TEMP;
char macaddr_str[VIR_MAC_STRING_BUFLEN];
if (!ebtables_cmd_path) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create rules since ebtables tool is "
"missing."));
return -1;
}
virMacAddrFormat(macaddr, macaddr_str);
ebiptablesAllTeardown(ifname);
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesCreateTmpRootChain(&buf, 1, ifname, 1);
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname);
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -s ! %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain, macaddr_str,
CMD_STOPONERR(1));
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -p IPv4 -j ACCEPT") CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
CMD_STOPONERR(1));
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -p ARP -j ACCEPT") CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
CMD_STOPONERR(1));
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain,
CMD_STOPONERR(1));
ebtablesLinkTmpRootChain(&buf, 1, ifname, 1);
ebtablesRenameTmpRootChain(&buf, 1, ifname);
if (ebiptablesExecCLI(&buf, NULL, NULL) < 0)
goto tear_down_tmpebchains;
return 0;
tear_down_tmpebchains:
ebtablesCleanAll(ifname);
virReportError(VIR_ERR_BUILD_FIREWALL,
"%s",
_("Some rules could not be created."));
return -1;
}
/**
* ebtablesApplyDHCPOnlyRules
*
* @ifname: name of the backend-interface to which to apply the rules
* @macaddr: MAC address the VM is using in packets sent through the
* interface
* @dhcpsrvrs: The DHCP server(s) from which the VM may receive traffic
* from; may be NULL
* @leaveTemporary: Whether to leave the table names with their temporary
* names (true) or also perform the renaming to their final names as
* part of this call (false)
*
* Returns 0 on success, -1 on failure with the rules removed
*
* Apply filtering rules so that the VM can only send and receive
* DHCP traffic and nothing else.
*/
static int
ebtablesApplyDHCPOnlyRules(const char *ifname,
const virMacAddrPtr macaddr,
virNWFilterVarValuePtr dhcpsrvrs,
bool leaveTemporary)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char chain_in [MAX_CHAINNAME_LENGTH],
chain_out[MAX_CHAINNAME_LENGTH];
char macaddr_str[VIR_MAC_STRING_BUFLEN];
unsigned int idx = 0;
unsigned int num_dhcpsrvrs;
if (!ebtables_cmd_path) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create rules since ebtables tool is "
"missing."));
return -1;
}
virMacAddrFormat(macaddr, macaddr_str);
ebiptablesAllTeardown(ifname);
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesCreateTmpRootChain(&buf, 1, ifname, 1);
ebtablesCreateTmpRootChain(&buf, 0, ifname, 1);
PRINT_ROOT_CHAIN(chain_in , CHAINPREFIX_HOST_IN_TEMP , ifname);
PRINT_ROOT_CHAIN(chain_out, CHAINPREFIX_HOST_OUT_TEMP, ifname);
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s"
" -s %s"
" -p ipv4 --ip-protocol udp"
" --ip-sport 68 --ip-dport 67"
" -j ACCEPT") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_in,
macaddr_str,
CMD_STOPONERR(1));
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_in,
CMD_STOPONERR(1));
num_dhcpsrvrs = (dhcpsrvrs != NULL)
? virNWFilterVarValueGetCardinality(dhcpsrvrs)
: 0;
while (true) {
char *srcIPParam = NULL;
int ctr;
if (idx < num_dhcpsrvrs) {
const char *dhcpserver;
dhcpserver = virNWFilterVarValueGetNthValue(dhcpsrvrs, idx);
if (virAsprintf(&srcIPParam, "--ip-src %s", dhcpserver) < 0) {
virReportOOMError();
goto tear_down_tmpebchains;
}
}
/*
* create two rules allowing response to MAC address of VM
* or to broadcast MAC address
*/
for (ctr = 0; ctr < 2; ctr++) {
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s"
" -d %s"
" -p ipv4 --ip-protocol udp"
" %s"
" --ip-sport 67 --ip-dport 68"
" -j ACCEPT") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_out,
(ctr == 0) ? macaddr_str : "ff:ff:ff:ff:ff:ff",
srcIPParam != NULL ? srcIPParam : "",
CMD_STOPONERR(1));
}
VIR_FREE(srcIPParam);
idx++;
if (idx >= num_dhcpsrvrs)
break;
}
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_out,
CMD_STOPONERR(1));
ebtablesLinkTmpRootChain(&buf, 1, ifname, 1);
ebtablesLinkTmpRootChain(&buf, 0, ifname, 1);
if (!leaveTemporary) {
ebtablesRenameTmpRootChain(&buf, 1, ifname);
ebtablesRenameTmpRootChain(&buf, 0, ifname);
}
if (ebiptablesExecCLI(&buf, NULL, NULL) < 0)
goto tear_down_tmpebchains;
return 0;
tear_down_tmpebchains:
ebtablesCleanAll(ifname);
virReportError(VIR_ERR_BUILD_FIREWALL,
"%s",
_("Some rules could not be created."));
return -1;
}
/**
* ebtablesApplyDropAllRules
*
* @ifname: name of the backend-interface to which to apply the rules
*
* Returns 0 on success, -1 on failure with the rules removed
*
* Apply filtering rules so that the VM cannot receive or send traffic.
*/
static int
ebtablesApplyDropAllRules(const char *ifname)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char chain_in [MAX_CHAINNAME_LENGTH],
chain_out[MAX_CHAINNAME_LENGTH];
if (!ebtables_cmd_path) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot create rules since ebtables tool is "
"missing."));
return -1;
}
ebiptablesAllTeardown(ifname);
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesCreateTmpRootChain(&buf, 1, ifname, 1);
ebtablesCreateTmpRootChain(&buf, 0, ifname, 1);
PRINT_ROOT_CHAIN(chain_in , CHAINPREFIX_HOST_IN_TEMP , ifname);
PRINT_ROOT_CHAIN(chain_out, CHAINPREFIX_HOST_OUT_TEMP, ifname);
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_in,
CMD_STOPONERR(1));
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -A %s -j DROP") CMD_SEPARATOR
CMD_EXEC
"%s",
chain_out,
CMD_STOPONERR(1));
ebtablesLinkTmpRootChain(&buf, 1, ifname, 1);
ebtablesLinkTmpRootChain(&buf, 0, ifname, 1);
ebtablesRenameTmpRootChain(&buf, 1, ifname);
ebtablesRenameTmpRootChain(&buf, 0, ifname);
if (ebiptablesExecCLI(&buf, NULL, NULL) < 0)
goto tear_down_tmpebchains;
return 0;
tear_down_tmpebchains:
ebtablesCleanAll(ifname);
virReportError(VIR_ERR_BUILD_FIREWALL,
"%s",
_("Some rules could not be created."));
return -1;
}
static int
ebtablesRemoveBasicRules(const char *ifname)
{
return ebtablesCleanAll(ifname);
}
static int ebtablesCleanAll(const char *ifname)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int cli_status;
if (!ebtables_cmd_path)
return 0;
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkRootChain(&buf, 1, ifname);
ebtablesUnlinkRootChain(&buf, 0, ifname);
ebtablesRemoveSubChains(&buf, ifname);
ebtablesRemoveRootChain(&buf, 1, ifname);
ebtablesRemoveRootChain(&buf, 0, ifname);
ebtablesUnlinkTmpRootChain(&buf, 1, ifname);
ebtablesUnlinkTmpRootChain(&buf, 0, ifname);
ebtablesRemoveTmpSubChains(&buf, ifname);
ebtablesRemoveTmpRootChain(&buf, 1, ifname);
ebtablesRemoveTmpRootChain(&buf, 0, ifname);
ebiptablesExecCLI(&buf, &cli_status, NULL);
return 0;
}
static int
ebiptablesRuleOrderSort(const void *a, const void *b)
{
const ebiptablesRuleInstPtr insta = (const ebiptablesRuleInstPtr)a;
const ebiptablesRuleInstPtr instb = (const ebiptablesRuleInstPtr)b;
const char *root = virNWFilterChainSuffixTypeToString(
VIR_NWFILTER_CHAINSUFFIX_ROOT);
bool root_a = STREQ(insta->neededProtocolChain, root);
bool root_b = STREQ(instb->neededProtocolChain, root);
/* ensure root chain commands appear before all others since
we will need them to create the child chains */
if (root_a) {
if (root_b) {
goto normal;
}
return -1; /* a before b */
}
if (root_b) {
return 1; /* b before a */
}
normal:
/* priorities are limited to range [-1000, 1000] */
return insta->priority - instb->priority;
}
static int
ebiptablesRuleOrderSortPtr(const void *a, const void *b)
{
const ebiptablesRuleInstPtr *insta = a;
const ebiptablesRuleInstPtr *instb = b;
return ebiptablesRuleOrderSort(*insta, *instb);
}
static int
ebiptablesFilterOrderSort(const virHashKeyValuePairPtr a,
const virHashKeyValuePairPtr b)
{
/* elements' values has been limited to range [-1000, 1000] */
return *(virNWFilterChainPriority *)a->value -
*(virNWFilterChainPriority *)b->value;
}
static void
iptablesCheckBridgeNFCallEnabled(bool isIPv6)
{
static time_t lastReport, lastReportIPv6;
const char *pathname = NULL;
char buffer[1];
time_t now = time(NULL);
if (isIPv6 &&
(now - lastReportIPv6) > BRIDGE_NF_CALL_ALERT_INTERVAL ) {
pathname = PROC_BRIDGE_NF_CALL_IP6TABLES;
} else if (now - lastReport > BRIDGE_NF_CALL_ALERT_INTERVAL) {
pathname = PROC_BRIDGE_NF_CALL_IPTABLES;
}
if (pathname) {
int fd = open(pathname, O_RDONLY);
if (fd >= 0) {
if (read(fd, buffer, 1) == 1) {
if (buffer[0] == '0') {
char msg[256];
snprintf(msg, sizeof(msg),
_("To enable ip%stables filtering for the VM do "
"'echo 1 > %s'"),
isIPv6 ? "6" : "",
pathname);
VIR_WARN("%s", msg);
if (isIPv6)
lastReportIPv6 = now;
else
lastReport = now;
}
}
VIR_FORCE_CLOSE(fd);
}
}
}
/*
* Given a filtername determine the protocol it is used for evaluating
* We do prefix-matching to determine the protocol.
*/
static enum l3_proto_idx
ebtablesGetProtoIdxByFiltername(const char *filtername)
{
enum l3_proto_idx idx;
for (idx = 0; idx < L3_PROTO_LAST_IDX; idx++) {
if (STRPREFIX(filtername, l3_protocols[idx].val)) {
return idx;
}
}
return -1;
}
static int
ebtablesCreateTmpRootAndSubChains(virBufferPtr buf,
const char *ifname,
virHashTablePtr chains, int direction,
ebiptablesRuleInstPtr *inst,
int *nRuleInstances)
{
int rc = 0, i;
virHashKeyValuePairPtr filter_names;
const virNWFilterChainPriority *priority;
if (ebtablesCreateTmpRootChain(buf, direction, ifname, 1) < 0)
return -1;
filter_names = virHashGetItems(chains,
ebiptablesFilterOrderSort);
if (filter_names == NULL)
return -1;
for (i = 0; filter_names[i].key; i++) {
enum l3_proto_idx idx = ebtablesGetProtoIdxByFiltername(
filter_names[i].key);
if ((int)idx < 0)
continue;
priority = (const virNWFilterChainPriority *)filter_names[i].value;
rc = ebtablesCreateTmpSubChain(inst, nRuleInstances,
direction, ifname, idx,
filter_names[i].key, 1,
*priority);
if (rc < 0)
break;
}
VIR_FREE(filter_names);
return rc;
}
static int
ebiptablesApplyNewRules(const char *ifname,
int nruleInstances,
void **_inst)
{
int i, j;
int cli_status;
ebiptablesRuleInstPtr *inst = (ebiptablesRuleInstPtr *)_inst;
virBuffer buf = VIR_BUFFER_INITIALIZER;
virHashTablePtr chains_in_set = virHashCreate(10, NULL);
virHashTablePtr chains_out_set = virHashCreate(10, NULL);
bool haveIptables = false;
bool haveIp6tables = false;
ebiptablesRuleInstPtr ebtChains = NULL;
int nEbtChains = 0;
char *errmsg = NULL;
if (inst == NULL)
nruleInstances = 0;
if (!chains_in_set || !chains_out_set) {
virReportOOMError();
goto exit_free_sets;
}
if (nruleInstances > 1 && inst)
qsort(inst, nruleInstances, sizeof(inst[0]),
ebiptablesRuleOrderSortPtr);
/* scan the rules to see which chains need to be created */
for (i = 0; i < nruleInstances; i++) {
sa_assert (inst);
if (inst[i]->ruleType == RT_EBTABLES) {
const char *name = inst[i]->neededProtocolChain;
if (inst[i]->chainprefix == CHAINPREFIX_HOST_IN_TEMP) {
if (virHashUpdateEntry(chains_in_set, name,
&inst[i]->chainPriority) < 0) {
virReportOOMError();
goto exit_free_sets;
}
} else {
if (virHashUpdateEntry(chains_out_set, name,
&inst[i]->chainPriority) < 0) {
virReportOOMError();
goto exit_free_sets;
}
}
}
}
/* cleanup whatever may exist */
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkTmpRootChain(&buf, 1, ifname);
ebtablesUnlinkTmpRootChain(&buf, 0, ifname);
ebtablesRemoveTmpSubChains(&buf, ifname);
ebtablesRemoveTmpRootChain(&buf, 1, ifname);
ebtablesRemoveTmpRootChain(&buf, 0, ifname);
ebiptablesExecCLI(&buf, &cli_status, NULL);
}
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
/* create needed chains */
if ((virHashSize(chains_in_set) > 0 &&
ebtablesCreateTmpRootAndSubChains(&buf, ifname, chains_in_set , 1,
&ebtChains, &nEbtChains) < 0) ||
(virHashSize(chains_out_set) > 0 &&
ebtablesCreateTmpRootAndSubChains(&buf, ifname, chains_out_set, 0,
&ebtChains, &nEbtChains) < 0)) {
goto tear_down_tmpebchains;
}
if (nEbtChains > 0)
qsort(&ebtChains[0], nEbtChains, sizeof(ebtChains[0]),
ebiptablesRuleOrderSort);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpebchains;
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
/* process ebtables commands; interleave commands from filters with
commands for creating and connecting ebtables chains */
j = 0;
for (i = 0; i < nruleInstances; i++) {
sa_assert (inst);
switch (inst[i]->ruleType) {
case RT_EBTABLES:
while (j < nEbtChains &&
ebtChains[j].priority <= inst[i]->priority) {
ebiptablesInstCommand(&buf,
ebtChains[j++].commandTemplate,
'A', -1, 1);
}
ebiptablesInstCommand(&buf,
inst[i]->commandTemplate,
'A', -1, 1);
break;
case RT_IPTABLES:
haveIptables = true;
break;
case RT_IP6TABLES:
haveIp6tables = true;
break;
}
}
while (j < nEbtChains)
ebiptablesInstCommand(&buf,
ebtChains[j++].commandTemplate,
'A', -1, 1);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpebchains;
if (haveIptables) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
iptablesCreateBaseChains(&buf);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpebchains;
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesCreateTmpRootChains(&buf, ifname);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpiptchains;
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesLinkTmpRootChains(&buf, ifname);
iptablesSetupVirtInPost(&buf, ifname);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpiptchains;
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
for (i = 0; i < nruleInstances; i++) {
sa_assert (inst);
if (inst[i]->ruleType == RT_IPTABLES)
iptablesInstCommand(&buf,
inst[i]->commandTemplate,
'A', -1, 1);
}
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpiptchains;
iptablesCheckBridgeNFCallEnabled(false);
}
if (haveIp6tables) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
iptablesCreateBaseChains(&buf);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpiptchains;
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesCreateTmpRootChains(&buf, ifname);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpip6tchains;
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesLinkTmpRootChains(&buf, ifname);
iptablesSetupVirtInPost(&buf, ifname);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpip6tchains;
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
for (i = 0; i < nruleInstances; i++) {
if (inst[i]->ruleType == RT_IP6TABLES)
iptablesInstCommand(&buf,
inst[i]->commandTemplate,
'A', -1, 1);
}
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_tmpip6tchains;
iptablesCheckBridgeNFCallEnabled(true);
}
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
if (virHashSize(chains_in_set) != 0)
ebtablesLinkTmpRootChain(&buf, 1, ifname, 1);
if (virHashSize(chains_out_set) != 0)
ebtablesLinkTmpRootChain(&buf, 0, ifname, 1);
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0)
goto tear_down_ebsubchains_and_unlink;
virHashFree(chains_in_set);
virHashFree(chains_out_set);
for (i = 0; i < nEbtChains; i++)
VIR_FREE(ebtChains[i].commandTemplate);
VIR_FREE(ebtChains);
VIR_FREE(errmsg);
return 0;
tear_down_ebsubchains_and_unlink:
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkTmpRootChain(&buf, 1, ifname);
ebtablesUnlinkTmpRootChain(&buf, 0, ifname);
}
tear_down_tmpip6tchains:
if (haveIp6tables) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
}
tear_down_tmpiptchains:
if (haveIptables) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
}
tear_down_tmpebchains:
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesRemoveTmpSubChains(&buf, ifname);
ebtablesRemoveTmpRootChain(&buf, 1, ifname);
ebtablesRemoveTmpRootChain(&buf, 0, ifname);
}
ebiptablesExecCLI(&buf, &cli_status, NULL);
virReportError(VIR_ERR_BUILD_FIREWALL,
_("Some rules could not be created for "
"interface %s%s%s"),
ifname,
errmsg ? ": " : "",
errmsg ? errmsg : "");
exit_free_sets:
virHashFree(chains_in_set);
virHashFree(chains_out_set);
for (i = 0; i < nEbtChains; i++)
VIR_FREE(ebtChains[i].commandTemplate);
VIR_FREE(ebtChains);
VIR_FREE(errmsg);
return -1;
}
static int
ebiptablesTearNewRules(const char *ifname)
{
int cli_status;
virBuffer buf = VIR_BUFFER_INITIALIZER;
if (iptables_cmd_path) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
}
if (ip6tables_cmd_path) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesUnlinkTmpRootChains(&buf, ifname);
iptablesRemoveTmpRootChains(&buf, ifname);
}
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkTmpRootChain(&buf, 1, ifname);
ebtablesUnlinkTmpRootChain(&buf, 0, ifname);
ebtablesRemoveTmpSubChains(&buf, ifname);
ebtablesRemoveTmpRootChain(&buf, 1, ifname);
ebtablesRemoveTmpRootChain(&buf, 0, ifname);
}
ebiptablesExecCLI(&buf, &cli_status, NULL);
return 0;
}
static int
ebiptablesTearOldRules(const char *ifname)
{
int cli_status;
virBuffer buf = VIR_BUFFER_INITIALIZER;
/* switch to new iptables user defined chains */
if (iptables_cmd_path) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesUnlinkRootChains(&buf, ifname);
iptablesRemoveRootChains(&buf, ifname);
iptablesRenameTmpRootChains(&buf, ifname);
ebiptablesExecCLI(&buf, &cli_status, NULL);
}
if (ip6tables_cmd_path) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesUnlinkRootChains(&buf, ifname);
iptablesRemoveRootChains(&buf, ifname);
iptablesRenameTmpRootChains(&buf, ifname);
ebiptablesExecCLI(&buf, &cli_status, NULL);
}
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkRootChain(&buf, 1, ifname);
ebtablesUnlinkRootChain(&buf, 0, ifname);
ebtablesRemoveSubChains(&buf, ifname);
ebtablesRemoveRootChain(&buf, 1, ifname);
ebtablesRemoveRootChain(&buf, 0, ifname);
ebtablesRenameTmpSubAndRootChains(&buf, ifname);
ebiptablesExecCLI(&buf, &cli_status, NULL);
}
return 0;
}
/**
* ebiptablesRemoveRules:
* @ifname : the name of the interface to which the rules apply
* @nRuleInstance : the number of given rules
* @_inst : array of rule instantiation data
*
* Remove all rules one after the other
*
* Return 0 on success, -1 if execution of one or more cleanup
* commands failed.
*/
static int
ebiptablesRemoveRules(const char *ifname ATTRIBUTE_UNUSED,
int nruleInstances,
void **_inst)
{
int rc = 0;
int cli_status;
int i;
virBuffer buf = VIR_BUFFER_INITIALIZER;
ebiptablesRuleInstPtr *inst = (ebiptablesRuleInstPtr *)_inst;
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
for (i = 0; i < nruleInstances; i++)
ebiptablesInstCommand(&buf,
inst[i]->commandTemplate,
'D', -1,
0);
if (ebiptablesExecCLI(&buf, &cli_status, NULL) < 0)
goto err_exit;
if (cli_status) {
virReportError(VIR_ERR_BUILD_FIREWALL,
"%s",
_("error while executing CLI commands"));
rc = -1;
}
err_exit:
return rc;
}
/**
* ebiptablesAllTeardown:
* @ifname : the name of the interface to which the rules apply
*
* Unconditionally remove all possible user defined tables and rules
* that were created for the given interface (ifname).
*
* Always returns 0.
*/
static int
ebiptablesAllTeardown(const char *ifname)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int cli_status;
if (iptables_cmd_path) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
iptablesUnlinkRootChains(&buf, ifname);
iptablesClearVirtInPost (&buf, ifname);
iptablesRemoveRootChains(&buf, ifname);
}
if (ip6tables_cmd_path) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
iptablesUnlinkRootChains(&buf, ifname);
iptablesClearVirtInPost (&buf, ifname);
iptablesRemoveRootChains(&buf, ifname);
}
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
ebtablesUnlinkRootChain(&buf, 1, ifname);
ebtablesUnlinkRootChain(&buf, 0, ifname);
ebtablesRemoveSubChains(&buf, ifname);
ebtablesRemoveRootChain(&buf, 1, ifname);
ebtablesRemoveRootChain(&buf, 0, ifname);
}
ebiptablesExecCLI(&buf, &cli_status, NULL);
return 0;
}
virNWFilterTechDriver ebiptables_driver = {
.name = EBIPTABLES_DRIVER_ID,
.flags = 0,
.init = ebiptablesDriverInit,
.shutdown = ebiptablesDriverShutdown,
.createRuleInstance = ebiptablesCreateRuleInstanceIterate,
.applyNewRules = ebiptablesApplyNewRules,
.tearNewRules = ebiptablesTearNewRules,
.tearOldRules = ebiptablesTearOldRules,
.allTeardown = ebiptablesAllTeardown,
.removeRules = ebiptablesRemoveRules,
.freeRuleInstance = ebiptablesFreeRuleInstance,
.displayRuleInstance = ebiptablesDisplayRuleInstance,
.canApplyBasicRules = ebiptablesCanApplyBasicRules,
.applyBasicRules = ebtablesApplyBasicRules,
.applyDHCPOnlyRules = ebtablesApplyDHCPOnlyRules,
.applyDropAllRules = ebtablesApplyDropAllRules,
.removeBasicRules = ebtablesRemoveBasicRules,
};
/*
* ebiptablesDriverInitWithFirewallD
*
* Try to use firewall-cmd by testing it once; if it works, have ebtables
* and ip6tables commands use firewall-cmd.
*/
static int
ebiptablesDriverInitWithFirewallD(void)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *firewall_cmd_path;
char *output = NULL;
int status;
int ret = -1;
if (!virNWFilterDriverIsWatchingFirewallD())
return -1;
firewall_cmd_path = virFindFileInPath("firewall-cmd");
if (firewall_cmd_path) {
virBufferAsprintf(&buf, "FWC=%s\n", firewall_cmd_path);
virBufferAsprintf(&buf,
CMD_DEF("$FWC --state") CMD_SEPARATOR
CMD_EXEC
"%s",
CMD_STOPONERR(1));
if (ebiptablesExecCLI(&buf, &status, &output) < 0 ||
status != 0) {
VIR_INFO("firewalld support disabled for nwfilter");
} else {
VIR_INFO("firewalld support enabled for nwfilter");
ignore_value(virAsprintf(&ebtables_cmd_path,
"%s --direct --passthrough eb",
firewall_cmd_path));
ignore_value(virAsprintf(&iptables_cmd_path,
"%s --direct --passthrough ipv4",
firewall_cmd_path));
ignore_value(virAsprintf(&ip6tables_cmd_path,
"%s --direct --passthrough ipv6",
firewall_cmd_path));
if (!ebtables_cmd_path || !iptables_cmd_path ||
!ip6tables_cmd_path) {
virReportOOMError();
VIR_FREE(ebtables_cmd_path);
VIR_FREE(iptables_cmd_path);
VIR_FREE(ip6tables_cmd_path);
ret = -1;
goto err_exit;
}
ret = 0;
}
}
err_exit:
VIR_FREE(firewall_cmd_path);
VIR_FREE(output);
return ret;
}
static int
ebiptablesDriverInitCLITools(void)
{
ebtables_cmd_path = virFindFileInPath("ebtables");
if (!ebtables_cmd_path)
VIR_WARN("Could not find 'ebtables' executable");
iptables_cmd_path = virFindFileInPath("iptables");
if (!iptables_cmd_path)
VIR_WARN("Could not find 'iptables' executable");
ip6tables_cmd_path = virFindFileInPath("ip6tables");
if (!ip6tables_cmd_path)
VIR_WARN("Could not find 'ip6tables' executable");
return 0;
}
/*
* ebiptablesDriverTestCLITools
*
* Test the CLI tools. If one is found not to be working, free the buffer
* holding its path as a sign that the tool cannot be used.
*/
static int
ebiptablesDriverTestCLITools(void)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
char *errmsg = NULL;
int ret = 0;
if (ebtables_cmd_path) {
NWFILTER_SET_EBTABLES_SHELLVAR(&buf);
/* basic probing */
virBufferAsprintf(&buf,
CMD_DEF("$EBT -t nat -L") CMD_SEPARATOR
CMD_EXEC
"%s",
CMD_STOPONERR(1));
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0) {
VIR_FREE(ebtables_cmd_path);
VIR_ERROR(_("Testing of ebtables command failed: %s"),
errmsg);
ret = -1;
}
}
if (iptables_cmd_path) {
NWFILTER_SET_IPTABLES_SHELLVAR(&buf);
virBufferAsprintf(&buf,
CMD_DEF("$IPT -n -L FORWARD") CMD_SEPARATOR
CMD_EXEC
"%s",
CMD_STOPONERR(1));
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0) {
VIR_FREE(iptables_cmd_path);
VIR_ERROR(_("Testing of iptables command failed: %s"),
errmsg);
ret = -1;
}
}
if (ip6tables_cmd_path) {
NWFILTER_SET_IP6TABLES_SHELLVAR(&buf);
virBufferAsprintf(&buf,
CMD_DEF("$IPT -n -L FORWARD") CMD_SEPARATOR
CMD_EXEC
"%s",
CMD_STOPONERR(1));
if (ebiptablesExecCLI(&buf, NULL, &errmsg) < 0) {
VIR_FREE(ip6tables_cmd_path);
VIR_ERROR(_("Testing of ip6tables command failed: %s"),
errmsg);
ret = -1;
}
}
VIR_FREE(errmsg);
return ret;
}
static int
ebiptablesDriverInit(bool privileged)
{
if (!privileged)
return 0;
if (virMutexInit(&execCLIMutex) < 0)
return -EINVAL;
grep_cmd_path = virFindFileInPath("grep");
/*
* check whether we can run with firewalld's tools --
* if not, we just fall back to eb/iptables command
* line tools.
*/
if (ebiptablesDriverInitWithFirewallD() < 0)
ebiptablesDriverInitCLITools();
/* make sure tools are available and work */
ebiptablesDriverTestCLITools();
/* ip(6)tables support needs awk & grep, ebtables doesn't */
if ((iptables_cmd_path != NULL || ip6tables_cmd_path != NULL) &&
!grep_cmd_path) {
VIR_ERROR(_("essential tools to support ip(6)tables "
"firewalls could not be located"));
VIR_FREE(iptables_cmd_path);
VIR_FREE(ip6tables_cmd_path);
}
if (!ebtables_cmd_path && !iptables_cmd_path && !ip6tables_cmd_path) {
VIR_ERROR(_("firewall tools were not found or cannot be used"));
ebiptablesDriverShutdown();
return -ENOTSUP;
}
ebiptables_driver.flags = TECHDRV_FLAG_INITIALIZED;
return 0;
}
static void
ebiptablesDriverShutdown(void)
{
VIR_FREE(grep_cmd_path);
VIR_FREE(ebtables_cmd_path);
VIR_FREE(iptables_cmd_path);
VIR_FREE(ip6tables_cmd_path);
ebiptables_driver.flags = 0;
}