/* * nwfilter_ebiptables_driver.c: driver for ebtables/iptables on tap devices * * Copyright (C) 2011-2014 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 * . */ #include #include #include #include #include "internal.h" #include "virbuffer.h" #include "viralloc.h" #include "virlog.h" #include "virerror.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 "vircommand.h" #include "configmake.h" #include "virstring.h" #include "virfirewall.h" #include "virutil.h" #define VIR_FROM_THIS VIR_FROM_NWFILTER VIR_LOG_INIT("nwfilter.nwfilter_ebiptables_driver"); #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' #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 */ /* * --ctdir original vs. --ctdir reply's meaning was inverted in netfilter * at some point (Linux 2.6.39) */ enum ctdirStatus { CTDIR_STATUS_UNKNOWN = 0, CTDIR_STATUS_CORRECTED = 1, CTDIR_STATUS_OLD = 2, }; static enum ctdirStatus iptables_ctdir_corrected; #define PRINT_ROOT_CHAIN(buf, prefix, ifname) \ g_snprintf(buf, sizeof(buf), "libvirt-%c-%s", prefix, ifname) #define PRINT_CHAIN(buf, prefix, ifname, suffix) \ g_snprintf(buf, sizeof(buf), "%c-%s-%s", prefix, ifname, suffix) #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) \ g_snprintf(buf, sizeof(buf), "%c%c-%s", prefix[0], prefix[1], ifname) static bool newMatchState; #define MATCH_PHYSDEV_IN_FW "-m", "physdev", "--physdev-in" #define MATCH_PHYSDEV_OUT_FW "-m", "physdev", "--physdev-is-bridged", "--physdev-out" #define MATCH_PHYSDEV_OUT_OLD_FW "-m", "physdev", "--physdev-out" 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); 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 char chainprefixes_host[3] = { CHAINPREFIX_HOST_IN, CHAINPREFIX_HOST_OUT, 0 }; static char chainprefixes_host_temp[3] = { CHAINPREFIX_HOST_IN_TEMP, CHAINPREFIX_HOST_OUT_TEMP, 0 }; static int printVar(virNWFilterVarCombIter *vars, char *buf, int bufsize, nwItemDesc *item, bool *done) { *done = false; 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) < 0) { const char *varName; varName = virNWFilterVarAccessGetVarName(item->varAccess); virReportError(VIR_ERR_INTERNAL_ERROR, _("Buffer too small to print variable " "'%s' into"), varName); return -1; } *done = true; } return 0; } static int _printDataType(virNWFilterVarCombIter *vars, char *buf, int bufsize, nwItemDesc *item, bool asHex, bool directionIn) { bool done; g_autofree char *data = NULL; uint8_t ctr; g_auto(virBuffer) vb = VIR_BUFFER_INITIALIZER; g_autofree char *flags = NULL; 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 (g_snprintf(buf, bufsize, "%s", data) >= bufsize) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("buffer too small for IP address")); return -1; } break; case DATATYPE_IPV6ADDR: data = virSocketAddrFormat(&item->u.ipaddr); if (!data) return -1; if (g_snprintf(buf, bufsize, "%s", data) >= bufsize) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("buffer too small for IPv6 address")); return -1; } 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 (g_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 (g_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 (g_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 (g_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) < 0) { 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"); } } flags = virBufferContentAndReset(&vb); if (virStrcpy(buf, flags, bufsize) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("Buffer too small for IPSETFLAGS type")); return -1; } break; case DATATYPE_STRING: case DATATYPE_STRINGCOPY: case DATATYPE_BOOLEAN: virReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot print data type %x"), item->datatype); return -1; case DATATYPE_LAST: default: virReportEnumRangeError(virNWFilterAttrDataType, item->datatype); return -1; } return 0; } static int printDataType(virNWFilterVarCombIter *vars, char *buf, int bufsize, nwItemDesc *item) { return _printDataType(vars, buf, bufsize, item, 0, 0); } static int printDataTypeDirection(virNWFilterVarCombIter *vars, char *buf, int bufsize, nwItemDesc *item, bool directionIn) { return _printDataType(vars, buf, bufsize, item, 0, directionIn); } static int printDataTypeAsHex(virNWFilterVarCombIter *vars, char *buf, int bufsize, nwItemDesc *item) { return _printDataType(vars, buf, bufsize, item, 1, 0); } static int ebtablesHandleEthHdr(virFirewall *fw, virFirewallRule *fwrule, virNWFilterVarCombIter *vars, ethHdrDataDef *ethHdr, bool reverse) { char macaddr[VIR_MAC_STRING_BUFLEN]; char macmask[VIR_MAC_STRING_BUFLEN]; if (HAS_ENTRY_ITEM(ðHdr->dataSrcMACAddr)) { if (printDataType(vars, macaddr, sizeof(macaddr), ðHdr->dataSrcMACAddr) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, reverse ? "-d" : "-s", NULL); if (ENTRY_WANT_NEG_SIGN(ðHdr->dataSrcMACAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(ðHdr->dataSrcMACMask)) { if (printDataType(vars, macmask, sizeof(macmask), ðHdr->dataSrcMACMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", macaddr, macmask); } else { virFirewallRuleAddArg(fw, fwrule, macaddr); } } if (HAS_ENTRY_ITEM(ðHdr->dataDstMACAddr)) { if (printDataType(vars, macaddr, sizeof(macaddr), ðHdr->dataDstMACAddr) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, reverse ? "-s" : "-d", NULL); if (ENTRY_WANT_NEG_SIGN(ðHdr->dataDstMACAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(ðHdr->dataDstMACMask)) { if (printDataType(vars, macmask, sizeof(macmask), ðHdr->dataDstMACMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", macaddr, macmask); } else { virFirewallRuleAddArg(fw, fwrule, macaddr); } } return 0; } /************************ iptables support ************************/ static void iptablesCreateBaseChainsFW(virFirewall *fw, virFirewallLayer layer) { virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-N", VIRT_IN_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-N", VIRT_OUT_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-N", VIRT_IN_POST_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-N", HOST_IN_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", "FORWARD", "-j", VIRT_IN_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", "FORWARD", "-j", VIRT_OUT_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", "FORWARD", "-j", VIRT_IN_POST_CHAIN, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", "INPUT", "-j", HOST_IN_CHAIN, NULL); virFirewallAddRule(fw, layer, "-I", "FORWARD", "1", "-j", VIRT_IN_CHAIN, NULL); virFirewallAddRule(fw, layer, "-I", "FORWARD", "2", "-j", VIRT_OUT_CHAIN, NULL); virFirewallAddRule(fw, layer, "-I", "FORWARD", "3", "-j", VIRT_IN_POST_CHAIN, NULL); virFirewallAddRule(fw, layer, "-I", "INPUT", "1", "-j", HOST_IN_CHAIN, NULL); } static void iptablesCreateTmpRootChainFW(virFirewall *fw, virFirewallLayer layer, char prefix, bool incoming, const char *ifname) { 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); virFirewallAddRule(fw, layer, "-N", chain, NULL); } static void iptablesCreateTmpRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesCreateTmpRootChainFW(fw, layer, 'F', false, ifname); iptablesCreateTmpRootChainFW(fw, layer, 'F', true, ifname); iptablesCreateTmpRootChainFW(fw, layer, 'H', true, ifname); } static void _iptablesRemoveRootChainFW(virFirewall *fw, virFirewallLayer layer, char prefix, bool 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); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-F", chain, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-X", chain, NULL); } static void iptablesRemoveRootChainFW(virFirewall *fw, virFirewallLayer layer, char prefix, bool incoming, const char *ifname) { _iptablesRemoveRootChainFW(fw, layer, prefix, incoming, ifname, false); } static void iptablesRemoveTmpRootChainFW(virFirewall *fw, virFirewallLayer layer, char prefix, bool incoming, const char *ifname) { _iptablesRemoveRootChainFW(fw, layer, prefix, incoming, ifname, 1); } static void iptablesRemoveTmpRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesRemoveTmpRootChainFW(fw, layer, 'F', false, ifname); iptablesRemoveTmpRootChainFW(fw, layer, 'F', true, ifname); iptablesRemoveTmpRootChainFW(fw, layer, 'H', true, ifname); } static void iptablesRemoveRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesRemoveRootChainFW(fw, layer, 'F', false, ifname); iptablesRemoveRootChainFW(fw, layer, 'F', true, ifname); iptablesRemoveRootChainFW(fw, layer, 'H', true, ifname); } static void iptablesLinkTmpRootChainFW(virFirewall *fw, virFirewallLayer layer, const char *basechain, char prefix, bool incoming, const char *ifname) { 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); if (incoming) virFirewallAddRule(fw, layer, "-A", basechain, MATCH_PHYSDEV_IN_FW, ifname, "-g", chain, NULL); else virFirewallAddRule(fw, layer, "-A", basechain, MATCH_PHYSDEV_OUT_FW, ifname, "-g", chain, NULL); } static void iptablesLinkTmpRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesLinkTmpRootChainFW(fw, layer, VIRT_OUT_CHAIN, 'F', false, ifname); iptablesLinkTmpRootChainFW(fw, layer, VIRT_IN_CHAIN, 'F', true, ifname); iptablesLinkTmpRootChainFW(fw, layer, HOST_IN_CHAIN, 'H', true, ifname); } static void iptablesSetupVirtInPostFW(virFirewall *fw G_GNUC_UNUSED, virFirewallLayer layer G_GNUC_UNUSED, const char *ifname G_GNUC_UNUSED) { virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", VIRT_IN_POST_CHAIN, MATCH_PHYSDEV_IN_FW, ifname, "-j", "ACCEPT", NULL); virFirewallAddRule(fw, layer, "-A", VIRT_IN_POST_CHAIN, MATCH_PHYSDEV_IN_FW, ifname, "-j", "ACCEPT", NULL); } static void iptablesClearVirtInPostFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", VIRT_IN_POST_CHAIN, MATCH_PHYSDEV_IN_FW, ifname, "-j", "ACCEPT", NULL); } static void _iptablesUnlinkRootChainFW(virFirewall *fw, virFirewallLayer layer, const char *basechain, char prefix, bool 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); if (incoming) virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", basechain, MATCH_PHYSDEV_IN_FW, ifname, "-g", chain, NULL); else virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", basechain, MATCH_PHYSDEV_OUT_FW, ifname, "-g", chain, NULL); /* * Previous versions of libvirt may have created a rule * with the --physdev-is-bridged missing. Remove this one * as well. */ if (!incoming) virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-D", basechain, MATCH_PHYSDEV_OUT_OLD_FW, ifname, "-g", chain, NULL); } static void iptablesUnlinkRootChainFW(virFirewall *fw, virFirewallLayer layer, const char *basechain, char prefix, bool incoming, const char *ifname) { _iptablesUnlinkRootChainFW(fw, layer, basechain, prefix, incoming, ifname, false); } static void iptablesUnlinkTmpRootChainFW(virFirewall *fw, virFirewallLayer layer, const char *basechain, char prefix, bool incoming, const char *ifname) { _iptablesUnlinkRootChainFW(fw, layer, basechain, prefix, incoming, ifname, 1); } static void iptablesUnlinkRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesUnlinkRootChainFW(fw, layer, VIRT_OUT_CHAIN, 'F', false, ifname); iptablesUnlinkRootChainFW(fw, layer, VIRT_IN_CHAIN, 'F', true, ifname); iptablesUnlinkRootChainFW(fw, layer, HOST_IN_CHAIN, 'H', true, ifname); } static void iptablesUnlinkTmpRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesUnlinkTmpRootChainFW(fw, layer, VIRT_OUT_CHAIN, 'F', false, ifname); iptablesUnlinkTmpRootChainFW(fw, layer, VIRT_IN_CHAIN, 'F', true, ifname); iptablesUnlinkTmpRootChainFW(fw, layer, HOST_IN_CHAIN, 'H', true, ifname); } static void iptablesRenameTmpRootChainFW(virFirewall *fw, virFirewallLayer layer, char prefix, bool 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); virFirewallAddRule(fw, layer, "-E", tmpchain, chain, NULL); } static void iptablesRenameTmpRootChainsFW(virFirewall *fw, virFirewallLayer layer, const char *ifname) { iptablesRenameTmpRootChainFW(fw, layer, 'F', false, ifname); iptablesRenameTmpRootChainFW(fw, layer, 'F', true, ifname); iptablesRenameTmpRootChainFW(fw, layer, 'H', true, ifname); } static int iptablesHandleSrcMacAddr(virFirewall *fw, virFirewallRule *fwrule, virNWFilterVarCombIter *vars, nwItemDesc *srcMacAddr, bool 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) return -1; virFirewallRuleAddArgList(fw, fwrule, "-m", "mac", NULL); if (ENTRY_WANT_NEG_SIGN(srcMacAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgList(fw, fwrule, "--mac-source", macaddr, NULL); } return 0; } static int iptablesHandleIPHdr(virFirewall *fw, virFirewallRule *fwrule, virNWFilterVarCombIter *vars, ipHdrDataDef *ipHdr, bool directionIn, bool *skipRule, bool *skipMatch) { char ipaddr[INET6_ADDRSTRLEN]; char ipaddralt[INET6_ADDRSTRLEN]; char number[VIR_INT64_STR_BUFLEN]; 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->dataSrcIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &ipHdr->dataSrcIPAddr) < 0) return -1; if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataSrcIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, src); if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPMask)) { if (printDataType(vars, number, sizeof(number), &ipHdr->dataSrcIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } else if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPFrom)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &ipHdr->dataSrcIPFrom) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-m", "iprange", NULL); if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataSrcIPFrom)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, srcrange); if (HAS_ENTRY_ITEM(&ipHdr->dataSrcIPTo)) { if (printDataType(vars, ipaddralt, sizeof(ipaddralt), &ipHdr->dataSrcIPTo) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s-%s", ipaddr, ipaddralt); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &ipHdr->dataDstIPAddr) < 0) return -1; if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataDstIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, dst); if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPMask)) { if (printDataType(vars, number, sizeof(number), &ipHdr->dataDstIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } else if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPFrom)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &ipHdr->dataDstIPFrom) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-m", "iprange", NULL); if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataDstIPFrom)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, dstrange); if (HAS_ENTRY_ITEM(&ipHdr->dataDstIPTo)) { if (printDataType(vars, ipaddralt, sizeof(ipaddralt), &ipHdr->dataDstIPTo) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s-%s", ipaddr, ipaddralt); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } if (HAS_ENTRY_ITEM(&ipHdr->dataDSCP)) { if (printDataType(vars, number, sizeof(number), &ipHdr->dataDSCP) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-m", "dscp", NULL); if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataDSCP)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgList(fw, fwrule, "--dscp", number, NULL); } if (HAS_ENTRY_ITEM(&ipHdr->dataConnlimitAbove)) { if (directionIn) { /* only support for limit in outgoing dir. */ *skipRule = true; } else { *skipMatch = true; } } return 0; } static int iptablesHandleIPHdrAfterStateMatch(virFirewall *fw, virFirewallRule *fwrule, virNWFilterVarCombIter *vars, ipHdrDataDef *ipHdr, bool directionIn) { char number[VIR_INT64_STR_BUFLEN]; char str[MAX_IPSET_NAME_LENGTH]; if (HAS_ENTRY_ITEM(&ipHdr->dataIPSet) && HAS_ENTRY_ITEM(&ipHdr->dataIPSetFlags)) { if (printDataType(vars, str, sizeof(str), &ipHdr->dataIPSet) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-m", "set", "--match-set", str, NULL); if (printDataTypeDirection(vars, str, sizeof(str), &ipHdr->dataIPSetFlags, directionIn) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, str); } if (HAS_ENTRY_ITEM(&ipHdr->dataConnlimitAbove)) { if (!directionIn) { if (printDataType(vars, number, sizeof(number), &ipHdr->dataConnlimitAbove) < 0) return -1; /* place connlimit after potential -m state --state ... since this is the most useful order */ virFirewallRuleAddArgList(fw, fwrule, "-m", "connlimit", NULL); if (ENTRY_WANT_NEG_SIGN(&ipHdr->dataConnlimitAbove)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgList(fw, fwrule, "--connlimit-above", number, NULL); } } if (HAS_ENTRY_ITEM(&ipHdr->dataComment)) { /* keep comments behind everything else -- they are packet eval. no-ops */ virFirewallRuleAddArgList(fw, fwrule, "-m", "comment", "--comment", ipHdr->dataComment.u.string, NULL); } return 0; } static int iptablesHandlePortData(virFirewall *fw, virFirewallRule *fwrule, virNWFilterVarCombIter *vars, portDataDef *portData, bool directionIn) { char portstr[20]; char portstralt[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) return -1; if (ENTRY_WANT_NEG_SIGN(&portData->dataSrcPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, sport); if (HAS_ENTRY_ITEM(&portData->dataSrcPortEnd)) { if (printDataType(vars, portstralt, sizeof(portstralt), &portData->dataSrcPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", portstr, portstralt); } else { virFirewallRuleAddArg(fw, fwrule, portstr); } } if (HAS_ENTRY_ITEM(&portData->dataDstPortStart)) { if (printDataType(vars, portstr, sizeof(portstr), &portData->dataDstPortStart) < 0) return -1; if (ENTRY_WANT_NEG_SIGN(&portData->dataDstPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, dport); if (HAS_ENTRY_ITEM(&portData->dataDstPortEnd)) { if (printDataType(vars, portstralt, sizeof(portstralt), &portData->dataDstPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", portstr, portstralt); } else { virFirewallRuleAddArg(fw, fwrule, portstr); } } return 0; } static void iptablesEnforceDirection(virFirewall *fw, virFirewallRule *fwrule, bool directionIn, virNWFilterRuleDef *rule) { switch (iptables_ctdir_corrected) { case CTDIR_STATUS_UNKNOWN: /* could not be determined or s.th. is seriously wrong */ return; case CTDIR_STATUS_CORRECTED: directionIn = !directionIn; break; case CTDIR_STATUS_OLD: break; } if (rule->tt != VIR_NWFILTER_RULE_DIRECTION_INOUT) virFirewallRuleAddArgList(fw, fwrule, "-m", "conntrack", "--ctdir", (directionIn ? "Original" : "Reply"), NULL); } /* * _iptablesCreateRuleInstance: * @fw: the firewall ruleset instance * @layer: the firewall layer * @chainPrefix : The prefix to put in front of the name of the chain * @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 * @match : optional string for state match * @accept_target : where to jump to on accepted traffic, i.e., "RETURN" * "ACCEPT" * @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(virFirewall *fw, virFirewallLayer layer, bool directionIn, const char *chainPrefix, virNWFilterRuleDef *rule, const char *ifname, virNWFilterVarCombIter *vars, const char *match, bool defMatch, const char *accept_target, bool maySkipICMP) { char chain[MAX_CHAINNAME_LENGTH]; char number[VIR_INT64_STR_BUFLEN]; char numberalt[VIR_INT64_STR_BUFLEN]; const char *target; bool srcMacSkipped = false; bool skipRule = false; bool skipMatch = false; bool hasICMPType = false; virFirewallRule *fwrule; size_t fwruleargs; PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname); switch ((int)rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_TCP: case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "tcp", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.tcpHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.tcpHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.tcpHdrFilter.dataTCPFlags)) { g_autofree char *mask = NULL; g_autofree char *flags = NULL; if (ENTRY_WANT_NEG_SIGN(&rule->p.tcpHdrFilter.dataTCPFlags)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, "--tcp-flags"); if (!(mask = virNWFilterPrintTCPFlags(rule->p.tcpHdrFilter.dataTCPFlags.u.tcpFlags.mask))) return -1; virFirewallRuleAddArg(fw, fwrule, mask); if (!(flags = virNWFilterPrintTCPFlags(rule->p.tcpHdrFilter.dataTCPFlags.u.tcpFlags.flags))) return -1; virFirewallRuleAddArg(fw, fwrule, flags); } if (iptablesHandlePortData(fw, fwrule, vars, &rule->p.tcpHdrFilter.portData, directionIn) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.tcpHdrFilter.dataTCPOption)) { if (printDataType(vars, number, sizeof(number), &rule->p.tcpHdrFilter.dataTCPOption) < 0) return -1; if (ENTRY_WANT_NEG_SIGN(&rule->p.tcpHdrFilter.dataTCPOption)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgList(fw, fwrule, "--tcp-option", number, NULL); } break; case VIR_NWFILTER_RULE_PROTOCOL_UDP: case VIR_NWFILTER_RULE_PROTOCOL_UDPoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "udp", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.udpHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.udpHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; if (iptablesHandlePortData(fw, fwrule, vars, &rule->p.udpHdrFilter.portData, directionIn) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_UDPLITE: case VIR_NWFILTER_RULE_PROTOCOL_UDPLITEoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "udplite", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.udpliteHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.udpliteHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_ESP: case VIR_NWFILTER_RULE_PROTOCOL_ESPoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "esp", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.espHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.espHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_AH: case VIR_NWFILTER_RULE_PROTOCOL_AHoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "ah", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.ahHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.ahHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_SCTP: case VIR_NWFILTER_RULE_PROTOCOL_SCTPoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "sctp", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.sctpHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.sctpHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; if (iptablesHandlePortData(fw, fwrule, vars, &rule->p.sctpHdrFilter.portData, directionIn) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_ICMP: case VIR_NWFILTER_RULE_PROTOCOL_ICMPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, NULL); if (rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ICMP) virFirewallRuleAddArgList(fw, fwrule, "-p", "icmp", NULL); else virFirewallRuleAddArgList(fw, fwrule, "-p", "icmpv6", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.icmpHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.icmpHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.icmpHdrFilter.dataICMPType)) { const char *parm; hasICMPType = true; if (maySkipICMP) { virFirewallRemoveRule(fw, fwrule); return 0; } 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) return -1; if (ENTRY_WANT_NEG_SIGN(&rule->p.icmpHdrFilter.dataICMPType)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, parm); if (HAS_ENTRY_ITEM(&rule->p.icmpHdrFilter.dataICMPCode)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.icmpHdrFilter.dataICMPCode) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", number, numberalt); } else { virFirewallRuleAddArg(fw, fwrule, number); } } break; case VIR_NWFILTER_RULE_PROTOCOL_IGMP: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "igmp", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.igmpHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.igmpHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; break; case VIR_NWFILTER_RULE_PROTOCOL_ALL: case VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6: fwrule = virFirewallAddRule(fw, layer, "-A", chain, "-p", "all", NULL); fwruleargs = virFirewallRuleGetArgCount(fwrule); if (iptablesHandleSrcMacAddr(fw, fwrule, vars, &rule->p.allHdrFilter.dataSrcMACAddr, directionIn, &srcMacSkipped) < 0) return -1; if (iptablesHandleIPHdr(fw, fwrule, vars, &rule->p.allHdrFilter.ipHdr, directionIn, &skipRule, &skipMatch) < 0) return -1; break; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected protocol %d"), rule->prtclType); return -1; } if ((srcMacSkipped && fwruleargs == virFirewallRuleGetArgCount(fwrule)) || skipRule) { virFirewallRemoveRule(fw, fwrule); return 0; } if (rule->action == VIR_NWFILTER_RULE_ACTION_ACCEPT) { target = accept_target; } else { target = virNWFilterJumpTargetTypeToString(rule->action); skipMatch = defMatch; } if (match && !skipMatch) { if (newMatchState) virFirewallRuleAddArgList(fw, fwrule, "-m", "conntrack", "--ctstate", match, NULL); else virFirewallRuleAddArgList(fw, fwrule, "-m", "state", "--state", match, NULL); } if (defMatch && match != NULL && !skipMatch && !hasICMPType) iptablesEnforceDirection(fw, fwrule, directionIn, rule); if (iptablesHandleIPHdrAfterStateMatch(fw, fwrule, vars, &rule->p.allHdrFilter.ipHdr, directionIn) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-j", target, NULL); return 0; } static int printStateMatchFlags(int32_t flags, char **bufptr) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; virNWFilterPrintStateMatchFlags(&buf, "", flags, false); *bufptr = virBufferContentAndReset(&buf); return 0; } static int iptablesCreateRuleInstanceStateCtrl(virFirewall *fw, virFirewallLayer layer, virNWFilterRuleDef *rule, const char *ifname, virNWFilterVarCombIter *vars) { int rc = 0; bool directionIn = false; char chainPrefix[2]; bool maySkipICMP, inout = false; g_autofree char *matchState1 = NULL; g_autofree char *matchState2 = NULL; g_autofree char *matchState3 = NULL; bool create; if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) || (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT)) { directionIn = true; inout = (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT); } chainPrefix[0] = 'F'; maySkipICMP = directionIn || inout; create = true; if (directionIn && !inout) { if ((rule->flags & IPTABLES_STATE_FLAGS)) create = false; } if (create && (rule->flags & IPTABLES_STATE_FLAGS)) { if (printStateMatchFlags(rule->flags, &matchState1) < 0) return -1; } chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; if (create) { rc = _iptablesCreateRuleInstance(fw, layer, directionIn, chainPrefix, rule, ifname, vars, matchState1, false, "RETURN", maySkipICMP); 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, &matchState2) < 0) return -1; } chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; if (create) { rc = _iptablesCreateRuleInstance(fw, layer, !directionIn, chainPrefix, rule, ifname, vars, matchState2, false, "ACCEPT", maySkipICMP); 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, &matchState3) < 0) return -1; } } if (create) { chainPrefix[0] = 'H'; chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; rc = _iptablesCreateRuleInstance(fw, layer, directionIn, chainPrefix, rule, ifname, vars, matchState3, false, "RETURN", maySkipICMP); } return rc; } static int iptablesCreateRuleInstance(virFirewall *fw, virFirewallLayer layer, virNWFilterRuleDef *rule, const char *ifname, virNWFilterVarCombIter *vars) { int rc; bool directionIn = false; char chainPrefix[2]; bool needState = true; bool maySkipICMP, inout = false; const char *matchState; if (!(rule->flags & RULE_FLAG_NO_STATEMATCH) && (rule->flags & IPTABLES_STATE_FLAGS)) { return iptablesCreateRuleInstanceStateCtrl(fw, layer, rule, ifname, vars); } if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) || (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT)) { directionIn = true; inout = (rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT); if (inout) needState = false; } if ((rule->flags & RULE_FLAG_NO_STATEMATCH)) needState = false; chainPrefix[0] = 'F'; maySkipICMP = directionIn || inout; if (needState) matchState = directionIn ? "ESTABLISHED" : "NEW,ESTABLISHED"; else matchState = NULL; chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; rc = _iptablesCreateRuleInstance(fw, layer, directionIn, chainPrefix, rule, ifname, vars, matchState, true, "RETURN", maySkipICMP); if (rc < 0) return rc; maySkipICMP = !directionIn || inout; if (needState) matchState = directionIn ? "NEW,ESTABLISHED" : "ESTABLISHED"; else matchState = NULL; chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; rc = _iptablesCreateRuleInstance(fw, layer, !directionIn, chainPrefix, rule, ifname, vars, matchState, true, "ACCEPT", maySkipICMP); if (rc < 0) return rc; maySkipICMP = directionIn; if (needState) matchState = directionIn ? "ESTABLISHED" : "NEW,ESTABLISHED"; else matchState = NULL; chainPrefix[0] = 'H'; chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; rc = _iptablesCreateRuleInstance(fw, layer, directionIn, chainPrefix, rule, ifname, vars, matchState, true, "RETURN", maySkipICMP); return rc; } /* * ebtablesCreateRuleInstance: * @fw: the firewall ruleset to add to * @chainPrefix : The prefix to put in front of the name of the chain * @chainSuffix: The suffix to put on the end of the name of the chain * @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 * @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(virFirewall *fw, char chainPrefix, const char *chainSuffix, virNWFilterRuleDef *rule, const char *ifname, virNWFilterVarCombIter *vars, bool reverse) { char macaddr[VIR_MAC_STRING_BUFLEN]; char ipaddr[INET_ADDRSTRLEN]; char ipmask[INET_ADDRSTRLEN]; char ipv6addr[INET6_ADDRSTRLEN]; char number[VIR_INT64_STR_BUFLEN]; char numberalt[VIR_INT64_STR_BUFLEN]; char field[VIR_INT64_STR_BUFLEN]; char fieldalt[VIR_INT64_STR_BUFLEN]; char chain[MAX_CHAINNAME_LENGTH]; const char *target; bool hasMask = false; virFirewallRule *fwrule; if (STREQ(chainSuffix, virNWFilterChainSuffixTypeToString( VIR_NWFILTER_CHAINSUFFIX_ROOT))) PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); else PRINT_CHAIN(chain, chainPrefix, ifname, chainSuffix); #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) \ return -1; \ virFirewallRuleAddArg(fw, fwrule, CLI); \ if (ENTRY_WANT_NEG_SIGN(&rule->p.STRUCT.ITEM)) \ virFirewallRuleAddArg(fw, fwrule, "!"); \ virFirewallRuleAddArg(fw, fwrule, 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) \ return -1; \ virFirewallRuleAddArg(fw, fwrule, CLI); \ if (ENTRY_WANT_NEG_SIGN(&rule->p.STRUCT.ITEM)) \ virFirewallRuleAddArg(fw, fwrule, "!"); \ if (HAS_ENTRY_ITEM(&rule->p.STRUCT.ITEM_HI)) { \ if (printDataType(vars, \ fieldalt, sizeof(fieldalt), \ &rule->p.STRUCT.ITEM_HI) < 0) \ return -1; \ virFirewallRuleAddArgFormat(fw, fwrule, \ "%s%s%s", field, SEP, fieldalt); \ } else { \ virFirewallRuleAddArg(fw, fwrule, 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, "/") switch ((int)rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_MAC: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.ethHdrFilter.ethHdr, reverse) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.ethHdrFilter.dataProtocolID)) { if (printDataTypeAsHex(vars, number, sizeof(number), &rule->p.ethHdrFilter.dataProtocolID) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "-p"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ethHdrFilter.dataProtocolID)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } break; case VIR_NWFILTER_RULE_PROTOCOL_VLAN: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.vlanHdrFilter.ethHdr, reverse) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-p", "0x8100", NULL); 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; } fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.stpHdrFilter.ethHdr, reverse) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-d", NWFILTER_MAC_BGA, NULL); 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: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.arpHdrFilter.ethHdr, reverse) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "-p"); virFirewallRuleAddArgFormat(fw, fwrule, "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) return -1; virFirewallRuleAddArg(fw, fwrule, "--arp-htype"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataHWType)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataOpcode)) { if (printDataType(vars, number, sizeof(number), &rule->p.arpHdrFilter.dataOpcode) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "--arp-opcode"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataOpcode)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataProtocolType)) { if (printDataTypeAsHex(vars, number, sizeof(number), &rule->p.arpHdrFilter.dataProtocolType) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "--arp-ptype"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataProtocolType)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &rule->p.arpHdrFilter.dataARPSrcIPAddr) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcIPMask)) { if (printDataType(vars, ipmask, sizeof(ipmask), &rule->p.arpHdrFilter.dataARPSrcIPMask) < 0) return -1; hasMask = true; } virFirewallRuleAddArg(fw, fwrule, reverse ? "--arp-ip-dst" : "--arp-ip-src"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataARPSrcIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, hasMask ? ipmask : "32"); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPDstIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &rule->p.arpHdrFilter.dataARPDstIPAddr) < 0) return -1; if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPDstIPMask)) { if (printDataType(vars, ipmask, sizeof(ipmask), &rule->p.arpHdrFilter.dataARPDstIPMask) < 0) return -1; hasMask = true; } virFirewallRuleAddArg(fw, fwrule, reverse ? "--arp-ip-src" : "--arp-ip-dst"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataARPDstIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, hasMask ? ipmask : "32"); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPSrcMACAddr)) { if (printDataType(vars, macaddr, sizeof(macaddr), &rule->p.arpHdrFilter.dataARPSrcMACAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--arp-mac-dst" : "--arp-mac-src"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataARPSrcMACAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, macaddr); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataARPDstMACAddr)) { if (printDataType(vars, macaddr, sizeof(macaddr), &rule->p.arpHdrFilter.dataARPDstMACAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--arp-mac-src" : "--arp-mac-dst"); if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataARPDstMACAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, macaddr); } if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataGratuitousARP) && rule->p.arpHdrFilter.dataGratuitousARP.u.boolean) { if (ENTRY_WANT_NEG_SIGN(&rule->p.arpHdrFilter.dataGratuitousARP)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, "--arp-gratuitous"); } break; case VIR_NWFILTER_RULE_PROTOCOL_IP: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.ipHdrFilter.ethHdr, reverse) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-p", "ipv4", NULL); if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip-destination" : "--ip-source"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataSrcIPMask)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipHdrFilter.ipHdr.dataSrcIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDstIPAddr)) { if (printDataType(vars, ipaddr, sizeof(ipaddr), &rule->p.ipHdrFilter.ipHdr.dataDstIPAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip-source" : "--ip-destination"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataDstIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDstIPMask)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipHdrFilter.ipHdr.dataDstIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipaddr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipaddr); } } if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataProtocolID)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipHdrFilter.ipHdr.dataProtocolID) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "--ip-protocol"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataProtocolID)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataSrcPortStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipHdrFilter.portData.dataSrcPortStart) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip-destination-port" : "--ip-source-port"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.portData.dataSrcPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataSrcPortEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipHdrFilter.portData.dataSrcPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", number, numberalt); } else { virFirewallRuleAddArg(fw, fwrule, number); } } if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataDstPortStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipHdrFilter.portData.dataDstPortStart) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip-source-port" : "--ip-destination-port"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.portData.dataDstPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.portData.dataDstPortEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipHdrFilter.portData.dataDstPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", number, numberalt); } else { virFirewallRuleAddArg(fw, fwrule, number); } } if (HAS_ENTRY_ITEM(&rule->p.ipHdrFilter.ipHdr.dataDSCP)) { if (printDataTypeAsHex(vars, number, sizeof(number), &rule->p.ipHdrFilter.ipHdr.dataDSCP) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "--ip-tos"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipHdrFilter.ipHdr.dataDSCP)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } break; case VIR_NWFILTER_RULE_PROTOCOL_IPV6: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); if (ebtablesHandleEthHdr(fw, fwrule, vars, &rule->p.ipv6HdrFilter.ethHdr, reverse) < 0) return -1; virFirewallRuleAddArgList(fw, fwrule, "-p", "ipv6", NULL); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr)) { if (printDataType(vars, ipv6addr, sizeof(ipv6addr), &rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip6-destination" : "--ip6-source"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipv6addr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipv6addr); } } if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr)) { if (printDataType(vars, ipv6addr, sizeof(ipv6addr), &rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip6-source" : "--ip6-destination"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s/%s", ipv6addr, number); } else { virFirewallRuleAddArg(fw, fwrule, ipv6addr); } } if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.ipHdr.dataProtocolID) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, "--ip6-protocol"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID)) virFirewallRuleAddArg(fw, fwrule, "!"); virFirewallRuleAddArg(fw, fwrule, number); } if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.portData.dataSrcPortStart) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip6-destination-port" : "--ip6-source-port"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipv6HdrFilter.portData.dataSrcPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", number, numberalt); } else { virFirewallRuleAddArg(fw, fwrule, number); } } if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.portData.dataDstPortStart) < 0) return -1; virFirewallRuleAddArg(fw, fwrule, reverse ? "--ip6-source-port" : "--ip6-destination-port"); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataDstPortStart)) virFirewallRuleAddArg(fw, fwrule, "!"); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipv6HdrFilter.portData.dataDstPortEnd) < 0) return -1; virFirewallRuleAddArgFormat(fw, fwrule, "%s:%s", number, numberalt); } else { virFirewallRuleAddArg(fw, fwrule, number); } } if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPTypeStart) || HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPTypeEnd) || HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPCodeStart) || HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPCodeEnd)) { bool lo = false; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; g_autofree char *r = NULL; virFirewallRuleAddArg(fw, fwrule, "--ip6-icmp-type"); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPTypeStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.dataICMPTypeStart) < 0) return -1; lo = true; } else { ignore_value(virStrcpyStatic(number, "0")); } virBufferStrcat(&buf, number, ":", NULL); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPTypeEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipv6HdrFilter.dataICMPTypeEnd) < 0) return -1; } else { if (lo) ignore_value(virStrcpyStatic(numberalt, number)); else ignore_value(virStrcpyStatic(numberalt, "255")); } virBufferStrcat(&buf, numberalt, "/", NULL); lo = false; if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPCodeStart)) { if (printDataType(vars, number, sizeof(number), &rule->p.ipv6HdrFilter.dataICMPCodeStart) < 0) return -1; lo = true; } else { ignore_value(virStrcpyStatic(number, "0")); } virBufferStrcat(&buf, number, ":", NULL); if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.dataICMPCodeEnd)) { if (printDataType(vars, numberalt, sizeof(numberalt), &rule->p.ipv6HdrFilter.dataICMPCodeEnd) < 0) return -1; } else { if (lo) ignore_value(virStrcpyStatic(numberalt, number)); else ignore_value(virStrcpyStatic(numberalt, "255")); } virBufferStrcat(&buf, numberalt, NULL); if (ENTRY_WANT_NEG_SIGN(&rule->p.ipv6HdrFilter.dataICMPTypeStart)) virFirewallRuleAddArg(fw, fwrule, "!"); r = virBufferContentAndReset(&buf); virFirewallRuleAddArg(fw, fwrule, r); } break; case VIR_NWFILTER_RULE_PROTOCOL_NONE: fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, NULL); break; default: virReportError(VIR_ERR_INTERNAL_ERROR, _("Unexpected rule protocol %d"), rule->prtclType); 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); } virFirewallRuleAddArgList(fw, fwrule, "-j", target, NULL); #undef INST_ITEM_RANGE #undef INST_ITEM_MASK #undef INST_ITEM_2PARMS #undef INST_ITEM return 0; } /* * ebiptablesCreateRuleInstance: * @chainPriority : The priority of the chain * @chainSuffix: The suffix to put on the end of the name of the chain * @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(virFirewall *fw, const char *chainSuffix, virNWFilterRuleDef *rule, const char *ifname, virNWFilterVarCombIter *vars) { if (virNWFilterRuleIsProtocolEthernet(rule)) { if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_OUT || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { if (ebtablesCreateRuleInstance(fw, CHAINPREFIX_HOST_IN_TEMP, chainSuffix, rule, ifname, vars, rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) < 0) return -1; } if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { if (ebtablesCreateRuleInstance(fw, CHAINPREFIX_HOST_OUT_TEMP, chainSuffix, rule, ifname, vars, false) < 0) return -1; } } else { virFirewallLayer layer; if (virNWFilterRuleIsProtocolIPv6(rule)) { layer = VIR_FIREWALL_LAYER_IPV6; } else if (virNWFilterRuleIsProtocolIPv4(rule)) { layer = VIR_FIREWALL_LAYER_IPV4; } else { virReportError(VIR_ERR_OPERATION_FAILED, "%s", _("unexpected protocol type")); return -1; } if (iptablesCreateRuleInstance(fw, layer, rule, ifname, vars) < 0) return -1; } return 0; } static void ebtablesCreateTmpRootChainFW(virFirewall *fw, int incoming, const char *ifname) { char chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_OUT_TEMP; PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-N", chain, NULL); } static void ebtablesLinkTmpRootChainFW(virFirewall *fw, int incoming, const char *ifname) { char chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = incoming ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_OUT_TEMP; PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", incoming ? EBTABLES_CHAIN_INCOMING : EBTABLES_CHAIN_OUTGOING, incoming ? "-i" : "-o", ifname, "-j", chain, NULL); } static void _ebtablesRemoveRootChainFW(virFirewall *fw, bool 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); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, true, NULL, NULL, "-t", "nat", "-F", chain, NULL); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, true, NULL, NULL, "-t", "nat", "-X", chain, NULL); } static void ebtablesRemoveRootChainFW(virFirewall *fw, bool incoming, const char *ifname) { _ebtablesRemoveRootChainFW(fw, incoming, ifname, false); } static void ebtablesRemoveTmpRootChainFW(virFirewall *fw, bool incoming, const char *ifname) { _ebtablesRemoveRootChainFW(fw, incoming, ifname, 1); } static void _ebtablesUnlinkRootChainFW(virFirewall *fw, bool 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); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, true, NULL, NULL, "-t", "nat", "-D", incoming ? EBTABLES_CHAIN_INCOMING : EBTABLES_CHAIN_OUTGOING, incoming ? "-i" : "-o", ifname, "-j", chain, NULL); } static void ebtablesUnlinkRootChainFW(virFirewall *fw, bool incoming, const char *ifname) { _ebtablesUnlinkRootChainFW(fw, incoming, ifname, false); } static void ebtablesUnlinkTmpRootChainFW(virFirewall *fw, int incoming, const char *ifname) { _ebtablesUnlinkRootChainFW(fw, incoming, ifname, 1); } static void ebtablesCreateTmpSubChainFW(virFirewall *fw, bool incoming, const char *ifname, enum l3_proto_idx protoidx, const char *filtername) { char rootchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = incoming ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_OUT_TEMP; virFirewallRule *fwrule; PRINT_ROOT_CHAIN(rootchain, chainPrefix, ifname); PRINT_CHAIN(chain, chainPrefix, ifname, (filtername) ? filtername : l3_protocols[protoidx].val); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, true, NULL, NULL, "-t", "nat", "-F", chain, NULL); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, true, NULL, NULL, "-t", "nat", "-X", chain, NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-N", chain, NULL); fwrule = virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", rootchain, NULL); switch ((int)protoidx) { case L2_PROTO_MAC_IDX: break; case L2_PROTO_STP_IDX: virFirewallRuleAddArgList(fw, fwrule, "-d", NWFILTER_MAC_BGA, NULL); break; default: virFirewallRuleAddArg(fw, fwrule, "-p"); virFirewallRuleAddArgFormat(fw, fwrule, "0x%04x", l3_protocols[protoidx].attr); break; } virFirewallRuleAddArgList(fw, fwrule, "-j", chain, NULL); } static int ebtablesRemoveSubChainsQuery(virFirewall *fw, virFirewallLayer layer, const char *const *lines, void *opaque) { size_t i, j; const char *chainprefixes = opaque; for (i = 0; lines[i] != NULL; i++) { char *tmp = strstr(lines[i], "-j "); VIR_DEBUG("Considering '%s'", lines[i]); if (!tmp) continue; tmp = tmp + 3; for (j = 0; chainprefixes[j]; j++) { if (tmp[0] == chainprefixes[j] && tmp[1] == '-') { VIR_DEBUG("Processing chain '%s'", tmp); virFirewallAddRuleFull(fw, layer, false, ebtablesRemoveSubChainsQuery, (void *)chainprefixes, "-t", "nat", "-L", tmp, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-t", "nat", "-F", tmp, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-t", "nat", "-X", tmp, NULL); } } } return 0; } static void _ebtablesRemoveSubChainsFW(virFirewall *fw, const char *ifname, const char *chainprefixes) { char rootchain[MAX_CHAINNAME_LENGTH]; size_t i; for (i = 0; chainprefixes[i] != 0; i++) { PRINT_ROOT_CHAIN(rootchain, chainprefixes[i], ifname); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, false, ebtablesRemoveSubChainsQuery, (void *)chainprefixes, "-t", "nat", "-L", rootchain, NULL); } } static void ebtablesRemoveSubChainsFW(virFirewall *fw, const char *ifname) { _ebtablesRemoveSubChainsFW(fw, ifname, chainprefixes_host); } static void ebtablesRemoveTmpSubChainsFW(virFirewall *fw, const char *ifname) { _ebtablesRemoveSubChainsFW(fw, ifname, chainprefixes_host_temp); } static void ebtablesRenameTmpSubChainFW(virFirewall *fw, 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); } virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-E", tmpchain, chain, NULL); } static void ebtablesRenameTmpRootChainFW(virFirewall *fw, bool incoming, const char *ifname) { ebtablesRenameTmpSubChainFW(fw, incoming, ifname, NULL); } static int ebtablesRenameTmpSubAndRootChainsQuery(virFirewall *fw, virFirewallLayer layer, const char *const *lines, void *opaque G_GNUC_UNUSED) { size_t i; char newchain[MAX_CHAINNAME_LENGTH]; for (i = 0; lines[i] != NULL; i++) { char *tmp = strstr(lines[i], "-j "); VIR_DEBUG("Considering '%s'", lines[i]); if (!tmp) continue; tmp = tmp + 3; if (tmp[0] != CHAINPREFIX_HOST_IN_TEMP && tmp[0] != CHAINPREFIX_HOST_OUT_TEMP) continue; if (tmp[1] != '-') continue; ignore_value(virStrcpyStatic(newchain, tmp)); if (newchain[0] == CHAINPREFIX_HOST_IN_TEMP) newchain[0] = CHAINPREFIX_HOST_IN; else newchain[0] = CHAINPREFIX_HOST_OUT; VIR_DEBUG("Renaming chain '%s' to '%s'", tmp, newchain); virFirewallAddRuleFull(fw, layer, false, ebtablesRenameTmpSubAndRootChainsQuery, NULL, "-t", "nat", "-L", tmp, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-t", "nat", "-F", newchain, NULL); virFirewallAddRuleFull(fw, layer, true, NULL, NULL, "-t", "nat", "-X", newchain, NULL); virFirewallAddRule(fw, layer, "-t", "nat", "-E", tmp, newchain, NULL); } return 0; } static void ebtablesRenameTmpSubAndRootChainsFW(virFirewall *fw, const char *ifname) { char rootchain[MAX_CHAINNAME_LENGTH]; size_t i; char chains[3] = { CHAINPREFIX_HOST_IN_TEMP, CHAINPREFIX_HOST_OUT_TEMP, 0 }; for (i = 0; chains[i] != 0; i++) { PRINT_ROOT_CHAIN(rootchain, chains[i], ifname); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_ETHERNET, false, ebtablesRenameTmpSubAndRootChainsQuery, NULL, "-t", "nat", "-L", rootchain, NULL); } ebtablesRenameTmpRootChainFW(fw, true, ifname); ebtablesRenameTmpRootChainFW(fw, false, ifname); } /** * 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 true; } /** * 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 virMacAddr *macaddr) { g_autoptr(virFirewall) fw = virFirewallNew(); char chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = CHAINPREFIX_HOST_IN_TEMP; char macaddr_str[VIR_MAC_STRING_BUFLEN]; virMacAddrFormat(macaddr, macaddr_str); if (ebiptablesAllTeardown(ifname) < 0) return -1; virFirewallStartTransaction(fw, 0); ebtablesCreateTmpRootChainFW(fw, true, ifname); PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, "-s", "!", macaddr_str, "-j", "DROP", NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, "-p", "IPv4", "-j", "ACCEPT", NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, "-p", "ARP", "-j", "ACCEPT", NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain, "-j", "DROP", NULL); ebtablesLinkTmpRootChainFW(fw, true, ifname); ebtablesRenameTmpRootChainFW(fw, true, ifname); if (virFirewallApply(fw) < 0) goto error; return 0; error: ebtablesCleanAll(ifname); 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 virMacAddr *macaddr, virNWFilterVarValue *dhcpsrvrs, bool leaveTemporary) { 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; g_autoptr(virFirewall) fw = virFirewallNew(); virMacAddrFormat(macaddr, macaddr_str); if (ebiptablesAllTeardown(ifname) < 0) return -1; virFirewallStartTransaction(fw, 0); ebtablesCreateTmpRootChainFW(fw, true, ifname); ebtablesCreateTmpRootChainFW(fw, false, ifname); PRINT_ROOT_CHAIN(chain_in, CHAINPREFIX_HOST_IN_TEMP, ifname); PRINT_ROOT_CHAIN(chain_out, CHAINPREFIX_HOST_OUT_TEMP, ifname); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_in, "-s", macaddr_str, "-p", "ipv4", "--ip-protocol", "udp", "--ip-sport", "68", "--ip-dport", "67", "-j", "ACCEPT", NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_in, "-j", "DROP", NULL); num_dhcpsrvrs = (dhcpsrvrs != NULL) ? virNWFilterVarValueGetCardinality(dhcpsrvrs) : 0; while (true) { const char *dhcpserver = NULL; int ctr; if (idx < num_dhcpsrvrs) dhcpserver = virNWFilterVarValueGetNthValue(dhcpsrvrs, idx); /* * create two rules allowing response to MAC address of VM * or to broadcast MAC address */ for (ctr = 0; ctr < 2; ctr++) { if (dhcpserver) virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_out, "-d", (ctr == 0) ? macaddr_str : "ff:ff:ff:ff:ff:ff", "-p", "ipv4", "--ip-protocol", "udp", "--ip-src", dhcpserver, "--ip-sport", "67", "--ip-dport", "68", "-j", "ACCEPT", NULL); else virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_out, "-d", (ctr == 0) ? macaddr_str : "ff:ff:ff:ff:ff:ff", "-p", "ipv4", "--ip-protocol", "udp", "--ip-sport", "67", "--ip-dport", "68", "-j", "ACCEPT", NULL); } idx++; if (idx >= num_dhcpsrvrs) break; } virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_out, "-j", "DROP", NULL); ebtablesLinkTmpRootChainFW(fw, true, ifname); ebtablesLinkTmpRootChainFW(fw, false, ifname); if (!leaveTemporary) { ebtablesRenameTmpRootChainFW(fw, true, ifname); ebtablesRenameTmpRootChainFW(fw, false, ifname); } if (virFirewallApply(fw) < 0) goto error; return 0; error: ebtablesCleanAll(ifname); 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) { char chain_in [MAX_CHAINNAME_LENGTH], chain_out[MAX_CHAINNAME_LENGTH]; g_autoptr(virFirewall) fw = virFirewallNew(); if (ebiptablesAllTeardown(ifname) < 0) return -1; virFirewallStartTransaction(fw, 0); ebtablesCreateTmpRootChainFW(fw, true, ifname); ebtablesCreateTmpRootChainFW(fw, false, ifname); PRINT_ROOT_CHAIN(chain_in, CHAINPREFIX_HOST_IN_TEMP, ifname); PRINT_ROOT_CHAIN(chain_out, CHAINPREFIX_HOST_OUT_TEMP, ifname); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_in, "-j", "DROP", NULL); virFirewallAddRule(fw, VIR_FIREWALL_LAYER_ETHERNET, "-t", "nat", "-A", chain_out, "-j", "DROP", NULL); ebtablesLinkTmpRootChainFW(fw, true, ifname); ebtablesLinkTmpRootChainFW(fw, false, ifname); ebtablesRenameTmpRootChainFW(fw, true, ifname); ebtablesRenameTmpRootChainFW(fw, false, ifname); if (virFirewallApply(fw) < 0) goto error; return 0; error: ebtablesCleanAll(ifname); return -1; } static int ebtablesRemoveBasicRules(const char *ifname) { return ebtablesCleanAll(ifname); } static int ebtablesCleanAll(const char *ifname) { g_autoptr(virFirewall) fw = virFirewallNew(); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); ebtablesUnlinkRootChainFW(fw, true, ifname); ebtablesUnlinkRootChainFW(fw, false, ifname); ebtablesRemoveSubChainsFW(fw, ifname); ebtablesRemoveRootChainFW(fw, true, ifname); ebtablesRemoveRootChainFW(fw, false, ifname); ebtablesUnlinkTmpRootChainFW(fw, true, ifname); ebtablesUnlinkTmpRootChainFW(fw, false, ifname); ebtablesRemoveTmpSubChainsFW(fw, ifname); ebtablesRemoveTmpRootChainFW(fw, true, ifname); ebtablesRemoveTmpRootChainFW(fw, false, ifname); return virFirewallApply(fw); } static int virNWFilterRuleInstSort(const void *a, const void *b) { const virNWFilterRuleInst *insta = a; const virNWFilterRuleInst *instb = b; const char *root = virNWFilterChainSuffixTypeToString( VIR_NWFILTER_CHAINSUFFIX_ROOT); bool root_a = STREQ(insta->chainSuffix, root); bool root_b = STREQ(instb->chainSuffix, 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) return -1; /* a before b */ } else if (root_b) { return 1; /* b before a */ } /* priorities are limited to range [-1000, 1000] */ return insta->priority - instb->priority; } static int virNWFilterRuleInstSortPtr(const void *a, const void *b) { virNWFilterRuleInst * const *insta = a; virNWFilterRuleInst * const *instb = b; return virNWFilterRuleInstSort(*insta, *instb); } static int ebiptablesFilterOrderSort(const void *va, const void *vb) { const virHashKeyValuePair *a = va; const virHashKeyValuePair *b = vb; /* 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]; g_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 iptablesRuleInstCommand(virFirewall *fw, const char *ifname, virNWFilterRuleInst *rule) { virNWFilterVarCombIter *vciter; virNWFilterVarCombIter *tmp; int ret = -1; /* 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. */ tmp = vciter = virNWFilterVarCombIterCreate(rule->vars, rule->def->varAccess, rule->def->nVarAccess); if (!vciter) return -1; do { if (ebiptablesCreateRuleInstance(fw, rule->chainSuffix, rule->def, ifname, tmp) < 0) goto cleanup; tmp = virNWFilterVarCombIterNext(tmp); } while (tmp != NULL); ret = 0; cleanup: virNWFilterVarCombIterFree(vciter); return ret; } static int ebtablesRuleInstCommand(virFirewall *fw, const char *ifname, virNWFilterRuleInst *rule) { virNWFilterVarCombIter *vciter; virNWFilterVarCombIter *tmp; int ret = -1; /* 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. */ tmp = vciter = virNWFilterVarCombIterCreate(rule->vars, rule->def->varAccess, rule->def->nVarAccess); if (!vciter) return -1; do { if (ebiptablesCreateRuleInstance(fw, rule->chainSuffix, rule->def, ifname, tmp) < 0) goto cleanup; tmp = virNWFilterVarCombIterNext(tmp); } while (tmp != NULL); ret = 0; cleanup: virNWFilterVarCombIterFree(vciter); return ret; } typedef struct _ebtablesSubChainInst ebtablesSubChainInst; struct _ebtablesSubChainInst { virNWFilterChainPriority priority; bool incoming; enum l3_proto_idx protoidx; const char *filtername; }; static int ebtablesSubChainInstSort(const void *a, const void *b) { const ebtablesSubChainInst **insta = (const ebtablesSubChainInst **)a; const ebtablesSubChainInst **instb = (const ebtablesSubChainInst **)b; /* priorities are limited to range [-1000, 1000] */ return (*insta)->priority - (*instb)->priority; } static int ebtablesGetSubChainInsts(GHashTable *chains, bool incoming, ebtablesSubChainInst ***insts, size_t *ninsts) { g_autofree virHashKeyValuePair *filter_names = NULL; size_t nfilter_names; size_t i; filter_names = virHashGetItems(chains, &nfilter_names, false); if (filter_names == NULL) return -1; qsort(filter_names, nfilter_names, sizeof(*filter_names), ebiptablesFilterOrderSort); for (i = 0; filter_names[i].key; i++) { g_autofree ebtablesSubChainInst *inst = NULL; enum l3_proto_idx idx = ebtablesGetProtoIdxByFiltername( filter_names[i].key); if ((int)idx < 0) continue; inst = g_new0(ebtablesSubChainInst, 1); inst->priority = *(const virNWFilterChainPriority *)filter_names[i].value; inst->incoming = incoming; inst->protoidx = idx; inst->filtername = filter_names[i].key; VIR_APPEND_ELEMENT(*insts, *ninsts, inst); } return 0; } static int ebiptablesApplyNewRules(const char *ifname, virNWFilterRuleInst **rules, size_t nrules) { size_t i, j; g_autoptr(virFirewall) fw = virFirewallNew(); g_autoptr(GHashTable) chains_in_set = virHashNew(NULL); g_autoptr(GHashTable) chains_out_set = virHashNew(NULL); bool haveEbtables = false; bool haveIptables = false; bool haveIp6tables = false; g_autofree ebtablesSubChainInst **subchains = NULL; size_t nsubchains = 0; int ret = -1; if (nrules) qsort(rules, nrules, sizeof(rules[0]), virNWFilterRuleInstSortPtr); /* cleanup whatever may exist */ virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); ebtablesUnlinkTmpRootChainFW(fw, true, ifname); ebtablesUnlinkTmpRootChainFW(fw, false, ifname); ebtablesRemoveTmpSubChainsFW(fw, ifname); ebtablesRemoveTmpRootChainFW(fw, true, ifname); ebtablesRemoveTmpRootChainFW(fw, false, ifname); virFirewallStartTransaction(fw, 0); /* walk the list of rules and increase the priority * of rules in case the chain priority is of higher value; * this preserves the order of the rules and ensures that * the chain will be created before the chain's rules * are created; don't adjust rules in the root chain * example: a rule of priority -510 will be adjusted to * priority -500 and the chain with priority -500 will * then be created before it. */ for (i = 0; i < nrules; i++) { if (rules[i]->chainPriority > rules[i]->priority && !strstr("root", rules[i]->chainSuffix)) { rules[i]->priority = rules[i]->chainPriority; } } for (i = 0; i < nrules; i++) { if (virNWFilterRuleIsProtocolEthernet(rules[i]->def)) { haveEbtables = true; } else { if (virNWFilterRuleIsProtocolIPv4(rules[i]->def)) haveIptables = true; else if (virNWFilterRuleIsProtocolIPv6(rules[i]->def)) haveIp6tables = true; } } /* process ebtables commands; interleave commands from filters with commands for creating and connecting ebtables chains */ if (haveEbtables) { /* scan the rules to see which chains need to be created */ for (i = 0; i < nrules; i++) { if (virNWFilterRuleIsProtocolEthernet(rules[i]->def)) { const char *name = rules[i]->chainSuffix; if (rules[i]->def->tt == VIR_NWFILTER_RULE_DIRECTION_OUT || rules[i]->def->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { if (virHashUpdateEntry(chains_in_set, name, &rules[i]->chainPriority) < 0) goto cleanup; } if (rules[i]->def->tt == VIR_NWFILTER_RULE_DIRECTION_IN || rules[i]->def->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { if (virHashUpdateEntry(chains_out_set, name, &rules[i]->chainPriority) < 0) goto cleanup; } } } /* create needed chains */ if (virHashSize(chains_in_set) > 0) { ebtablesCreateTmpRootChainFW(fw, true, ifname); if (ebtablesGetSubChainInsts(chains_in_set, true, &subchains, &nsubchains) < 0) goto cleanup; } if (virHashSize(chains_out_set) > 0) { ebtablesCreateTmpRootChainFW(fw, false, ifname); if (ebtablesGetSubChainInsts(chains_out_set, false, &subchains, &nsubchains) < 0) goto cleanup; } if (nsubchains > 0) qsort(subchains, nsubchains, sizeof(subchains[0]), ebtablesSubChainInstSort); for (i = 0, j = 0; i < nrules; i++) { if (virNWFilterRuleIsProtocolEthernet(rules[i]->def)) { while (j < nsubchains && subchains[j]->priority <= rules[i]->priority) { ebtablesCreateTmpSubChainFW(fw, subchains[j]->incoming, ifname, subchains[j]->protoidx, subchains[j]->filtername); j++; } if (ebtablesRuleInstCommand(fw, ifname, rules[i]) < 0) goto cleanup; } } while (j < nsubchains) { ebtablesCreateTmpSubChainFW(fw, subchains[j]->incoming, ifname, subchains[j]->protoidx, subchains[j]->filtername); j++; } } if (haveIptables) { iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesCreateBaseChainsFW(fw, VIR_FIREWALL_LAYER_IPV4); iptablesCreateTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesLinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesSetupVirtInPostFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); for (i = 0; i < nrules; i++) { if (virNWFilterRuleIsProtocolIPv4(rules[i]->def)) { if (iptablesRuleInstCommand(fw, ifname, rules[i]) < 0) goto cleanup; } } iptablesCheckBridgeNFCallEnabled(false); } if (haveIp6tables) { iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesCreateBaseChainsFW(fw, VIR_FIREWALL_LAYER_IPV6); iptablesCreateTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesLinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesSetupVirtInPostFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); for (i = 0; i < nrules; i++) { if (virNWFilterRuleIsProtocolIPv6(rules[i]->def)) { if (iptablesRuleInstCommand(fw, ifname, rules[i]) < 0) goto cleanup; } } iptablesCheckBridgeNFCallEnabled(true); } if (virHashSize(chains_in_set) != 0) ebtablesLinkTmpRootChainFW(fw, true, ifname); if (virHashSize(chains_out_set) != 0) ebtablesLinkTmpRootChainFW(fw, false, ifname); virFirewallStartRollback(fw, 0); ebtablesUnlinkTmpRootChainFW(fw, true, ifname); ebtablesUnlinkTmpRootChainFW(fw, false, ifname); if (haveIp6tables) { iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); } if (haveIptables) { iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); } ebtablesRemoveTmpSubChainsFW(fw, ifname); ebtablesRemoveTmpRootChainFW(fw, true, ifname); ebtablesRemoveTmpRootChainFW(fw, false, ifname); if (virFirewallApply(fw) < 0) goto cleanup; ret = 0; cleanup: for (i = 0; i < nsubchains; i++) g_free(subchains[i]); return ret; } static void ebiptablesTearNewRulesFW(virFirewall *fw, const char *ifname) { iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesUnlinkTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRemoveTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); ebtablesUnlinkTmpRootChainFW(fw, true, ifname); ebtablesUnlinkTmpRootChainFW(fw, false, ifname); ebtablesRemoveTmpSubChainsFW(fw, ifname); ebtablesRemoveTmpRootChainFW(fw, true, ifname); ebtablesRemoveTmpRootChainFW(fw, false, ifname); } static int ebiptablesTearNewRules(const char *ifname) { g_autoptr(virFirewall) fw = virFirewallNew(); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); ebiptablesTearNewRulesFW(fw, ifname); return virFirewallApply(fw); } static int ebiptablesTearOldRules(const char *ifname) { g_autoptr(virFirewall) fw = virFirewallNew(); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); iptablesUnlinkRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRemoveRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRenameTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesUnlinkRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRemoveRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRenameTmpRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); ebtablesUnlinkRootChainFW(fw, true, ifname); ebtablesUnlinkRootChainFW(fw, false, ifname); ebtablesRemoveSubChainsFW(fw, ifname); ebtablesRemoveRootChainFW(fw, true, ifname); ebtablesRemoveRootChainFW(fw, false, ifname); ebtablesRenameTmpSubAndRootChainsFW(fw, ifname); return virFirewallApply(fw); } /** * 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). * * Returns 0 on success, -1 on OOM */ static int ebiptablesAllTeardown(const char *ifname) { g_autoptr(virFirewall) fw = virFirewallNew(); virFirewallStartTransaction(fw, VIR_FIREWALL_TRANSACTION_IGNORE_ERRORS); ebiptablesTearNewRulesFW(fw, ifname); iptablesUnlinkRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesClearVirtInPostFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesRemoveRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV4, ifname); iptablesUnlinkRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesClearVirtInPostFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); iptablesRemoveRootChainsFW(fw, VIR_FIREWALL_LAYER_IPV6, ifname); ebtablesUnlinkRootChainFW(fw, true, ifname); ebtablesUnlinkRootChainFW(fw, false, ifname); ebtablesRemoveSubChainsFW(fw, ifname); ebtablesRemoveRootChainFW(fw, true, ifname); ebtablesRemoveRootChainFW(fw, false, ifname); return virFirewallApply(fw); } virNWFilterTechDriver ebiptables_driver = { .name = EBIPTABLES_DRIVER_ID, .flags = 0, .init = ebiptablesDriverInit, .shutdown = ebiptablesDriverShutdown, .applyNewRules = ebiptablesApplyNewRules, .tearNewRules = ebiptablesTearNewRules, .tearOldRules = ebiptablesTearOldRules, .allTeardown = ebiptablesAllTeardown, .canApplyBasicRules = ebiptablesCanApplyBasicRules, .applyBasicRules = ebtablesApplyBasicRules, .applyDHCPOnlyRules = ebtablesApplyDHCPOnlyRules, .applyDropAllRules = ebtablesApplyDropAllRules, .removeBasicRules = ebtablesRemoveBasicRules, }; static void ebiptablesDriverProbeCtdir(void) { struct utsname utsname; unsigned long thisversion; iptables_ctdir_corrected = CTDIR_STATUS_UNKNOWN; if (uname(&utsname) < 0) { VIR_ERROR(_("Call to utsname failed: %d"), errno); return; } /* following Linux lxr, the logic was inverted in 2.6.39 */ if (virParseVersionString(utsname.release, &thisversion, true) < 0) { VIR_ERROR(_("Could not determine kernel version from string %s"), utsname.release); return; } if (thisversion >= 2 * 1000000 + 6 * 1000 + 39) iptables_ctdir_corrected = CTDIR_STATUS_CORRECTED; else iptables_ctdir_corrected = CTDIR_STATUS_OLD; } static int ebiptablesDriverProbeStateMatchQuery(virFirewall *fw G_GNUC_UNUSED, virFirewallLayer layer G_GNUC_UNUSED, const char *const *lines, void *opaque) { unsigned long *version = opaque; char *tmp; if (!lines || !lines[0]) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("No output from iptables --version")); return -1; } /* * we expect output in the format * 'iptables v1.4.16' */ if (!(tmp = strchr(lines[0], 'v')) || virParseVersionString(tmp + 1, version, true) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot parse version string '%s'"), lines[0]); return -1; } return 0; } static int ebiptablesDriverProbeStateMatch(void) { unsigned long version; g_autoptr(virFirewall) fw = virFirewallNew(); virFirewallStartTransaction(fw, 0); virFirewallAddRuleFull(fw, VIR_FIREWALL_LAYER_IPV4, false, ebiptablesDriverProbeStateMatchQuery, &version, "--version", NULL); if (virFirewallApply(fw) < 0) return -1; /* * since version 1.4.16 '-m state --state ...' will be converted to * '-m conntrack --ctstate ...' */ if (version >= 1 * 1000000 + 4 * 1000 + 16) newMatchState = true; return 0; } static int ebiptablesDriverInit(bool privileged) { if (!privileged) return 0; ebiptablesDriverProbeCtdir(); if (ebiptablesDriverProbeStateMatch() < 0) return -1; ebiptables_driver.flags = TECHDRV_FLAG_INITIALIZED; return 0; } static void ebiptablesDriverShutdown(void) { ebiptables_driver.flags = 0; }