This patch adds IPv6 support for the ebtables layer. Since the parser etc. are all parameterized, it was fairly easy to add this... Signed-off-by: Stefan Berger --- src/conf/nwfilter_conf.c | 230 +++++++++++++++++++++++++++++- src/conf/nwfilter_conf.h | 18 ++ src/nwfilter/nwfilter_ebiptables_driver.c | 155 ++++++++++++++++++++ 3 files changed, 399 insertions(+), 4 deletions(-) Index: libvirt-acl/src/conf/nwfilter_conf.c =================================================================== --- libvirt-acl.orig/src/conf/nwfilter_conf.c +++ libvirt-acl/src/conf/nwfilter_conf.c @@ -73,7 +73,8 @@ VIR_ENUM_IMPL(virNWFilterEbtablesTable, VIR_ENUM_IMPL(virNWFilterChainSuffix, VIR_NWFILTER_CHAINSUFFIX_LAST, "root", "arp", - "ipv4"); + "ipv4", + "ipv6"); /* @@ -366,6 +367,9 @@ static const struct int_map macProtoMap[ .attr = ETHERTYPE_IP, .val = "ipv4", }, { + .attr = ETHERTYPE_IPV6, + .val = "ipv6", + }, { .val = NULL, } }; @@ -449,6 +453,13 @@ checkIPv4Mask(enum attrDatatype datatype return checkValidMask(maskptr, 4); } +static bool +checkIPv6Mask(enum attrDatatype datatype ATTRIBUTE_UNUSED, void *maskptr, + virNWFilterRuleDefPtr nwf ATTRIBUTE_UNUSED) +{ + return checkValidMask(maskptr, 16); +} + static bool checkMACMask(enum attrDatatype datatype ATTRIBUTE_UNUSED, @@ -765,6 +776,61 @@ static const virXMLAttr2Struct ipAttribu }; +static const virXMLAttr2Struct ipv6Attributes[] = { + COMMON_MAC_PROPS(ipv6HdrFilter), + { + .name = SRCIPADDR, + .datatype = DATATYPE_IPV6ADDR, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataSrcIPAddr), + }, + { + .name = DSTIPADDR, + .datatype = DATATYPE_IPV6ADDR, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataDstIPAddr), + }, + { + .name = SRCIPMASK, + .datatype = DATATYPE_IPV6MASK, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataSrcIPMask), + }, + { + .name = DSTIPMASK, + .datatype = DATATYPE_IPV6MASK, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataDstIPMask), + }, + { + .name = "protocol", + .datatype = DATATYPE_STRING, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.ipHdr.dataProtocolID), + .validator= checkIPProtocolID, + .formatter= formatIPProtocolID, + }, + { + .name = SRCPORTSTART, + .datatype = DATATYPE_UINT16, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataSrcPortStart), + }, + { + .name = SRCPORTEND, + .datatype = DATATYPE_UINT16, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataSrcPortEnd), + }, + { + .name = DSTPORTSTART, + .datatype = DATATYPE_UINT16, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataDstPortStart), + }, + { + .name = DSTPORTEND, + .datatype = DATATYPE_UINT16, + .dataIdx = offsetof(virNWFilterRuleDef, p.ipv6HdrFilter.portData.dataDstPortEnd), + }, + { + .name = NULL, + } +}; + + typedef struct _virAttributes virAttributes; struct _virAttributes { const char *id; @@ -787,6 +853,10 @@ static const virAttributes virAttr[] = { .att = ipAttributes, .prtclType = VIR_NWFILTER_RULE_PROTOCOL_IP, }, { + .id = "ipv6", + .att = ipv6Attributes, + .prtclType = VIR_NWFILTER_RULE_PROTOCOL_IPV6, + }, { .id = NULL, } }; @@ -825,6 +895,89 @@ virNWIPv4AddressParser(const char *input } +static bool +virNWIPv6AddressParser(const char *input, + nwIPAddressPtr output) +{ + int i, j, pos; + uint16_t n; + int shiftpos = -1; + char prevchar; + char base; + + memset(output, 0x0, sizeof(*output)); + + output->isIPv6 = 1; + + pos = 0; + i = 0; + + while (i < 8) { + j = 0; + n = 0; + while (1) { + prevchar = input[pos++]; + if (prevchar == ':' || prevchar == 0) { + if (j > 0) { + output->addr.ipv6Addr[i * 2 + 0] = n >> 8; + output->addr.ipv6Addr[i * 2 + 1] = n; + i++; + } + break; + } + + if (j >= 4) + return 0; + + if (prevchar >= '0' && prevchar <= '9') + base = '0'; + else if (prevchar >= 'a' && prevchar <= 'f') + base = 'a' - 10; + else if (prevchar >= 'A' && prevchar <= 'F') + base = 'A' - 10; + else + return 0; + n <<= 4; + n |= (prevchar - base); + j++; + } + + if (prevchar == 0) + break; + + if (input[pos] == ':') { + pos ++; + // sequence of zeros + if (prevchar != ':') + return 0; + + if (shiftpos != -1) + return 0; + + shiftpos = i; + } + } + + if (shiftpos != -1) { + if (i >= 7) + return 0; + i--; + j = 0; + while (i >= shiftpos) { + output->addr.ipv6Addr[15 - (j*2) - 1] = + output->addr.ipv6Addr[i * 2 + 0]; + output->addr.ipv6Addr[15 - (j*2) - 0] = + output->addr.ipv6Addr[i * 2 + 1]; + output->addr.ipv6Addr[i * 2 + 0] = 0; + output->addr.ipv6Addr[i * 2 + 1] = 0; + i--; + j++; + } + } + return 1; +} + + static int virNWFilterRuleDetailsParse(virConnectPtr conn ATTRIBUTE_UNUSED, xmlNodePtr node, @@ -969,6 +1122,41 @@ virNWFilterRuleDetailsParse(virConnectPt found = 1; break; + case DATATYPE_IPV6ADDR: + storage_ptr = &item->u.ipaddr; + if (!virNWIPv6AddressParser(prop, + (nwIPAddressPtr)storage_ptr)) { + rc = -1; + } + found = 1; + break; + + case DATATYPE_IPV6MASK: + storage_ptr = &item->u.u8; + if (!virNWIPv6AddressParser(prop, &ipaddr)) { + if (sscanf(prop, "%d", &int_val) == 1) { + if (int_val >= 0 && int_val <= 128) { + if (!validator) + *(uint8_t *)storage_ptr = + (uint8_t)int_val; + found = 1; + data_ptr = &int_val; + } else + rc = -1; + } else + rc = -1; + } else { + if (checkIPv6Mask(datatype, + ipaddr.addr.ipv6Addr, nwf)) + *(uint8_t *)storage_ptr = + getMaskNumBits(ipaddr.addr.ipv6Addr, + sizeof(ipaddr.addr.ipv6Addr)); + else + rc = -1; + found = 1; + } + break; + case DATATYPE_STRING: if (!validator) { // not supported @@ -1076,6 +1264,13 @@ virNWFilterRuleDefFixup(virNWFilterRuleD rule->p.ipHdrFilter.ipHdr.dataDstIPAddr); 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); + break; + case VIR_NWFILTER_RULE_PROTOCOL_ARP: case VIR_NWFILTER_RULE_PROTOCOL_NONE: break; @@ -1930,7 +2125,36 @@ virNWIPAddressFormat(virBufferPtr buf, n ipaddr->addr.ipv4Addr[2], ipaddr->addr.ipv4Addr[3]); } else { - virBufferAddLit(buf, "MISSING IPv6 ADDRESS FORMATTER"); + int i; + int dcshown = 0, in_dc = 0; + unsigned short n; + while (i < 8) { + n = (ipaddr->addr.ipv6Addr[i * 2 + 0] << 8) | + ipaddr->addr.ipv6Addr[i * 2 + 1]; + if (n == 0) { + if (!dcshown) { + in_dc = 1; + if (i == 0) + virBufferAddLit(buf, ":"); + dcshown = 1; + } + if (in_dc) { + i++; + continue; + } + } + if (in_dc) { + dcshown = 1; + virBufferAddLit(buf, ":"); + in_dc = 0; + } + i++; + virBufferVSprintf(buf, "%x", n); + if (i < 8) + virBufferAddLit(buf, ":"); + } + if (in_dc) + virBufferAddLit(buf, ":"); } } @@ -1999,6 +2223,7 @@ virNWFilterRuleDefDetailsFormat(virConne switch (att[i].datatype) { case DATATYPE_IPMASK: + case DATATYPE_IPV6MASK: // display all masks in CIDR format case DATATYPE_UINT8: storage_ptr = &item->u.u8; @@ -2011,6 +2236,7 @@ virNWFilterRuleDefDetailsFormat(virConne break; case DATATYPE_IPADDR: + case DATATYPE_IPV6ADDR: storage_ptr = &item->u.ipaddr; virNWIPAddressFormat(buf, (nwIPAddressPtr)storage_ptr); Index: libvirt-acl/src/conf/nwfilter_conf.h =================================================================== --- libvirt-acl.orig/src/conf/nwfilter_conf.h +++ libvirt-acl/src/conf/nwfilter_conf.h @@ -68,8 +68,10 @@ enum attrDatatype { DATATYPE_IPADDR = (1 << 4), DATATYPE_IPMASK = (1 << 5), DATATYPE_STRING = (1 << 6), + DATATYPE_IPV6ADDR = (1 << 7), + DATATYPE_IPV6MASK = (1 << 8), - DATATYPE_LAST = (1 << 7), + DATATYPE_LAST = (1 << 9), }; @@ -86,7 +88,7 @@ struct _nwIPAddress { int isIPv6; union { unsigned char ipv4Addr[4]; - /* unsigned char ipv6Addr[16]; future :-) */ + unsigned char ipv6Addr[16]; } addr; }; @@ -171,6 +173,15 @@ struct _ipHdrFilterDef { }; +typedef struct _ipv6HdrFilterDef ipv6HdrFilterDef; +typedef ipv6HdrFilterDef *ipv6HdrFilterDefPtr; +struct _ipv6HdrFilterDef { + ethHdrDataDef ethHdr; + ipHdrDataDef ipHdr; + portDataDef portData; +}; + + enum virNWFilterRuleActionType { VIR_NWFILTER_RULE_ACTION_DROP = 0, VIR_NWFILTER_RULE_ACTION_ACCEPT, @@ -198,6 +209,7 @@ enum virNWFilterRuleProtocolType { VIR_NWFILTER_RULE_PROTOCOL_MAC, VIR_NWFILTER_RULE_PROTOCOL_ARP, VIR_NWFILTER_RULE_PROTOCOL_IP, + VIR_NWFILTER_RULE_PROTOCOL_IPV6, }; enum virNWFilterEbtablesTableType { @@ -223,6 +235,7 @@ struct _virNWFilterRuleDef { ethHdrFilterDef ethHdrFilter; arpHdrFilterDef arpHdrFilter; ipHdrFilterDef ipHdrFilter; + ipv6HdrFilterDef ipv6HdrFilter; } p; int nvars; @@ -249,6 +262,7 @@ enum virNWFilterChainSuffixType { VIR_NWFILTER_CHAINSUFFIX_ROOT = 0, VIR_NWFILTER_CHAINSUFFIX_ARP, VIR_NWFILTER_CHAINSUFFIX_IPv4, + VIR_NWFILTER_CHAINSUFFIX_IPv6, VIR_NWFILTER_CHAINSUFFIX_LAST, }; Index: libvirt-acl/src/nwfilter/nwfilter_ebiptables_driver.c =================================================================== --- libvirt-acl.orig/src/nwfilter/nwfilter_ebiptables_driver.c +++ libvirt-acl/src/nwfilter/nwfilter_ebiptables_driver.c @@ -73,6 +73,7 @@ static const char *supported_protocols[] = { "ipv4", + "ipv6", "arp", NULL, }; @@ -117,6 +118,8 @@ printDataType(virConnectPtr conn, nwItemDescPtr item) { int done; + int i, pos, s; + if (printVar(conn, vars, buf, bufsize, item, &done)) return 1; @@ -136,6 +139,21 @@ printDataType(virConnectPtr conn, } break; + case DATATYPE_IPV6ADDR: + pos = 0; + for (i = 0; i < 16; i++) { + s = snprintf(&buf[pos], bufsize - pos, "%x%s", + (unsigned int)item->u.ipaddr.addr.ipv6Addr[i], + ((i & 1) && (i < 15)) ? ":" : "" ); + if (s >= bufsize - pos) { + virNWFilterReportError(conn, VIR_ERR_INVALID_NWFILTER, + _("Buffer too small for IPv6 address")); + return 1; + } + pos += s; + } + break; + case DATATYPE_MACADDR: if (bufsize < VIR_MAC_STRING_BUFLEN) { virNWFilterReportError(conn, VIR_ERR_INVALID_NWFILTER, @@ -155,6 +173,7 @@ printDataType(virConnectPtr conn, } break; + case DATATYPE_IPV6MASK: case DATATYPE_IPMASK: case DATATYPE_UINT8: if (snprintf(buf, bufsize, "%d", @@ -304,6 +323,7 @@ ebtablesCreateRuleInstance(virConnectPtr { char macaddr[VIR_MAC_STRING_BUFLEN], ipaddr[INET_ADDRSTRLEN], + ipv6addr[INET6_ADDRSTRLEN], number[20]; char chain[MAX_CHAINNAME_LENGTH]; virBuffer buf = VIR_BUFFER_INITIALIZER; @@ -587,6 +607,135 @@ ebtablesCreateRuleInstance(virConnectPtr } break; + case VIR_NWFILTER_RULE_PROTOCOL_IPV6: + virBufferVSprintf(&buf, + CMD_DEF_PRE EBTABLES_CMD " -t %s -%%c %s %%s", + EBTABLES_DEFAULT_TABLE, chain); + + if (ebtablesHandleEthHdr(conn, + &buf, + vars, + &rule->p.ipv6HdrFilter.ethHdr)) + goto err_exit; + + virBufferAddLit(&buf, + " -p ipv6"); + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr)) { + if (printDataType(conn, + vars, + ipv6addr, sizeof(ipv6addr), + &rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr)) + goto err_exit; + + virBufferVSprintf(&buf, + " --ip6-source %s %s", + ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPAddr), + ipv6addr); + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask)) { + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.ipHdr.dataSrcIPMask)) + goto err_exit; + virBufferVSprintf(&buf, + "/%s", + number); + } + } + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr)) { + + if (printDataType(conn, + vars, + ipv6addr, sizeof(ipv6addr), + &rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr)) + goto err_exit; + + virBufferVSprintf(&buf, + " --ip6-destination %s %s", + ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPAddr), + ipv6addr); + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask)) { + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.ipHdr.dataDstIPMask)) + goto err_exit; + virBufferVSprintf(&buf, + "/%s", + number); + } + } + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID)) { + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.ipHdr.dataProtocolID)) + goto err_exit; + + virBufferVSprintf(&buf, + " --ip6-protocol %s %s", + ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.ipHdr.dataProtocolID), + number); + } + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart)) { + + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.portData.dataSrcPortStart)) + goto err_exit; + + virBufferVSprintf(&buf, + " --ip6-source-port %s %s", + ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataSrcPortStart), + number); + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataSrcPortEnd)) { + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.portData.dataSrcPortEnd)) + goto err_exit; + + virBufferVSprintf(&buf, + ":%s", + number); + } + } + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortStart)) { + + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.portData.dataDstPortStart)) + goto err_exit; + + virBufferVSprintf(&buf, + " --ip6-destination-port %s %s", + ENTRY_GET_NEG_SIGN(&rule->p.ipv6HdrFilter.portData.dataDstPortStart), + number); + + if (HAS_ENTRY_ITEM(&rule->p.ipv6HdrFilter.portData.dataDstPortEnd)) { + if (printDataType(conn, + vars, + number, sizeof(number), + &rule->p.ipv6HdrFilter.portData.dataDstPortEnd)) + goto err_exit; + + virBufferVSprintf(&buf, + ":%s", + number); + } + } + break; + case VIR_NWFILTER_RULE_PROTOCOL_NONE: virBufferVSprintf(&buf, CMD_DEF_PRE EBTABLES_CMD " -t %s -%%c %s %%s", @@ -650,6 +799,7 @@ ebiptablesCreateRuleInstance(virConnectP case VIR_NWFILTER_RULE_PROTOCOL_MAC: case VIR_NWFILTER_RULE_PROTOCOL_ARP: case VIR_NWFILTER_RULE_PROTOCOL_NONE: + case VIR_NWFILTER_RULE_PROTOCOL_IPV6: if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_OUT || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { @@ -1230,6 +1380,11 @@ ebiptablesApplyRules(virConnectPtr conn, if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_IPv4)) ebtablesCreateTmpSubChain(conn, &buf, 0, ifname, "ipv4", 1); + if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_IPv6)) + ebtablesCreateTmpSubChain(conn, &buf, 1, ifname, "ipv6", 1); + if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_IPv6)) + ebtablesCreateTmpSubChain(conn, &buf, 0, ifname, "ipv6", 1); + // keep arp as last if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_ARP)) ebtablesCreateTmpSubChain(conn, &buf, 1, ifname, "arp", 1);