/* * nwfilter_conf.c: network filter XML processing * (derived from storage_conf.c) * * Copyright (C) 2006-2018 Red Hat, Inc. * Copyright (C) 2006-2008 Daniel P. Berrange * * Copyright (C) 2010-2011 IBM Corporation * Copyright (C) 2010-2011 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 #if WITH_NET_ETHERNET_H # include #endif #include #include "internal.h" #include "viruuid.h" #include "viralloc.h" #include "virerror.h" #include "nwfilter_params.h" #include "nwfilter_conf.h" #include "virfile.h" #include "virstring.h" #define VIR_FROM_THIS VIR_FROM_NWFILTER VIR_ENUM_IMPL(virNWFilterRuleAction, VIR_NWFILTER_RULE_ACTION_LAST, "drop", "accept", "reject", "return", "continue", ); VIR_ENUM_IMPL(virNWFilterJumpTarget, VIR_NWFILTER_RULE_ACTION_LAST, "DROP", "ACCEPT", "REJECT", "RETURN", "CONTINUE", ); VIR_ENUM_IMPL(virNWFilterRuleDirection, VIR_NWFILTER_RULE_DIRECTION_LAST, "in", "out", "inout", ); VIR_ENUM_IMPL(virNWFilterChainPolicy, VIR_NWFILTER_CHAIN_POLICY_LAST, "ACCEPT", "DROP", ); VIR_ENUM_IMPL(virNWFilterEbtablesTable, VIR_NWFILTER_EBTABLES_TABLE_LAST, "filter", "nat", "broute", ); VIR_ENUM_IMPL(virNWFilterChainSuffix, VIR_NWFILTER_CHAINSUFFIX_LAST, "root", "mac", "vlan", "stp", "arp", "rarp", "ipv4", "ipv6", ); VIR_ENUM_IMPL(virNWFilterRuleProtocol, VIR_NWFILTER_RULE_PROTOCOL_LAST, "none", "mac", "vlan", "stp", "arp", "rarp", "ip", "ipv6", "tcp", "icmp", "igmp", "udp", "udplite", "esp", "ah", "sctp", "all", "tcp-ipv6", "icmpv6", "udp-ipv6", "udplite-ipv6", "esp-ipv6", "ah-ipv6", "sctp-ipv6", "all-ipv6", ); /* * a map entry for a simple static int-to-string map */ struct int_map { int32_t attr; const char *val; }; #define INTMAP_ENTRY(ATT, VAL) { .attr = ATT, .val = VAL } #define INTMAP_ENTRY_LAST { .val = NULL } static const struct int_map chain_priorities[] = { INTMAP_ENTRY(NWFILTER_ROOT_FILTER_PRI, "root"), INTMAP_ENTRY(NWFILTER_MAC_FILTER_PRI, "mac"), INTMAP_ENTRY(NWFILTER_VLAN_FILTER_PRI, "vlan"), INTMAP_ENTRY(NWFILTER_IPV4_FILTER_PRI, "ipv4"), INTMAP_ENTRY(NWFILTER_IPV6_FILTER_PRI, "ipv6"), INTMAP_ENTRY(NWFILTER_ARP_FILTER_PRI, "arp"), INTMAP_ENTRY(NWFILTER_RARP_FILTER_PRI, "rarp"), INTMAP_ENTRY_LAST, }; /* * attribute names for the rules XML */ static const char srcmacaddr_str[] = "srcmacaddr"; static const char srcmacmask_str[] = "srcmacmask"; static const char dstmacaddr_str[] = "dstmacaddr"; static const char dstmacmask_str[] = "dstmacmask"; static const char arpsrcmacaddr_str[] = "arpsrcmacaddr"; static const char arpdstmacaddr_str[] = "arpdstmacaddr"; static const char arpsrcipaddr_str[] = "arpsrcipaddr"; static const char arpsrcipmask_str[] = "arpsrcipmask"; static const char arpdstipaddr_str[] = "arpdstipaddr"; static const char arpdstipmask_str[] = "arpdstipmask"; static const char srcipaddr_str[] = "srcipaddr"; static const char srcipmask_str[] = "srcipmask"; static const char dstipaddr_str[] = "dstipaddr"; static const char dstipmask_str[] = "dstipmask"; static const char srcipfrom_str[] = "srcipfrom"; static const char srcipto_str[] = "srcipto"; static const char dstipfrom_str[] = "dstipfrom"; static const char dstipto_str[] = "dstipto"; static const char srcportstart_str[] = "srcportstart"; static const char srcportend_str[] = "srcportend"; static const char dstportstart_str[] = "dstportstart"; static const char dstportend_str[] = "dstportend"; static const char dscp_str[] = "dscp"; static const char state_str[] = "state"; static const char ipset_str[] = "ipset"; static const char ipsetflags_str[] = "ipsetflags"; #define SRCMACADDR srcmacaddr_str #define SRCMACMASK srcmacmask_str #define DSTMACADDR dstmacaddr_str #define DSTMACMASK dstmacmask_str #define ARPSRCMACADDR arpsrcmacaddr_str #define ARPDSTMACADDR arpdstmacaddr_str #define ARPSRCIPADDR arpsrcipaddr_str #define ARPSRCIPMASK arpsrcipmask_str #define ARPDSTIPADDR arpdstipaddr_str #define ARPDSTIPMASK arpdstipmask_str #define SRCIPADDR srcipaddr_str #define SRCIPMASK srcipmask_str #define DSTIPADDR dstipaddr_str #define DSTIPMASK dstipmask_str #define SRCIPFROM srcipfrom_str #define SRCIPTO srcipto_str #define DSTIPFROM dstipfrom_str #define DSTIPTO dstipto_str #define SRCPORTSTART srcportstart_str #define SRCPORTEND srcportend_str #define DSTPORTSTART dstportstart_str #define DSTPORTEND dstportend_str #define DSCP dscp_str #define STATE state_str #define IPSET ipset_str #define IPSETFLAGS ipsetflags_str /** * intMapGetByInt: * @intmap: Pointer to int-to-string map * @attr: The attribute to look up * @res: Pointer to string pointer for result * * Returns 0 if value was found with result returned, -1 otherwise. * * lookup a map entry given the integer. */ static int intMapGetByInt(const struct int_map *intmap, int32_t attr, const char **res) { size_t i = 0; bool found = false; while (intmap[i].val && !found) { if (intmap[i].attr == attr) { *res = intmap[i].val; found = true; } i++; } return (found) ? 0 : -1; } /** * intMapGetByString: * @intmap: Pointer to int-to-string map * @str: Pointer to string for which to find the entry * @casecmp : Whether to ignore case when doing string matching * @result: Pointer to int for result * * Returns 0 if entry was found, -1 otherwise. * * Do a lookup in the map trying to find an integer key using the string * value. Returns 0 if entry was found with result returned, -1 otherwise. */ static int intMapGetByString(const struct int_map *intmap, const char *str, int casecmp, int32_t *result) { size_t i = 0; bool found = false; while (intmap[i].val && !found) { if ((casecmp && STRCASEEQ(intmap[i].val, str)) || STREQ(intmap[i].val, str)) { *result = intmap[i].attr; found = true; } i++; } return (found) ? 0 : -1; } void virNWFilterRuleDefFree(virNWFilterRuleDef *def) { size_t i; if (!def) return; for (i = 0; i < def->nVarAccess; i++) virNWFilterVarAccessFree(def->varAccess[i]); for (i = 0; i < def->nstrings; i++) g_free(def->strings[i]); g_free(def->varAccess); g_free(def->strings); g_free(def); } static void virNWFilterIncludeDefFree(virNWFilterIncludeDef *inc) { if (!inc) return; g_clear_pointer(&inc->params, g_hash_table_unref); g_free(inc->filterref); g_free(inc); } static void virNWFilterEntryFree(virNWFilterEntry *entry) { if (!entry) return; virNWFilterRuleDefFree(entry->rule); virNWFilterIncludeDefFree(entry->include); g_free(entry); } void virNWFilterDefFree(virNWFilterDef *def) { size_t i; if (!def) return; g_free(def->name); for (i = 0; i < def->nentries; i++) virNWFilterEntryFree(def->filterEntries[i]); g_free(def->filterEntries); g_free(def->chainsuffix); g_free(def); } static int virNWFilterRuleDefAddVar(virNWFilterRuleDef *nwf, nwItemDesc *item, const char *var) { size_t i = 0; virNWFilterVarAccess *varAccess; varAccess = virNWFilterVarAccessParse(var); if (varAccess == NULL) return -1; if (nwf->varAccess) { for (i = 0; i < nwf->nVarAccess; i++) if (virNWFilterVarAccessEqual(nwf->varAccess[i], varAccess)) { virNWFilterVarAccessFree(varAccess); item->varAccess = nwf->varAccess[i]; return 0; } } VIR_EXPAND_N(nwf->varAccess, nwf->nVarAccess, 1); nwf->varAccess[nwf->nVarAccess - 1] = varAccess; item->varAccess = varAccess; return 0; } static char * virNWFilterRuleDefAddString(virNWFilterRuleDef *nwf, const char *string, size_t maxstrlen) { char *tmp = g_strndup(string, maxstrlen); VIR_APPEND_ELEMENT_COPY(nwf->strings, nwf->nstrings, tmp); return tmp; } union data { void *v; char *c; unsigned char *uc; unsigned int ui; }; typedef bool (*valueValidator)(enum attrDatatype datatype, union data *valptr, virNWFilterRuleDef *nwf, nwItemDesc *item); typedef bool (*valueFormatter)(virBuffer *buf, virNWFilterRuleDef *nwf, nwItemDesc *item); typedef struct _virXMLAttr2Struct virXMLAttr2Struct; struct _virXMLAttr2Struct { const char *name; /* attribute name */ enum attrDatatype datatype; int dataIdx; /* offset of the hasXYZ boolean */ valueValidator validator; /* beyond-standard checkers */ valueFormatter formatter; /* beyond-standard formatter */ size_t maxstrlen; }; static const struct int_map macProtoMap[] = { INTMAP_ENTRY(ETHERTYPE_ARP, "arp"), INTMAP_ENTRY(ETHERTYPE_REVARP, "rarp"), INTMAP_ENTRY(ETHERTYPE_IP, "ipv4"), INTMAP_ENTRY(ETHERTYPE_IPV6, "ipv6"), INTMAP_ENTRY(ETHERTYPE_VLAN, "vlan"), INTMAP_ENTRY_LAST }; static bool checkMacProtocolID(enum attrDatatype datatype, union data *value, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item G_GNUC_UNUSED) { int32_t res = -1; if (datatype == DATATYPE_STRING) { if (intMapGetByString(macProtoMap, value->c, 1, &res) < 0) res = -1; datatype = DATATYPE_UINT16; } else if (datatype == DATATYPE_UINT16 || datatype == DATATYPE_UINT16_HEX) { res = value->ui; if (res < 0x600) res = -1; } if (res != -1) { nwf->p.ethHdrFilter.dataProtocolID.u.u16 = res; nwf->p.ethHdrFilter.dataProtocolID.datatype = datatype; return true; } return false; } static bool macProtocolIDFormatter(virBuffer *buf, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { const char *str = NULL; bool asHex = true; if (intMapGetByInt(macProtoMap, nwf->p.ethHdrFilter.dataProtocolID.u.u16, &str) == 0) { virBufferAdd(buf, str, -1); } else { if (nwf->p.ethHdrFilter.dataProtocolID.datatype == DATATYPE_UINT16) asHex = false; virBufferAsprintf(buf, asHex ? "0x%x" : "%d", nwf->p.ethHdrFilter.dataProtocolID.u.u16); } return true; } static bool checkVlanVlanID(enum attrDatatype datatype, union data *value, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { int32_t res; res = value->ui; if (res < 0 || res > 4095) res = -1; if (res != -1) { nwf->p.vlanHdrFilter.dataVlanID.u.u16 = res; nwf->p.vlanHdrFilter.dataVlanID.datatype = datatype; return true; } return false; } static bool checkVlanProtocolID(enum attrDatatype datatype, union data *value, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { int32_t res = -1; if (datatype == DATATYPE_STRING) { if (intMapGetByString(macProtoMap, value->c, 1, &res) < 0) res = -1; datatype = DATATYPE_UINT16; } else if (datatype == DATATYPE_UINT16 || datatype == DATATYPE_UINT16_HEX) { res = value->ui; if (res < 0x3c) res = -1; } if (res != -1) { nwf->p.vlanHdrFilter.dataVlanEncap.u.u16 = res; nwf->p.vlanHdrFilter.dataVlanEncap.datatype = datatype; return true; } return false; } static bool vlanProtocolIDFormatter(virBuffer *buf, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { const char *str = NULL; bool asHex = true; if (intMapGetByInt(macProtoMap, nwf->p.vlanHdrFilter.dataVlanEncap.u.u16, &str) == 0) { virBufferAdd(buf, str, -1); } else { if (nwf->p.vlanHdrFilter.dataVlanEncap.datatype == DATATYPE_UINT16) asHex = false; virBufferAsprintf(buf, asHex ? "0x%x" : "%d", nwf->p.vlanHdrFilter.dataVlanEncap.u.u16); } return true; } /* generic function to check for a valid (ipv4,ipv6, mac) mask * A mask is valid of there is a sequence of 1's followed by a sequence * of 0s or only 1s or only 0s */ static bool checkValidMask(unsigned char *data, int len) { uint32_t idx = 0; uint8_t mask = 0x80; bool checkones = true; while ((idx >> 3) < len) { if (checkones) { if (!(data[idx>>3] & mask)) checkones = false; } else { if ((data[idx>>3] & mask)) return false; } idx++; mask >>= 1; if (!mask) mask = 0x80; } return true; } static bool checkMACMask(enum attrDatatype datatype G_GNUC_UNUSED, union data *macMask, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item G_GNUC_UNUSED) { return checkValidMask(macMask->uc, 6); } /* * supported arp opcode -- see 'ebtables -h arp' for the naming */ static const struct int_map arpOpcodeMap[] = { INTMAP_ENTRY(1, "Request"), INTMAP_ENTRY(2, "Reply"), INTMAP_ENTRY(3, "Request_Reverse"), INTMAP_ENTRY(4, "Reply_Reverse"), INTMAP_ENTRY(5, "DRARP_Request"), INTMAP_ENTRY(6, "DRARP_Reply"), INTMAP_ENTRY(7, "DRARP_Error"), INTMAP_ENTRY(8, "InARP_Request"), INTMAP_ENTRY(9, "ARP_NAK"), INTMAP_ENTRY_LAST }; static bool arpOpcodeValidator(enum attrDatatype datatype, union data *value, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { int32_t res = -1; if (datatype == DATATYPE_STRING) { if (intMapGetByString(arpOpcodeMap, value->c, 1, &res) < 0) res = -1; datatype = DATATYPE_UINT16; } else if (datatype == DATATYPE_UINT16 || datatype == DATATYPE_UINT16_HEX) { res = (uint32_t)value->ui; } if (res != -1) { nwf->p.arpHdrFilter.dataOpcode.u.u16 = res; nwf->p.arpHdrFilter.dataOpcode.datatype = datatype; return true; } return false; } static bool arpOpcodeFormatter(virBuffer *buf, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { const char *str = NULL; if (intMapGetByInt(arpOpcodeMap, nwf->p.arpHdrFilter.dataOpcode.u.u16, &str) == 0) { virBufferAdd(buf, str, -1); } else { virBufferAsprintf(buf, "%d", nwf->p.arpHdrFilter.dataOpcode.u.u16); } return true; } static const struct int_map ipProtoMap[] = { INTMAP_ENTRY(IPPROTO_TCP, "tcp"), INTMAP_ENTRY(IPPROTO_UDP, "udp"), #ifdef IPPROTO_UDPLITE INTMAP_ENTRY(IPPROTO_UDPLITE, "udplite"), #endif INTMAP_ENTRY(IPPROTO_ESP, "esp"), INTMAP_ENTRY(IPPROTO_AH, "ah"), INTMAP_ENTRY(IPPROTO_ICMP, "icmp"), INTMAP_ENTRY(IPPROTO_IGMP, "igmp"), #ifdef IPPROTO_SCTP INTMAP_ENTRY(IPPROTO_SCTP, "sctp"), #endif INTMAP_ENTRY(IPPROTO_ICMPV6, "icmpv6"), INTMAP_ENTRY_LAST }; static bool checkIPProtocolID(enum attrDatatype datatype, union data *value, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { int32_t res = -1; if (datatype == DATATYPE_STRING) { if (intMapGetByString(ipProtoMap, value->c, 1, &res) < 0) res = -1; datatype = DATATYPE_UINT8_HEX; } else if (datatype == DATATYPE_UINT8 || datatype == DATATYPE_UINT8_HEX) { res = (uint32_t)value->ui; } if (res != -1) { nwf->p.ipHdrFilter.ipHdr.dataProtocolID.u.u8 = res; nwf->p.ipHdrFilter.ipHdr.dataProtocolID.datatype = datatype; return true; } return false; } static bool formatIPProtocolID(virBuffer *buf, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { const char *str = NULL; bool asHex = true; if (intMapGetByInt(ipProtoMap, nwf->p.ipHdrFilter.ipHdr.dataProtocolID.u.u8, &str) == 0) { virBufferAdd(buf, str, -1); } else { if (nwf->p.ipHdrFilter.ipHdr.dataProtocolID.datatype == DATATYPE_UINT8) asHex = false; virBufferAsprintf(buf, asHex ? "0x%x" : "%d", nwf->p.ipHdrFilter.ipHdr.dataProtocolID.u.u8); } return true; } static bool dscpValidator(enum attrDatatype datatype, union data *val, virNWFilterRuleDef *nwf, nwItemDesc *item G_GNUC_UNUSED) { uint8_t dscp = val->ui; if (dscp > 63) return false; nwf->p.ipHdrFilter.ipHdr.dataDSCP.datatype = datatype; return true; } static const struct int_map stateMatchMap[] = { INTMAP_ENTRY(RULE_FLAG_STATE_NEW, "NEW"), INTMAP_ENTRY(RULE_FLAG_STATE_ESTABLISHED, "ESTABLISHED"), INTMAP_ENTRY(RULE_FLAG_STATE_RELATED, "RELATED"), INTMAP_ENTRY(RULE_FLAG_STATE_INVALID, "INVALID"), INTMAP_ENTRY(RULE_FLAG_STATE_NONE, "NONE"), INTMAP_ENTRY_LAST, }; static int parseStringItems(const struct int_map *int_map, const char *input, int32_t *flags, char sep) { int rc = 0; size_t i, j; bool found; i = 0; while (input[i]) { found = false; while (g_ascii_isspace(input[i]) || input[i] == sep) i++; if (!input[i]) break; for (j = 0; int_map[j].val; j++) { if (STRCASEEQLEN(&input[i], int_map[j].val, strlen(int_map[j].val))) { *flags |= int_map[j].attr; i += strlen(int_map[j].val); found = true; break; } } if (!found) { rc = -1; break; } } return rc; } static int printStringItems(virBuffer *buf, const struct int_map *int_map, int32_t flags, const char *sep) { size_t i; unsigned int c = 0; int32_t mask = 0x1; while (mask) { if ((mask & flags)) { for (i = 0; int_map[i].val; i++) { if (mask == int_map[i].attr) { if (c >= 1) virBufferAdd(buf, sep, -1); virBufferAdd(buf, int_map[i].val, -1); c++; } } flags ^= mask; } if (!flags) break; mask <<= 1; } return 0; } static int parseStateMatch(const char *statematch, int32_t *flags) { int rc = parseStringItems(stateMatchMap, statematch, flags, ','); if ((*flags & RULE_FLAG_STATE_NONE)) *flags = RULE_FLAG_STATE_NONE; return rc; } void virNWFilterPrintStateMatchFlags(virBuffer *buf, const char *prefix, int32_t flags, bool disp_none) { if (!disp_none && (flags & RULE_FLAG_STATE_NONE)) return; virBufferAdd(buf, prefix, -1); printStringItems(buf, stateMatchMap, flags, ","); } static bool stateValidator(enum attrDatatype datatype G_GNUC_UNUSED, union data *val, virNWFilterRuleDef *nwf, nwItemDesc *item) { char *input = val->c; int32_t flags = 0; if (parseStateMatch(input, &flags) < 0) return false; item->u.u16 = flags; nwf->flags |= flags; item->datatype = DATATYPE_UINT16; return true; } static bool stateFormatter(virBuffer *buf, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { virNWFilterPrintStateMatchFlags(buf, "", item->u.u16, true); return true; } static const struct int_map tcpFlags[] = { INTMAP_ENTRY(0x1, "FIN"), INTMAP_ENTRY(0x2, "SYN"), INTMAP_ENTRY(0x4, "RST"), INTMAP_ENTRY(0x8, "PSH"), INTMAP_ENTRY(0x10, "ACK"), INTMAP_ENTRY(0x20, "URG"), INTMAP_ENTRY(0x3F, "ALL"), INTMAP_ENTRY(0x0, "NONE"), INTMAP_ENTRY_LAST }; static bool tcpFlagsValidator(enum attrDatatype datatype G_GNUC_UNUSED, union data *val, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { bool rc = false; char *s_mask = val->c; char *sep = strchr(val->c, '/'); char *s_flags; int32_t mask = 0, flags = 0; if (!sep) return false; s_flags = sep + 1; *sep = '\0'; if (parseStringItems(tcpFlags, s_mask, &mask, ',') == 0 && parseStringItems(tcpFlags, s_flags, &flags, ',') == 0) { item->u.tcpFlags.mask = mask & 0x3f; item->u.tcpFlags.flags = flags & 0x3f; rc = true; } *sep = '/'; return rc; } static void printTCPFlags(virBuffer *buf, uint8_t flags) { if (flags == 0) virBufferAddLit(buf, "NONE"); else if (flags == 0x3f) virBufferAddLit(buf, "ALL"); else printStringItems(buf, tcpFlags, flags, ","); } char * virNWFilterPrintTCPFlags(uint8_t flags) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; printTCPFlags(&buf, flags); return virBufferContentAndReset(&buf); } static bool tcpFlagsFormatter(virBuffer *buf, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { printTCPFlags(buf, item->u.tcpFlags.mask); virBufferAddLit(buf, "/"); printTCPFlags(buf, item->u.tcpFlags.flags); return true; } static bool ipsetValidator(enum attrDatatype datatype G_GNUC_UNUSED, union data *val, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { const char *errmsg = NULL; if (virStrcpyStatic(item->u.ipset.setname, val->c) < 0) { errmsg = _("ipset name is too long"); goto arg_err_exit; } if (item->u.ipset.setname[strspn(item->u.ipset.setname, VALID_IPSETNAME)] != 0) { errmsg = _("ipset name contains invalid characters"); goto arg_err_exit; } return true; arg_err_exit: virReportError(VIR_ERR_INVALID_ARG, "%s", errmsg); return false; } static bool ipsetFormatter(virBuffer *buf, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { virBufferAdd(buf, item->u.ipset.setname, -1); return true; } static bool ipsetFlagsValidator(enum attrDatatype datatype G_GNUC_UNUSED, union data *val, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { const char *errmsg = NULL; size_t idx = 0; item->u.ipset.numFlags = 0; item->u.ipset.flags = 0; errmsg = _("malformed ipset flags"); while (item->u.ipset.numFlags < 6) { if (STRCASEEQLEN(&val->c[idx], "src", 3)) { item->u.ipset.flags |= (1 << item->u.ipset.numFlags); } else if (!STRCASEEQLEN(&val->c[idx], "dst", 3)) { goto arg_err_exit; } item->u.ipset.numFlags++; idx += 3; if (val->c[idx] != ',') break; idx++; } if (val->c[idx] != '\0') goto arg_err_exit; return true; arg_err_exit: virReportError(VIR_ERR_INVALID_ARG, "%s", errmsg); return false; } static bool ipsetFlagsFormatter(virBuffer *buf, virNWFilterRuleDef *nwf G_GNUC_UNUSED, nwItemDesc *item) { uint8_t ctr; for (ctr = 0; ctr < item->u.ipset.numFlags; ctr++) { if (ctr != 0) virBufferAddLit(buf, ","); if ((item->u.ipset.flags & (1 << ctr))) virBufferAddLit(buf, "src"); else virBufferAddLit(buf, "dst"); } return true; } #define COMMON_MAC_PROPS(STRUCT) \ {\ .name = SRCMACADDR,\ .datatype = DATATYPE_MACADDR,\ .dataIdx = offsetof(virNWFilterRuleDef,\ p.STRUCT.ethHdr.dataSrcMACAddr),\ },\ {\ .name = SRCMACMASK,\ .datatype = DATATYPE_MACMASK,\ .dataIdx = offsetof(virNWFilterRuleDef,\ p.STRUCT.ethHdr.dataSrcMACMask),\ },\ {\ .name = DSTMACADDR,\ .datatype = DATATYPE_MACADDR,\ .dataIdx = offsetof(virNWFilterRuleDef,\ p.STRUCT.ethHdr.dataDstMACAddr),\ },\ {\ .name = DSTMACMASK,\ .datatype = DATATYPE_MACMASK,\ .dataIdx = offsetof(virNWFilterRuleDef,\ p.STRUCT.ethHdr.dataDstMACMask),\ } #define COMMENT_PROP(STRUCT) \ {\ .name = "comment",\ .datatype = DATATYPE_STRINGCOPY,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.dataComment),\ .maxstrlen = MAX_COMMENT_LENGTH,\ } #define COMMENT_PROP_IPHDR(STRUCT) \ COMMENT_PROP(STRUCT.ipHdr) static const virXMLAttr2Struct macAttributes[] = { COMMON_MAC_PROPS(ethHdrFilter), { .name = "protocolid", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX | DATATYPE_STRING, .dataIdx = offsetof(virNWFilterRuleDef, p.ethHdrFilter.dataProtocolID), .validator = checkMacProtocolID, .formatter = macProtocolIDFormatter, }, COMMENT_PROP(ethHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct vlanAttributes[] = { COMMON_MAC_PROPS(ethHdrFilter), { .name = "vlanid", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.vlanHdrFilter.dataVlanID), .validator = checkVlanVlanID, }, { .name = "encap-protocol", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX | DATATYPE_STRING, .dataIdx = offsetof(virNWFilterRuleDef, p.vlanHdrFilter.dataVlanEncap), .validator = checkVlanProtocolID, .formatter = vlanProtocolIDFormatter, }, COMMENT_PROP(vlanHdrFilter), { .name = NULL, } }; /* STP is documented by IEEE 802.1D; for a synopsis, see * https://web.archive.org/web/20130530200728/http://www.javvin.com/protocolSTP.html */ static const virXMLAttr2Struct stpAttributes[] = { /* spanning tree uses a special destination MAC address */ { .name = SRCMACADDR, .datatype = DATATYPE_MACADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.ethHdr.dataSrcMACAddr), }, { .name = SRCMACMASK, .datatype = DATATYPE_MACMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.ethHdr.dataSrcMACMask), }, { .name = "type", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataType), }, { .name = "flags", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataFlags), }, { .name = "root-priority", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootPri), }, { .name = "root-priority-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootPriHi), }, { .name = "root-address", .datatype = DATATYPE_MACADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootAddr), }, { .name = "root-address-mask", .datatype = DATATYPE_MACMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootAddrMask), }, { .name = "root-cost", .datatype = DATATYPE_UINT32 | DATATYPE_UINT32_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootCost), }, { .name = "root-cost-hi", .datatype = DATATYPE_UINT32 | DATATYPE_UINT32_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataRootCostHi), }, { .name = "sender-priority", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataSndrPrio), }, { .name = "sender-priority-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataSndrPrioHi), }, { .name = "sender-address", .datatype = DATATYPE_MACADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataSndrAddr), }, { .name = "sender-address-mask", .datatype = DATATYPE_MACMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataSndrAddrMask), }, { .name = "port", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataPort), }, { .name = "port-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataPortHi), }, { .name = "age", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataAge), }, { .name = "age-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataAgeHi), }, { .name = "max-age", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataMaxAge), }, { .name = "max-age-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataMaxAgeHi), }, { .name = "hello-time", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataHelloTime), }, { .name = "hello-time-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataHelloTimeHi), }, { .name = "forward-delay", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataFwdDelay), }, { .name = "forward-delay-hi", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.stpHdrFilter.dataFwdDelayHi), }, COMMENT_PROP(stpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct arpAttributes[] = { COMMON_MAC_PROPS(arpHdrFilter), { .name = "hwtype", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataHWType), }, { .name = "protocoltype", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataProtocolType), }, { .name = "opcode", .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX | DATATYPE_STRING, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataOpcode), .validator = arpOpcodeValidator, .formatter = arpOpcodeFormatter, }, { .name = ARPSRCMACADDR, .datatype = DATATYPE_MACADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPSrcMACAddr), }, { .name = ARPDSTMACADDR, .datatype = DATATYPE_MACADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPDstMACAddr), }, { .name = ARPSRCIPADDR, .datatype = DATATYPE_IPADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPSrcIPAddr), }, { .name = ARPSRCIPMASK, .datatype = DATATYPE_IPMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPSrcIPMask), }, { .name = ARPDSTIPADDR, .datatype = DATATYPE_IPADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPDstIPAddr), }, { .name = ARPDSTIPMASK, .datatype = DATATYPE_IPMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataARPDstIPMask), }, { .name = "gratuitous", .datatype = DATATYPE_BOOLEAN, .dataIdx = offsetof(virNWFilterRuleDef, p.arpHdrFilter.dataGratuitousARP), }, COMMENT_PROP(arpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct ipAttributes[] = { COMMON_MAC_PROPS(ipHdrFilter), { .name = SRCIPADDR, .datatype = DATATYPE_IPADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataSrcIPAddr), }, { .name = SRCIPMASK, .datatype = DATATYPE_IPMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataSrcIPMask), }, { .name = DSTIPADDR, .datatype = DATATYPE_IPADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataDstIPAddr), }, { .name = DSTIPMASK, .datatype = DATATYPE_IPMASK, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataDstIPMask), }, { .name = "protocol", .datatype = DATATYPE_STRING | DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataProtocolID), .validator = checkIPProtocolID, .formatter = formatIPProtocolID, }, { .name = SRCPORTSTART, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.portData.dataSrcPortStart), }, { .name = SRCPORTEND, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.portData.dataSrcPortEnd), }, { .name = DSTPORTSTART, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.portData.dataDstPortStart), }, { .name = DSTPORTEND, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.portData.dataDstPortEnd), }, { .name = DSCP, .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipHdrFilter.ipHdr.dataDSCP), .validator = dscpValidator, }, COMMENT_PROP_IPHDR(ipHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct ipv6Attributes[] = { COMMON_MAC_PROPS(ipv6HdrFilter), { .name = SRCIPADDR, .datatype = DATATYPE_IPV6ADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataSrcIPAddr), }, { .name = SRCIPMASK, .datatype = DATATYPE_IPV6MASK, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataSrcIPMask), }, { .name = DSTIPADDR, .datatype = DATATYPE_IPV6ADDR, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataDstIPAddr), }, { .name = DSTIPMASK, .datatype = DATATYPE_IPV6MASK, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataDstIPMask), }, { .name = "protocol", .datatype = DATATYPE_STRING | DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataProtocolID), .validator = checkIPProtocolID, .formatter = formatIPProtocolID, }, { .name = SRCPORTSTART, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataSrcPortStart), }, { .name = SRCPORTEND, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataSrcPortEnd), }, { .name = DSTPORTSTART, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataDstPortStart), }, { .name = DSTPORTEND, .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataDstPortEnd), }, { .name = "type", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.dataICMPTypeStart), }, { .name = "typeend", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.dataICMPTypeEnd), }, { .name = "code", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.dataICMPCodeStart), }, { .name = "codeend", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.dataICMPCodeEnd), }, COMMENT_PROP_IPHDR(ipv6HdrFilter), { .name = NULL, } }; #define COMMON_L3_MAC_PROPS(STRUCT) \ {\ .name = SRCMACADDR,\ .datatype = DATATYPE_MACADDR,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.dataSrcMACAddr),\ } #define COMMON_IP_PROPS(STRUCT, ADDRTYPE, MASKTYPE) \ COMMON_L3_MAC_PROPS(STRUCT),\ {\ .name = SRCIPADDR,\ .datatype = ADDRTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataSrcIPAddr),\ },\ {\ .name = SRCIPMASK,\ .datatype = MASKTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataSrcIPMask),\ },\ {\ .name = DSTIPADDR,\ .datatype = ADDRTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataDstIPAddr),\ },\ {\ .name = DSTIPMASK,\ .datatype = MASKTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataDstIPMask),\ },\ {\ .name = SRCIPFROM,\ .datatype = ADDRTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataSrcIPFrom),\ },\ {\ .name = SRCIPTO,\ .datatype = ADDRTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataSrcIPTo),\ },\ {\ .name = DSTIPFROM,\ .datatype = ADDRTYPE,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataDstIPFrom),\ },\ {\ .name = DSTIPTO,\ .datatype = DATATYPE_IPADDR,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataDstIPTo),\ },\ {\ .name = DSCP,\ .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataDSCP),\ .validator = dscpValidator,\ },\ {\ .name = "connlimit-above",\ .datatype = DATATYPE_UINT16,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataConnlimitAbove),\ },\ {\ .name = STATE,\ .datatype = DATATYPE_STRING,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataState),\ .validator = stateValidator,\ .formatter = stateFormatter,\ },\ {\ .name = IPSET,\ .datatype = DATATYPE_IPSETNAME,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataIPSet),\ .validator = ipsetValidator,\ .formatter = ipsetFormatter,\ },\ {\ .name = IPSETFLAGS,\ .datatype = DATATYPE_IPSETFLAGS,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.ipHdr.dataIPSetFlags),\ .validator = ipsetFlagsValidator,\ .formatter = ipsetFlagsFormatter,\ } #define COMMON_PORT_PROPS(STRUCT) \ {\ .name = SRCPORTSTART,\ .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.portData.dataSrcPortStart),\ },\ {\ .name = SRCPORTEND,\ .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.portData.dataSrcPortEnd),\ },\ {\ .name = DSTPORTSTART,\ .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.portData.dataDstPortStart),\ },\ {\ .name = DSTPORTEND,\ .datatype = DATATYPE_UINT16 | DATATYPE_UINT16_HEX,\ .dataIdx = offsetof(virNWFilterRuleDef, p.STRUCT.portData.dataDstPortEnd),\ } static const virXMLAttr2Struct tcpAttributes[] = { COMMON_IP_PROPS(tcpHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMON_PORT_PROPS(tcpHdrFilter), { .name = "option", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.tcpHdrFilter.dataTCPOption), }, { .name = "flags", .datatype = DATATYPE_STRING, .dataIdx = offsetof(virNWFilterRuleDef, p.tcpHdrFilter.dataTCPFlags), .validator = tcpFlagsValidator, .formatter = tcpFlagsFormatter, }, COMMENT_PROP_IPHDR(tcpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct udpAttributes[] = { COMMON_IP_PROPS(udpHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMON_PORT_PROPS(udpHdrFilter), COMMENT_PROP_IPHDR(udpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct udpliteAttributes[] = { COMMON_IP_PROPS(udpliteHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMENT_PROP_IPHDR(udpliteHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct espAttributes[] = { COMMON_IP_PROPS(espHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMENT_PROP_IPHDR(espHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct ahAttributes[] = { COMMON_IP_PROPS(ahHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMENT_PROP_IPHDR(ahHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct sctpAttributes[] = { COMMON_IP_PROPS(sctpHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMON_PORT_PROPS(sctpHdrFilter), COMMENT_PROP_IPHDR(sctpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct icmpAttributes[] = { COMMON_IP_PROPS(icmpHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), { .name = "type", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.icmpHdrFilter.dataICMPType), }, { .name = "code", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.icmpHdrFilter.dataICMPCode), }, COMMENT_PROP_IPHDR(icmpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct allAttributes[] = { COMMON_IP_PROPS(allHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMENT_PROP_IPHDR(allHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct igmpAttributes[] = { COMMON_IP_PROPS(igmpHdrFilter, DATATYPE_IPADDR, DATATYPE_IPMASK), COMMENT_PROP_IPHDR(igmpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct tcpipv6Attributes[] = { COMMON_IP_PROPS(tcpHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMON_PORT_PROPS(tcpHdrFilter), { .name = "option", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.tcpHdrFilter.dataTCPOption), }, COMMENT_PROP_IPHDR(tcpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct udpipv6Attributes[] = { COMMON_IP_PROPS(udpHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMON_PORT_PROPS(udpHdrFilter), COMMENT_PROP_IPHDR(udpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct udpliteipv6Attributes[] = { COMMON_IP_PROPS(udpliteHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMENT_PROP_IPHDR(udpliteHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct espipv6Attributes[] = { COMMON_IP_PROPS(espHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMENT_PROP_IPHDR(espHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct ahipv6Attributes[] = { COMMON_IP_PROPS(ahHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMENT_PROP_IPHDR(ahHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct sctpipv6Attributes[] = { COMMON_IP_PROPS(sctpHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMON_PORT_PROPS(sctpHdrFilter), COMMENT_PROP_IPHDR(sctpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct icmpv6Attributes[] = { COMMON_IP_PROPS(icmpHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), { .name = "type", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.icmpHdrFilter.dataICMPType), }, { .name = "code", .datatype = DATATYPE_UINT8 | DATATYPE_UINT8_HEX, .dataIdx = offsetof(virNWFilterRuleDef, p.icmpHdrFilter.dataICMPCode), }, COMMENT_PROP_IPHDR(icmpHdrFilter), { .name = NULL, } }; static const virXMLAttr2Struct allipv6Attributes[] = { COMMON_IP_PROPS(allHdrFilter, DATATYPE_IPV6ADDR, DATATYPE_IPV6MASK), COMMENT_PROP_IPHDR(allHdrFilter), { .name = NULL, } }; typedef struct _virAttributes virAttributes; struct _virAttributes { const char *id; const virXMLAttr2Struct *att; virNWFilterRuleProtocolType prtclType; }; #define PROTOCOL_ENTRY(ID, ATT, PRTCLTYPE) \ { .id = ID, .att = ATT, .prtclType = PRTCLTYPE } #define PROTOCOL_ENTRY_LAST { .id = NULL } static const virAttributes virAttr[] = { PROTOCOL_ENTRY("arp", arpAttributes, VIR_NWFILTER_RULE_PROTOCOL_ARP), PROTOCOL_ENTRY("rarp", arpAttributes, VIR_NWFILTER_RULE_PROTOCOL_RARP), PROTOCOL_ENTRY("mac", macAttributes, VIR_NWFILTER_RULE_PROTOCOL_MAC), PROTOCOL_ENTRY("vlan", vlanAttributes, VIR_NWFILTER_RULE_PROTOCOL_VLAN), PROTOCOL_ENTRY("stp", stpAttributes, VIR_NWFILTER_RULE_PROTOCOL_STP), PROTOCOL_ENTRY("ip", ipAttributes, VIR_NWFILTER_RULE_PROTOCOL_IP), PROTOCOL_ENTRY("ipv6", ipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_IPV6), PROTOCOL_ENTRY("tcp", tcpAttributes, VIR_NWFILTER_RULE_PROTOCOL_TCP), PROTOCOL_ENTRY("udp", udpAttributes, VIR_NWFILTER_RULE_PROTOCOL_UDP), PROTOCOL_ENTRY("udplite", udpliteAttributes, VIR_NWFILTER_RULE_PROTOCOL_UDPLITE), PROTOCOL_ENTRY("esp", espAttributes, VIR_NWFILTER_RULE_PROTOCOL_ESP), PROTOCOL_ENTRY("ah", ahAttributes, VIR_NWFILTER_RULE_PROTOCOL_AH), PROTOCOL_ENTRY("sctp", sctpAttributes, VIR_NWFILTER_RULE_PROTOCOL_SCTP), PROTOCOL_ENTRY("icmp", icmpAttributes, VIR_NWFILTER_RULE_PROTOCOL_ICMP), PROTOCOL_ENTRY("all", allAttributes, VIR_NWFILTER_RULE_PROTOCOL_ALL), PROTOCOL_ENTRY("igmp", igmpAttributes, VIR_NWFILTER_RULE_PROTOCOL_IGMP), PROTOCOL_ENTRY("tcp-ipv6", tcpipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6), PROTOCOL_ENTRY("udp-ipv6", udpipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_UDPoIPV6), PROTOCOL_ENTRY("udplite-ipv6", udpliteipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_UDPLITEoIPV6), PROTOCOL_ENTRY("esp-ipv6", espipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_ESPoIPV6), PROTOCOL_ENTRY("ah-ipv6", ahipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_AHoIPV6), PROTOCOL_ENTRY("sctp-ipv6", sctpipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_SCTPoIPV6), PROTOCOL_ENTRY("icmpv6", icmpv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_ICMPV6), PROTOCOL_ENTRY("all-ipv6", allipv6Attributes, VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6), PROTOCOL_ENTRY_LAST }; static int virNWFilterRuleDetailsParse(xmlNodePtr node, virNWFilterRuleDef *nwf, const virXMLAttr2Struct *att) { int rc = 0, g_rc = 0; int idx = 0; char *prop; bool found = false; enum attrDatatype datatype, att_datatypes; virNWFilterEntryItemFlags *flags, match_flag = 0, flags_set = 0; nwItemDesc *item; int int_val; unsigned int uint_val; union data data; valueValidator validator; char *match = virXMLPropString(node, "match"); virSocketAddr ipaddr; int base; if (match && STREQ(match, "no")) match_flag = NWFILTER_ENTRY_ITEM_FLAG_IS_NEG; VIR_FREE(match); while (att[idx].name != NULL) { prop = virXMLPropString(node, att[idx].name); VIR_WARNINGS_NO_CAST_ALIGN item = (nwItemDesc *)((char *)nwf + att[idx].dataIdx); VIR_WARNINGS_RESET flags = &item->flags; flags_set = match_flag; if (prop) { found = false; validator = NULL; if (STRPREFIX(prop, "$")) { flags_set |= NWFILTER_ENTRY_ITEM_FLAG_HAS_VAR; if (virNWFilterRuleDefAddVar(nwf, item, &prop[1]) < 0) rc = -1; found = true; } datatype = 1; att_datatypes = att[idx].datatype; while (datatype <= DATATYPE_LAST && !found && rc == 0) { if ((att_datatypes & datatype)) { att_datatypes ^= datatype; validator = att[idx].validator; base = 10; switch (datatype) { case DATATYPE_UINT8_HEX: base = 16; G_GNUC_FALLTHROUGH; case DATATYPE_UINT8: if (virStrToLong_ui(prop, NULL, base, &uint_val) >= 0) { if (uint_val <= 0xff) { item->u.u8 = uint_val; found = true; data.ui = uint_val; } else { rc = -1; } } else { rc = -1; } break; case DATATYPE_UINT16_HEX: base = 16; G_GNUC_FALLTHROUGH; case DATATYPE_UINT16: if (virStrToLong_ui(prop, NULL, base, &uint_val) >= 0) { if (uint_val <= 0xffff) { item->u.u16 = uint_val; found = true; data.ui = uint_val; } else { rc = -1; } } else { rc = -1; } break; case DATATYPE_UINT32_HEX: base = 16; G_GNUC_FALLTHROUGH; case DATATYPE_UINT32: if (virStrToLong_ui(prop, NULL, base, &uint_val) >= 0) { item->u.u32 = uint_val; found = true; data.ui = uint_val; } else { rc = -1; } break; case DATATYPE_IPADDR: if (virSocketAddrParseIPv4(&item->u.ipaddr, prop) < 0) rc = -1; found = true; break; case DATATYPE_IPMASK: if (virStrToLong_ui(prop, NULL, 10, &uint_val) == 0) { if (uint_val <= 32) { if (!validator) item->u.u8 = (uint8_t)uint_val; found = true; data.ui = uint_val; } else { rc = -1; } } else { if (virSocketAddrParseIPv4(&ipaddr, prop) < 0) { rc = -1; } else { int_val = virSocketAddrGetNumNetmaskBits(&ipaddr); if (int_val >= 0) item->u.u8 = int_val; else rc = -1; found = true; } } break; case DATATYPE_MACADDR: if (virMacAddrParse(prop, &item->u.macaddr) < 0) { rc = -1; } found = true; break; case DATATYPE_MACMASK: validator = checkMACMask; if (virMacAddrParse(prop, &item->u.macaddr) < 0) { rc = -1; } data.v = &item->u.macaddr; found = true; break; case DATATYPE_IPV6ADDR: if (virSocketAddrParseIPv6(&item->u.ipaddr, prop) < 0) rc = -1; found = true; break; case DATATYPE_IPV6MASK: if (virStrToLong_ui(prop, NULL, 10, &uint_val) == 0) { if (uint_val <= 128) { if (!validator) item->u.u8 = (uint8_t)uint_val; found = true; data.ui = uint_val; } else { rc = -1; } } else { if (virSocketAddrParseIPv6(&ipaddr, prop) < 0) { rc = -1; } else { int_val = virSocketAddrGetNumNetmaskBits(&ipaddr); if (int_val >= 0) item->u.u8 = int_val; else rc = -1; found = true; } } break; case DATATYPE_STRING: case DATATYPE_IPSETFLAGS: case DATATYPE_IPSETNAME: if (!validator) { /* not supported */ rc = -1; break; } data.c = prop; found = true; break; case DATATYPE_STRINGCOPY: if (!(item->u.string = virNWFilterRuleDefAddString(nwf, prop, att[idx].maxstrlen))) { rc = -1; break; } data.c = item->u.string; found = true; break; case DATATYPE_BOOLEAN: if (STREQ(prop, "true") || STREQ(prop, "1") || STREQ(prop, "yes")) item->u.boolean = true; else item->u.boolean = false; data.ui = item->u.boolean; found = true; break; case DATATYPE_LAST: default: break; } } if (rc != 0 && att_datatypes != 0) { rc = 0; found = false; } datatype <<= 1; } /* while */ if (found && rc == 0) { *flags = NWFILTER_ENTRY_ITEM_FLAG_EXISTS | flags_set; item->datatype = datatype >> 1; if (validator) { if (!validator(datatype >> 1, &data, nwf, item)) { rc = -1; *flags = 0; } } } if (!found || rc) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%1$s has illegal value %2$s"), att[idx].name, prop); rc = -1; } VIR_FREE(prop); } if (rc) { g_rc = rc; rc = 0; } idx++; } return g_rc; } static virNWFilterIncludeDef * virNWFilterIncludeParse(xmlNodePtr cur) { virNWFilterIncludeDef *ret; ret = g_new0(virNWFilterIncludeDef, 1); ret->filterref = virXMLPropString(cur, "filter"); if (!ret->filterref) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("rule node requires action attribute")); goto err_exit; } ret->params = virNWFilterParseParamAttributes(cur); if (!ret->params) goto err_exit; return ret; err_exit: virNWFilterIncludeDefFree(ret); return NULL; } static void virNWFilterRuleDefFixupIPSet(ipHdrDataDef *ipHdr) { if (HAS_ENTRY_ITEM(&ipHdr->dataIPSet) && !HAS_ENTRY_ITEM(&ipHdr->dataIPSetFlags)) { ipHdr->dataIPSetFlags.flags = NWFILTER_ENTRY_ITEM_FLAG_EXISTS; ipHdr->dataIPSetFlags.u.ipset.numFlags = 1; ipHdr->dataIPSetFlags.u.ipset.flags = 1; } else { ipHdr->dataIPSet.flags = 0; ipHdr->dataIPSetFlags.flags = 0; } } /* * virNWFilterRuleValidate * * Perform some basic rule validation to prevent rules from being * defined that cannot be instantiated. */ static int virNWFilterRuleValidate(virNWFilterRuleDef *rule) { int ret = 0; portDataDef *portData = NULL; nwItemDesc *dataProtocolID = NULL; const char *protocol = NULL; switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_IP: portData = &rule->p.ipHdrFilter.portData; protocol = "IP"; dataProtocolID = &rule->p.ipHdrFilter.ipHdr.dataProtocolID; G_GNUC_FALLTHROUGH; case VIR_NWFILTER_RULE_PROTOCOL_IPV6: if (portData == NULL) { portData = &rule->p.ipv6HdrFilter.portData; protocol = "IPv6"; dataProtocolID = &rule->p.ipv6HdrFilter.ipHdr.dataProtocolID; } if (HAS_ENTRY_ITEM(&portData->dataSrcPortStart) || HAS_ENTRY_ITEM(&portData->dataDstPortStart) || HAS_ENTRY_ITEM(&portData->dataSrcPortEnd) || HAS_ENTRY_ITEM(&portData->dataDstPortEnd)) { if (HAS_ENTRY_ITEM(dataProtocolID)) { switch (dataProtocolID->u.u8) { case 6: /* tcp */ case 17: /* udp */ case 33: /* dccp */ case 132: /* sctp */ break; default: ret = -1; } } else { ret = -1; } if (ret < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("%1$s rule with port specification requires protocol specification with protocol to be either one of tcp(6), udp(17), dccp(33), or sctp(132)"), protocol); } } break; case VIR_NWFILTER_RULE_PROTOCOL_NONE: 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_TCP: case VIR_NWFILTER_RULE_PROTOCOL_ICMP: case VIR_NWFILTER_RULE_PROTOCOL_IGMP: 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_ALL: case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: case VIR_NWFILTER_RULE_PROTOCOL_ICMPV6: 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_ALLoIPV6: break; case VIR_NWFILTER_RULE_PROTOCOL_LAST: default: virReportEnumRangeError(virNWFilterRuleProtocolType, rule->prtclType); return -1; } return ret; } static void virNWFilterRuleDefFixup(virNWFilterRuleDef *rule) { #define COPY_NEG_SIGN(A, B) \ (A).flags = ((A).flags & ~NWFILTER_ENTRY_ITEM_FLAG_IS_NEG) | \ ((B).flags & NWFILTER_ENTRY_ITEM_FLAG_IS_NEG); switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_MAC: COPY_NEG_SIGN(rule->p.ethHdrFilter.ethHdr.dataSrcMACMask, rule->p.ethHdrFilter.ethHdr.dataSrcMACAddr); COPY_NEG_SIGN(rule->p.ethHdrFilter.ethHdr.dataDstMACMask, rule->p.ethHdrFilter.ethHdr.dataDstMACAddr); break; case VIR_NWFILTER_RULE_PROTOCOL_VLAN: COPY_NEG_SIGN(rule->p.vlanHdrFilter.ethHdr.dataSrcMACMask, rule->p.vlanHdrFilter.ethHdr.dataSrcMACAddr); COPY_NEG_SIGN(rule->p.vlanHdrFilter.ethHdr.dataDstMACMask, rule->p.vlanHdrFilter.ethHdr.dataDstMACAddr); break; case VIR_NWFILTER_RULE_PROTOCOL_STP: COPY_NEG_SIGN(rule->p.stpHdrFilter.ethHdr.dataSrcMACMask, rule->p.stpHdrFilter.ethHdr.dataSrcMACAddr); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataRootPriHi, rule->p.stpHdrFilter.dataRootPri); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataRootAddrMask, rule->p.stpHdrFilter.dataRootAddr); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataRootCostHi, rule->p.stpHdrFilter.dataRootCost); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataSndrPrioHi, rule->p.stpHdrFilter.dataSndrPrio); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataSndrAddrMask, rule->p.stpHdrFilter.dataSndrAddr); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataPortHi, rule->p.stpHdrFilter.dataPort); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataAgeHi, rule->p.stpHdrFilter.dataAge); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataMaxAgeHi, rule->p.stpHdrFilter.dataMaxAge); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataHelloTimeHi, rule->p.stpHdrFilter.dataHelloTime); COPY_NEG_SIGN(rule->p.stpHdrFilter.dataFwdDelayHi, rule->p.stpHdrFilter.dataFwdDelay); break; case VIR_NWFILTER_RULE_PROTOCOL_IP: COPY_NEG_SIGN(rule->p.ipHdrFilter.ipHdr.dataSrcIPMask, rule->p.ipHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.ipHdrFilter.ipHdr.dataDstIPMask, rule->p.ipHdrFilter.ipHdr.dataDstIPAddr); virNWFilterRuleDefFixupIPSet(&rule->p.ipHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_IPV6: COPY_NEG_SIGN(rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask, rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask, rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.ipv6HdrFilter.dataICMPTypeEnd, rule->p.ipv6HdrFilter.dataICMPTypeStart); COPY_NEG_SIGN(rule->p.ipv6HdrFilter.dataICMPCodeStart, rule->p.ipv6HdrFilter.dataICMPTypeStart); COPY_NEG_SIGN(rule->p.ipv6HdrFilter.dataICMPCodeEnd, rule->p.ipv6HdrFilter.dataICMPTypeStart); virNWFilterRuleDefFixupIPSet(&rule->p.ipv6HdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_ARP: case VIR_NWFILTER_RULE_PROTOCOL_RARP: case VIR_NWFILTER_RULE_PROTOCOL_NONE: break; case VIR_NWFILTER_RULE_PROTOCOL_TCP: case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: COPY_NEG_SIGN(rule->p.tcpHdrFilter.ipHdr.dataSrcIPMask, rule->p.tcpHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.tcpHdrFilter.ipHdr.dataDstIPMask, rule->p.tcpHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.tcpHdrFilter.ipHdr.dataSrcIPTo, rule->p.tcpHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.tcpHdrFilter.ipHdr.dataDstIPTo, rule->p.tcpHdrFilter.ipHdr.dataDstIPFrom); COPY_NEG_SIGN(rule->p.tcpHdrFilter.portData.dataSrcPortEnd, rule->p.tcpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.tcpHdrFilter.portData.dataDstPortStart, rule->p.tcpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.tcpHdrFilter.portData.dataDstPortEnd, rule->p.tcpHdrFilter.portData.dataSrcPortStart); virNWFilterRuleDefFixupIPSet(&rule->p.tcpHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_UDP: case VIR_NWFILTER_RULE_PROTOCOL_UDPoIPV6: COPY_NEG_SIGN(rule->p.udpHdrFilter.ipHdr.dataSrcIPMask, rule->p.udpHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.udpHdrFilter.ipHdr.dataDstIPMask, rule->p.udpHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.udpHdrFilter.ipHdr.dataSrcIPTo, rule->p.udpHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.udpHdrFilter.ipHdr.dataDstIPTo, rule->p.udpHdrFilter.ipHdr.dataDstIPFrom); COPY_NEG_SIGN(rule->p.udpHdrFilter.portData.dataSrcPortEnd, rule->p.udpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.udpHdrFilter.portData.dataDstPortStart, rule->p.udpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.udpHdrFilter.portData.dataDstPortEnd, rule->p.udpHdrFilter.portData.dataSrcPortStart); virNWFilterRuleDefFixupIPSet(&rule->p.udpHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_UDPLITE: case VIR_NWFILTER_RULE_PROTOCOL_UDPLITEoIPV6: COPY_NEG_SIGN(rule->p.udpliteHdrFilter.ipHdr.dataSrcIPMask, rule->p.udpliteHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.udpliteHdrFilter.ipHdr.dataDstIPMask, rule->p.udpliteHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.udpliteHdrFilter.ipHdr.dataSrcIPTo, rule->p.udpliteHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.udpliteHdrFilter.ipHdr.dataDstIPTo, rule->p.udpliteHdrFilter.ipHdr.dataDstIPFrom); virNWFilterRuleDefFixupIPSet(&rule->p.udpliteHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_ESP: case VIR_NWFILTER_RULE_PROTOCOL_ESPoIPV6: COPY_NEG_SIGN(rule->p.espHdrFilter.ipHdr.dataSrcIPMask, rule->p.espHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.espHdrFilter.ipHdr.dataDstIPMask, rule->p.espHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.espHdrFilter.ipHdr.dataSrcIPTo, rule->p.espHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.espHdrFilter.ipHdr.dataDstIPTo, rule->p.espHdrFilter.ipHdr.dataDstIPFrom); virNWFilterRuleDefFixupIPSet(&rule->p.espHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_AH: case VIR_NWFILTER_RULE_PROTOCOL_AHoIPV6: COPY_NEG_SIGN(rule->p.ahHdrFilter.ipHdr.dataSrcIPMask, rule->p.ahHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.ahHdrFilter.ipHdr.dataDstIPMask, rule->p.ahHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.ahHdrFilter.ipHdr.dataSrcIPTo, rule->p.ahHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.ahHdrFilter.ipHdr.dataDstIPTo, rule->p.ahHdrFilter.ipHdr.dataDstIPFrom); virNWFilterRuleDefFixupIPSet(&rule->p.ahHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_SCTP: case VIR_NWFILTER_RULE_PROTOCOL_SCTPoIPV6: COPY_NEG_SIGN(rule->p.sctpHdrFilter.ipHdr.dataSrcIPMask, rule->p.sctpHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.sctpHdrFilter.ipHdr.dataDstIPMask, rule->p.sctpHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.sctpHdrFilter.ipHdr.dataSrcIPTo, rule->p.sctpHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.sctpHdrFilter.ipHdr.dataDstIPTo, rule->p.sctpHdrFilter.ipHdr.dataDstIPFrom); COPY_NEG_SIGN(rule->p.sctpHdrFilter.portData.dataSrcPortEnd, rule->p.sctpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.sctpHdrFilter.portData.dataDstPortStart, rule->p.sctpHdrFilter.portData.dataSrcPortStart); COPY_NEG_SIGN(rule->p.sctpHdrFilter.portData.dataDstPortEnd, rule->p.sctpHdrFilter.portData.dataSrcPortStart); virNWFilterRuleDefFixupIPSet(&rule->p.sctpHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_ICMP: case VIR_NWFILTER_RULE_PROTOCOL_ICMPV6: COPY_NEG_SIGN(rule->p.icmpHdrFilter.ipHdr.dataSrcIPMask, rule->p.icmpHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.icmpHdrFilter.ipHdr.dataDstIPMask, rule->p.icmpHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.icmpHdrFilter.ipHdr.dataSrcIPTo, rule->p.icmpHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.icmpHdrFilter.ipHdr.dataDstIPTo, rule->p.icmpHdrFilter.ipHdr.dataDstIPFrom); COPY_NEG_SIGN(rule->p.icmpHdrFilter.dataICMPCode, rule->p.icmpHdrFilter.dataICMPType); virNWFilterRuleDefFixupIPSet(&rule->p.icmpHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_ALL: case VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6: COPY_NEG_SIGN(rule->p.allHdrFilter.ipHdr.dataSrcIPMask, rule->p.allHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.allHdrFilter.ipHdr.dataDstIPMask, rule->p.allHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.allHdrFilter.ipHdr.dataSrcIPTo, rule->p.allHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.allHdrFilter.ipHdr.dataDstIPTo, rule->p.allHdrFilter.ipHdr.dataDstIPFrom); break; case VIR_NWFILTER_RULE_PROTOCOL_IGMP: COPY_NEG_SIGN(rule->p.igmpHdrFilter.ipHdr.dataSrcIPMask, rule->p.igmpHdrFilter.ipHdr.dataSrcIPAddr); COPY_NEG_SIGN(rule->p.igmpHdrFilter.ipHdr.dataDstIPMask, rule->p.igmpHdrFilter.ipHdr.dataDstIPAddr); COPY_NEG_SIGN(rule->p.igmpHdrFilter.ipHdr.dataSrcIPTo, rule->p.igmpHdrFilter.ipHdr.dataSrcIPFrom); COPY_NEG_SIGN(rule->p.igmpHdrFilter.ipHdr.dataDstIPTo, rule->p.igmpHdrFilter.ipHdr.dataDstIPFrom); virNWFilterRuleDefFixupIPSet(&rule->p.igmpHdrFilter.ipHdr); break; case VIR_NWFILTER_RULE_PROTOCOL_LAST: break; } #undef COPY_NEG_SIGN } static virNWFilterRuleDef * virNWFilterRuleParse(xmlNodePtr node) { g_autofree char *action = NULL; g_autofree char *direction = NULL; g_autofree char *prio = NULL; g_autofree char *statematch = NULL; bool found; int found_i = 0; int priority; xmlNodePtr cur; g_autoptr(virNWFilterRuleDef) ret = NULL; ret = g_new0(virNWFilterRuleDef, 1); action = virXMLPropString(node, "action"); direction = virXMLPropString(node, "direction"); prio = virXMLPropString(node, "priority"); statematch = virXMLPropString(node, "statematch"); if (!action) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("rule node requires action attribute")); return NULL; } if ((ret->action = virNWFilterRuleActionTypeFromString(action)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("unknown rule action attribute value")); return NULL; } if (!direction) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("rule node requires direction attribute")); return NULL; } if ((ret->tt = virNWFilterRuleDirectionTypeFromString(direction)) < 0) { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", _("unknown rule direction attribute value")); return NULL; } ret->priority = MAX_RULE_PRIORITY / 2; if (prio) { if (virStrToLong_i(prio, NULL, 10, &priority) >= 0) { if (priority <= MAX_RULE_PRIORITY && priority >= MIN_RULE_PRIORITY) ret->priority = priority; } } if (statematch && (STREQ(statematch, "0") || STRCASEEQ(statematch, "false"))) ret->flags |= RULE_FLAG_NO_STATEMATCH; cur = node->children; found = false; while (cur != NULL) { if (cur->type == XML_ELEMENT_NODE) { size_t i = 0; while (1) { if (found) i = found_i; if (virXMLNodeNameEqual(cur, virAttr[i].id)) { found_i = i; found = true; ret->prtclType = virAttr[i].prtclType; if (virNWFilterRuleDetailsParse(cur, ret, virAttr[i].att) < 0) { return NULL; } if (virNWFilterRuleValidate(ret) < 0) return NULL; break; } if (!found) { i++; if (!virAttr[i].id) break; } else { break; } } } cur = cur->next; } virNWFilterRuleDefFixup(ret); return g_steal_pointer(&ret); } static bool virNWFilterIsValidChainName(const char *chainname) { if (strlen(chainname) > MAX_CHAIN_SUFFIX_SIZE) { virReportError(VIR_ERR_INVALID_ARG, _("Name of chain is longer than %1$u characters"), MAX_CHAIN_SUFFIX_SIZE); return false; } if (chainname[strspn(chainname, VALID_CHAINNAME)] != 0) { virReportError(VIR_ERR_INVALID_ARG, "%s", _("Chain name contains invalid characters")); return false; } return true; } /* * Test whether the name of the chain is supported. * It current has to have a prefix of either one of the strings found in * virNWFilterChainSuffixTypeToString(). */ static const char * virNWFilterIsAllowedChain(const char *chainname) { virNWFilterChainSuffixType i; const char *name; g_autofree char *msg = NULL; g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; bool printed = false; if (!virNWFilterIsValidChainName(chainname)) return NULL; for (i = 0; i < VIR_NWFILTER_CHAINSUFFIX_LAST; i++) { name = virNWFilterChainSuffixTypeToString(i); if (i == VIR_NWFILTER_CHAINSUFFIX_ROOT) { /* allow 'root' as a complete name but not as a prefix */ if (STREQ(chainname, name)) return name; if (STRPREFIX(chainname, name)) return NULL; } if (STRPREFIX(chainname, name)) return name; } virBufferAsprintf(&buf, _("Invalid chain name '%1$s'. Please use a chain name called '%2$s' or any of the following prefixes: "), chainname, virNWFilterChainSuffixTypeToString( VIR_NWFILTER_CHAINSUFFIX_ROOT)); for (i = 0; i < VIR_NWFILTER_CHAINSUFFIX_LAST; i++) { if (i == VIR_NWFILTER_CHAINSUFFIX_ROOT) continue; if (printed) virBufferAddLit(&buf, ", "); virBufferAdd(&buf, virNWFilterChainSuffixTypeToString(i), -1); printed = true; } msg = virBufferContentAndReset(&buf); virReportError(VIR_ERR_INVALID_ARG, "%s", msg); return NULL; } static virNWFilterDef * virNWFilterDefParseXML(xmlXPathContextPtr ctxt) { g_autoptr(virNWFilterDef) ret = NULL; xmlNodePtr curr = ctxt->node; g_autofree char *uuid = NULL; g_autofree char *chain = NULL; g_autofree char *chain_pri_s = NULL; virNWFilterEntry *entry; int chain_priority; const char *name_prefix; ret = g_new0(virNWFilterDef, 1); ret->name = virXPathString("string(./@name)", ctxt); if (!ret->name) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("filter has no name")); return NULL; } chain_pri_s = virXPathString("string(./@priority)", ctxt); if (chain_pri_s) { if (virStrToLong_i(chain_pri_s, NULL, 10, &chain_priority) < 0) { virReportError(VIR_ERR_INVALID_ARG, _("Could not parse chain priority '%1$s'"), chain_pri_s); return NULL; } if (chain_priority < NWFILTER_MIN_FILTER_PRIORITY || chain_priority > NWFILTER_MAX_FILTER_PRIORITY) { virReportError(VIR_ERR_INVALID_ARG, _("Priority '%1$d' is outside valid range of [%2$d,%3$d]"), chain_priority, NWFILTER_MIN_FILTER_PRIORITY, NWFILTER_MAX_FILTER_PRIORITY); return NULL; } } chain = virXPathString("string(./@chain)", ctxt); if (chain) { name_prefix = virNWFilterIsAllowedChain(chain); if (name_prefix == NULL) return NULL; ret->chainsuffix = g_steal_pointer(&chain); if (chain_pri_s) { ret->chainPriority = chain_priority; } else { /* assign default priority if none can be found via lookup */ if (intMapGetByString(chain_priorities, name_prefix, 0, &ret->chainPriority) < 0) { ret->chainPriority = (NWFILTER_MAX_FILTER_PRIORITY + NWFILTER_MIN_FILTER_PRIORITY) / 2; } } } else { ret->chainsuffix = g_strdup(virNWFilterChainSuffixTypeToString(VIR_NWFILTER_CHAINSUFFIX_ROOT)); } uuid = virXPathString("string(./uuid)", ctxt); ret->uuid_specified = (uuid != NULL); if (uuid == NULL) { if (virUUIDGenerate(ret->uuid) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("unable to generate uuid")); return NULL; } } else { if (virUUIDParse(uuid, ret->uuid) < 0) { virReportError(VIR_ERR_XML_ERROR, "%s", _("malformed uuid element")); return NULL; } } curr = curr->children; while (curr != NULL) { if (curr->type == XML_ELEMENT_NODE) { entry = g_new0(virNWFilterEntry, 1); if (virXMLNodeNameEqual(curr, "rule")) { if (!(entry->rule = virNWFilterRuleParse(curr))) { virNWFilterEntryFree(entry); return NULL; } } else if (virXMLNodeNameEqual(curr, "filterref")) { if (!(entry->include = virNWFilterIncludeParse(curr))) { virNWFilterEntryFree(entry); return NULL; } } if (entry->rule || entry->include) { VIR_APPEND_ELEMENT_COPY(ret->filterEntries, ret->nentries, entry); } else { virNWFilterEntryFree(entry); } } curr = curr->next; } return g_steal_pointer(&ret); } virNWFilterDef * virNWFilterDefParse(const char *xmlStr, const char *filename, unsigned int flags) { g_autoptr(xmlDoc) xml = NULL; g_autoptr(xmlXPathContext) ctxt = NULL; bool validate = flags & VIR_NWFILTER_DEFINE_VALIDATE; if (!(xml = virXMLParse(filename, xmlStr, _("(nwfilter_definition)"), "filter", &ctxt, "nwfilter.rng", validate))) return NULL; return virNWFilterDefParseXML(ctxt); } int virNWFilterSaveConfig(const char *configDir, virNWFilterDef *def) { g_autofree char *xml = NULL; char uuidstr[VIR_UUID_STRING_BUFLEN]; g_autofree char *configFile = NULL; if (!(xml = virNWFilterDefFormat(def))) return -1; if (!(configFile = virFileBuildPath(configDir, def->name, ".xml"))) return -1; virUUIDFormat(def->uuid, uuidstr); return virXMLSaveFile(configFile, virXMLPickShellSafeComment(def->name, uuidstr), "nwfilter-edit", xml); } int virNWFilterDeleteDef(const char *configDir, virNWFilterDef *def) { g_autofree char *configFile = NULL; if (!(configFile = virFileBuildPath(configDir, def->name, ".xml"))) return -1; if (unlink(configFile) < 0) { virReportError(VIR_ERR_INTERNAL_ERROR, _("cannot remove config for %1$s"), def->name); return -1; } return 0; } static void virNWIPAddressFormat(virBuffer *buf, virSocketAddr *ipaddr) { char *output = virSocketAddrFormat(ipaddr); if (output) { virBufferAdd(buf, output, -1); VIR_FREE(output); } } static void virNWFilterRuleDefDetailsFormat(virBuffer *buf, const char *type, const virXMLAttr2Struct *att, virNWFilterRuleDef *def) { size_t i = 0, j; bool typeShown = false; bool neverShown = true; bool asHex; enum match { MATCH_NONE = 0, MATCH_YES, MATCH_NO } matchShown = MATCH_NONE; nwItemDesc *item; while (att[i].name) { virNWFilterEntryItemFlags flags; VIR_WARNINGS_NO_CAST_ALIGN item = (nwItemDesc *)((char *)def + att[i].dataIdx); VIR_WARNINGS_RESET flags = item->flags; if ((flags & NWFILTER_ENTRY_ITEM_FLAG_EXISTS)) { if (!typeShown) { virBufferAsprintf(buf, "<%s", type); typeShown = true; neverShown = false; } if ((flags & NWFILTER_ENTRY_ITEM_FLAG_IS_NEG)) { if (matchShown == MATCH_NONE) { virBufferAddLit(buf, " match='no'"); matchShown = MATCH_NO; } else if (matchShown == MATCH_YES) { virBufferAddLit(buf, "/>\n"); typeShown = false; matchShown = MATCH_NONE; continue; } } else { if (matchShown == MATCH_NO) { virBufferAddLit(buf, "/>\n"); typeShown = false; matchShown = MATCH_NONE; continue; } matchShown = MATCH_YES; } virBufferAsprintf(buf, " %s='", att[i].name); if (att[i].formatter && !(flags & NWFILTER_ENTRY_ITEM_FLAG_HAS_VAR)) { if (!att[i].formatter(buf, def, item)) { virReportError(VIR_ERR_INTERNAL_ERROR, _("formatter for %1$s %2$s reported error"), type, att[i].name); return; } } else if ((flags & NWFILTER_ENTRY_ITEM_FLAG_HAS_VAR)) { virBufferAddChar(buf, '$'); virNWFilterVarAccessPrint(item->varAccess, buf); } else { asHex = false; switch (item->datatype) { case DATATYPE_UINT8_HEX: asHex = true; G_GNUC_FALLTHROUGH; case DATATYPE_IPMASK: case DATATYPE_IPV6MASK: /* display all masks in CIDR format */ case DATATYPE_UINT8: virBufferAsprintf(buf, asHex ? "0x%x" : "%d", item->u.u8); break; case DATATYPE_UINT16_HEX: asHex = true; G_GNUC_FALLTHROUGH; case DATATYPE_UINT16: virBufferAsprintf(buf, asHex ? "0x%x" : "%d", item->u.u16); break; case DATATYPE_UINT32_HEX: asHex = true; G_GNUC_FALLTHROUGH; case DATATYPE_UINT32: virBufferAsprintf(buf, asHex ? "0x%x" : "%u", item->u.u32); break; case DATATYPE_IPADDR: case DATATYPE_IPV6ADDR: virNWIPAddressFormat(buf, &item->u.ipaddr); break; case DATATYPE_MACMASK: case DATATYPE_MACADDR: for (j = 0; j < 6; j++) virBufferAsprintf(buf, "%02x%s", item->u.macaddr.addr[j], (j < 5) ? ":" : ""); break; case DATATYPE_STRINGCOPY: virBufferEscapeString(buf, "%s", item->u.string); break; case DATATYPE_BOOLEAN: if (item->u.boolean) virBufferAddLit(buf, "true"); else virBufferAddLit(buf, "false"); break; case DATATYPE_IPSETNAME: case DATATYPE_IPSETFLAGS: case DATATYPE_STRING: case DATATYPE_LAST: default: virBufferAsprintf(buf, "UNSUPPORTED DATATYPE 0x%02x\n", att[i].datatype); } } virBufferAddLit(buf, "'"); } i++; } if (typeShown) virBufferAddLit(buf, "/>\n"); if (neverShown) virBufferAsprintf(buf, "<%s/>\n", type); return; } static int virNWFilterRuleDefFormat(virBuffer *buf, virNWFilterRuleDef *def) { size_t i; bool subelement = false; virBufferAsprintf(buf, "action), virNWFilterRuleDirectionTypeToString(def->tt), def->priority); if ((def->flags & RULE_FLAG_NO_STATEMATCH)) virBufferAddLit(buf, " statematch='false'"); virBufferAdjustIndent(buf, 2); i = 0; while (virAttr[i].id) { if (virAttr[i].prtclType == def->prtclType) { if (!subelement) virBufferAddLit(buf, ">\n"); virNWFilterRuleDefDetailsFormat(buf, virAttr[i].id, virAttr[i].att, def); subelement = true; break; } i++; } virBufferAdjustIndent(buf, -2); if (subelement) virBufferAddLit(buf, "\n"); else virBufferAddLit(buf, "/>\n"); return 0; } static int virNWFilterEntryFormat(virBuffer *buf, virNWFilterEntry *entry) { if (entry->rule) return virNWFilterRuleDefFormat(buf, entry->rule); return virNWFilterFormatParamAttributes(buf, entry->include->params, entry->include->filterref); } char * virNWFilterDefFormat(const virNWFilterDef *def) { g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER; char uuid[VIR_UUID_STRING_BUFLEN]; size_t i; virBufferAsprintf(&buf, "name, def->chainsuffix); if (def->chainPriority != 0) virBufferAsprintf(&buf, " priority='%d'", def->chainPriority); virBufferAddLit(&buf, ">\n"); virBufferAdjustIndent(&buf, 2); virUUIDFormat(def->uuid, uuid); virBufferAsprintf(&buf, "%s\n", uuid); for (i = 0; i < def->nentries; i++) { if (virNWFilterEntryFormat(&buf, def->filterEntries[i]) < 0) return NULL; } virBufferAdjustIndent(&buf, -2); virBufferAddLit(&buf, "\n"); return virBufferContentAndReset(&buf); } static bool initialized; static virNWFilterTriggerRebuildCallback rebuildCallback; static void *rebuildOpaque; int virNWFilterConfLayerInit(virNWFilterTriggerRebuildCallback cb, void *opaque) { if (initialized) return -1; rebuildCallback = cb; rebuildOpaque = opaque; initialized = true; return 0; } void virNWFilterConfLayerShutdown(void) { if (!initialized) return; initialized = false; rebuildCallback = NULL; rebuildOpaque = NULL; } int virNWFilterTriggerRebuild(void) { if (rebuildCallback) return rebuildCallback(rebuildOpaque); return 0; } bool virNWFilterRuleIsProtocolIPv4(virNWFilterRuleDef *rule) { if (rule->prtclType >= VIR_NWFILTER_RULE_PROTOCOL_TCP && rule->prtclType <= VIR_NWFILTER_RULE_PROTOCOL_ALL) return true; return false; } bool virNWFilterRuleIsProtocolIPv6(virNWFilterRuleDef *rule) { if (rule->prtclType >= VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6 && rule->prtclType <= VIR_NWFILTER_RULE_PROTOCOL_ALLoIPV6) return true; return false; } bool virNWFilterRuleIsProtocolEthernet(virNWFilterRuleDef *rule) { if (rule->prtclType <= VIR_NWFILTER_RULE_PROTOCOL_IPV6) return true; return false; }