[libvirt] [libvirt PATCHv3 00/10] DHCP snooping support for libvirt

This series of patches adds DHCP snooping support to libvirt. This version saves leases on disk for restoration after a libvirtd restart and allows selection of different ip_learning methods by setting filter parameter "ip_learning" to one of "any" (existing IP learning code) "none" (static only addresses) or "DHCP" (DHCP Snooping). This code does not (yet) support passing lease information across a migration. A migrated guest requires a DHCP ACK (e.g., via ifdown/ifup on the guest) to send/receive traffic for DHCP-learned addresses after a migration. Differences from v2: added support for multiple static IP addresses using a comma-separated list. David L Stevens (10): support continue/return allow required ARP packets reverse sense of address matching make default chain policy "DROP" allow chain modification support addRules support variable value changing add DHCP snooping add leasefile support support multiple static IP addresses examples/xml/nwfilter/Makefile.am | 5 +- examples/xml/nwfilter/allow-arp.xml | 5 +- examples/xml/nwfilter/allow-arpip.xml | 3 + examples/xml/nwfilter/allow-arpmac.xml | 3 + examples/xml/nwfilter/clean-traffic.xml | 6 +- examples/xml/nwfilter/no-arp-spoofing.xml | 38 +- examples/xml/nwfilter/no-arpip-spoofing.xml | 10 + examples/xml/nwfilter/no-arpmac-spoofing.xml | 5 + examples/xml/nwfilter/no-ip-spoofing.xml | 9 +- examples/xml/nwfilter/no-mac-spoofing.xml | 10 +- examples/xml/nwfilter/no-other-l2-traffic.xml | 13 +- examples/xml/nwfilter/no-other-rarp-traffic.xml | 3 - examples/xml/nwfilter/qemu-announce-self.xml | 1 - src/Makefile.am | 2 + src/conf/nwfilter_conf.c | 12 +- src/conf/nwfilter_conf.h | 16 +- src/nwfilter/nwfilter_dhcpsnoop.c | 938 +++++++++++++++++++++++ src/nwfilter/nwfilter_dhcpsnoop.h | 36 + src/nwfilter/nwfilter_driver.c | 5 + src/nwfilter/nwfilter_ebiptables_driver.c | 225 +++++-- src/nwfilter/nwfilter_gentech_driver.c | 225 +++++- src/nwfilter/nwfilter_gentech_driver.h | 11 + 22 files changed, 1445 insertions(+), 136 deletions(-) create mode 100644 examples/xml/nwfilter/allow-arpip.xml create mode 100644 examples/xml/nwfilter/allow-arpmac.xml create mode 100644 examples/xml/nwfilter/no-arpip-spoofing.xml create mode 100644 examples/xml/nwfilter/no-arpmac-spoofing.xml delete mode 100644 examples/xml/nwfilter/no-other-rarp-traffic.xml create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h -- 1.7.6.4

This patch adds support for "continue" and "return" actions in filter rules. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/conf/nwfilter_conf.c | 8 ++++++-- src/conf/nwfilter_conf.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/conf/nwfilter_conf.c b/src/conf/nwfilter_conf.c index 08ede48..e0c2fb6 100644 --- a/src/conf/nwfilter_conf.c +++ b/src/conf/nwfilter_conf.c @@ -55,12 +55,16 @@ VIR_ENUM_IMPL(virNWFilterRuleAction, VIR_NWFILTER_RULE_ACTION_LAST, "drop", "accept", - "reject"); + "reject", + "return", + "continue"); VIR_ENUM_IMPL(virNWFilterJumpTarget, VIR_NWFILTER_RULE_ACTION_LAST, "DROP", "ACCEPT", - "REJECT"); + "REJECT", + "RETURN", + "CONTINUE"); VIR_ENUM_IMPL(virNWFilterRuleDirection, VIR_NWFILTER_RULE_DIRECTION_LAST, "in", diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 5306403..c96851a 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -299,6 +299,8 @@ enum virNWFilterRuleActionType { VIR_NWFILTER_RULE_ACTION_DROP = 0, VIR_NWFILTER_RULE_ACTION_ACCEPT, VIR_NWFILTER_RULE_ACTION_REJECT, + VIR_NWFILTER_RULE_ACTION_RETURN, + VIR_NWFILTER_RULE_ACTION_CONTINUE, VIR_NWFILTER_RULE_ACTION_LAST, }; -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds support for "continue" and "return" actions in filter rules.
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- src/conf/nwfilter_conf.c | 8 ++++++-- src/conf/nwfilter_conf.h | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/conf/nwfilter_conf.c b/src/conf/nwfilter_conf.c index 08ede48..e0c2fb6 100644 --- a/src/conf/nwfilter_conf.c +++ b/src/conf/nwfilter_conf.c @@ -55,12 +55,16 @@ VIR_ENUM_IMPL(virNWFilterRuleAction, VIR_NWFILTER_RULE_ACTION_LAST, "drop", "accept", - "reject"); + "reject", + "return", + "continue");
VIR_ENUM_IMPL(virNWFilterJumpTarget, VIR_NWFILTER_RULE_ACTION_LAST, "DROP", "ACCEPT", - "REJECT"); + "REJECT", + "RETURN", + "CONTINUE");
VIR_ENUM_IMPL(virNWFilterRuleDirection, VIR_NWFILTER_RULE_DIRECTION_LAST, "in", diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 5306403..c96851a 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -299,6 +299,8 @@ enum virNWFilterRuleActionType { VIR_NWFILTER_RULE_ACTION_DROP = 0, VIR_NWFILTER_RULE_ACTION_ACCEPT, VIR_NWFILTER_RULE_ACTION_REJECT, + VIR_NWFILTER_RULE_ACTION_RETURN, + VIR_NWFILTER_RULE_ACTION_CONTINUE,
VIR_NWFILTER_RULE_ACTION_LAST, }; I'd ACK this. I'd like to also have test cases for the libvirt-tck.
Stefan

The ARP protocol requires processing of packets that may not be explicitly addressed to a host and only defines request and reply. This patch removes the filtering of ARP requests not explicitly addressed to a VM to allow for proper ARP cache updates for entries based on any traffic and removes the unnecessary check for arpop of request or reply. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- examples/xml/nwfilter/no-arp-spoofing.xml | 23 ++--------------------- 1 files changed, 2 insertions(+), 21 deletions(-) diff --git a/examples/xml/nwfilter/no-arp-spoofing.xml b/examples/xml/nwfilter/no-arp-spoofing.xml index 96c58c1..3c83acd 100644 --- a/examples/xml/nwfilter/no-arp-spoofing.xml +++ b/examples/xml/nwfilter/no-arp-spoofing.xml @@ -12,25 +12,6 @@ <rule action='drop' direction='out' priority='400' > <arp match='no' arpsrcipaddr='$IP' /> </rule> - <!-- allow gratuitous arp --> - <rule action='accept' direction='in' priority='425'> - <arp gratuitous='true'/> - </rule> - <!-- drop if ipaddr or macaddr does not belong to guest --> - <rule action='drop' direction='in' priority='450' > - <arp match='no' arpdstmacaddr='$MAC'/> - <arp opcode='reply'/> - </rule> - <rule action='drop' direction='in' priority='500' > - <arp match='no' arpdstipaddr='$IP' /> - </rule> - <!-- accept only request or reply packets --> - <rule action='accept' direction='inout' priority='600' > - <arp opcode='request'/> - </rule> - <rule action='accept' direction='inout' priority='650' > - <arp opcode='reply'/> - </rule> - <!-- drop everything else --> - <rule action='drop' direction='inout' priority='1000' /> + <!-- allow everything else --> + <rule action='accept' direction='in' priority='425' /> </filter> -- 1.7.6.4

This patch changes rules of the form: if ! addr drop accept to: if addr return ... drop The patch adds a "mac" chain to do a mac address list and separates the "arp" chain into separate "arpmac" and "arpip" chains that can check multiple MAC or IP addresses in any combination. This patch itself does not support multiple addresses via the MAC and IP variables, but only changes the form of the rules to allow multiple addresses in the future. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- examples/xml/nwfilter/Makefile.am | 4 ++ examples/xml/nwfilter/allow-arp.xml | 5 ++- examples/xml/nwfilter/allow-arpip.xml | 3 ++ examples/xml/nwfilter/allow-arpmac.xml | 3 ++ examples/xml/nwfilter/clean-traffic.xml | 6 ++-- examples/xml/nwfilter/no-arp-spoofing.xml | 19 ++-------- examples/xml/nwfilter/no-arpip-spoofing.xml | 12 ++++++ examples/xml/nwfilter/no-arpmac-spoofing.xml | 7 ++++ examples/xml/nwfilter/no-ip-spoofing.xml | 6 ++- examples/xml/nwfilter/no-mac-spoofing.xml | 12 ++++-- examples/xml/nwfilter/no-other-l2-traffic.xml | 13 +++++-- src/conf/nwfilter_conf.c | 4 ++- src/conf/nwfilter_conf.h | 4 ++- src/nwfilter/nwfilter_ebiptables_driver.c | 48 +++++++++++++++++------- 14 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 examples/xml/nwfilter/allow-arpip.xml create mode 100644 examples/xml/nwfilter/allow-arpmac.xml create mode 100644 examples/xml/nwfilter/no-arpip-spoofing.xml create mode 100644 examples/xml/nwfilter/no-arpmac-spoofing.xml diff --git a/examples/xml/nwfilter/Makefile.am b/examples/xml/nwfilter/Makefile.am index 23fd753..84aaa3c 100644 --- a/examples/xml/nwfilter/Makefile.am +++ b/examples/xml/nwfilter/Makefile.am @@ -3,12 +3,16 @@ FILTERS = \ allow-arp.xml \ + allow-arpip.xml \ + allow-arpmac.xml \ allow-dhcp-server.xml \ allow-dhcp.xml \ allow-incoming-ipv4.xml \ allow-ipv4.xml \ clean-traffic.xml \ no-arp-spoofing.xml \ + no-arpmac-spoofing.xml \ + no-arpip-spoofing.xml \ no-ip-multicast.xml \ no-ip-spoofing.xml \ no-mac-broadcast.xml \ diff --git a/examples/xml/nwfilter/allow-arp.xml b/examples/xml/nwfilter/allow-arp.xml index 63a92b2..6271ae4 100644 --- a/examples/xml/nwfilter/allow-arp.xml +++ b/examples/xml/nwfilter/allow-arp.xml @@ -1,3 +1,4 @@ -<filter name='allow-arp' chain='arp'> - <rule direction='inout' action='accept'/> +<filter name='allow-arp' chain='arpmac'> + <filterref filter='allow-arpmac.xml'/> + <filterref filter='allow-arpip.xml'/> </filter> diff --git a/examples/xml/nwfilter/allow-arpip.xml b/examples/xml/nwfilter/allow-arpip.xml new file mode 100644 index 0000000..6ddb6fe --- /dev/null +++ b/examples/xml/nwfilter/allow-arpip.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpip' chain='arpip'> + <rule direction='inout' action='accept'/> +</filter> diff --git a/examples/xml/nwfilter/allow-arpmac.xml b/examples/xml/nwfilter/allow-arpmac.xml new file mode 100644 index 0000000..54f6714 --- /dev/null +++ b/examples/xml/nwfilter/allow-arpmac.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpmac' chain='arpmac'> + <rule direction='inout' action='accept'/> +</filter> diff --git a/examples/xml/nwfilter/clean-traffic.xml b/examples/xml/nwfilter/clean-traffic.xml index 40f0ecb..9cee799 100644 --- a/examples/xml/nwfilter/clean-traffic.xml +++ b/examples/xml/nwfilter/clean-traffic.xml @@ -11,10 +11,10 @@ <!-- preventing ARP spoofing/poisoning --> <filterref filter='no-arp-spoofing'/> - <!-- preventing any other traffic than IPv4 and ARP --> - <filterref filter='no-other-l2-traffic'/> - <!-- allow qemu to send a self-announce upon migration end --> <filterref filter='qemu-announce-self'/> + <!-- preventing any other traffic than IPv4 and ARP --> + <filterref filter='no-other-l2-traffic'/> + </filter> diff --git a/examples/xml/nwfilter/no-arp-spoofing.xml b/examples/xml/nwfilter/no-arp-spoofing.xml index 3c83acd..1979b20 100644 --- a/examples/xml/nwfilter/no-arp-spoofing.xml +++ b/examples/xml/nwfilter/no-arp-spoofing.xml @@ -1,17 +1,4 @@ -<filter name='no-arp-spoofing' chain='arp'> - <uuid>f88f1932-debf-4aa1-9fbe-f10d3aa4bc95</uuid> - <rule action='drop' direction='out' priority='300' > - <mac match='no' srcmacaddr='$MAC'/> - </rule> - - <!-- no arp spoofing --> - <!-- drop if ipaddr or macaddr does not belong to guest --> - <rule action='drop' direction='out' priority='350' > - <arp match='no' arpsrcmacaddr='$MAC'/> - </rule> - <rule action='drop' direction='out' priority='400' > - <arp match='no' arpsrcipaddr='$IP' /> - </rule> - <!-- allow everything else --> - <rule action='accept' direction='in' priority='425' /> +<filter name='no-arp-spoofing'> + <filterref filter='no-arpmac-spoofing' /> + <filterref filter='no-arpip-spoofing' /> </filter> diff --git a/examples/xml/nwfilter/no-arpip-spoofing.xml b/examples/xml/nwfilter/no-arpip-spoofing.xml new file mode 100644 index 0000000..ee42d40 --- /dev/null +++ b/examples/xml/nwfilter/no-arpip-spoofing.xml @@ -0,0 +1,12 @@ +<filter name='no-arpip-spoofing' chain='arpip'> + <!-- no arp spoofing --> + <!-- drop if ipaddr does not belong to guest --> + <rule action='return' direction='out' priority='400' > + <arp match='yes' arpsrcipaddr='$IP' /> + </rule> + <rule action='return' direction='out' priority='410' > + <arp match='yes' arpsrcipaddr='0.0.0.0' /> + </rule> + <!-- drop everything else --> + <rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-arpmac-spoofing.xml b/examples/xml/nwfilter/no-arpmac-spoofing.xml new file mode 100644 index 0000000..90499d3 --- /dev/null +++ b/examples/xml/nwfilter/no-arpmac-spoofing.xml @@ -0,0 +1,7 @@ +<filter name='no-arpmac-spoofing' chain='arpmac'> + <rule action='return' direction='out' priority='350' > + <arp match='yes' arpsrcmacaddr='$MAC'/> + </rule> + <!-- drop everything else --> + <rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index b8c94c8..84e8a5e 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -1,7 +1,9 @@ <filter name='no-ip-spoofing' chain='ipv4'> <!-- drop if srcipaddr is not the IP address of the guest --> - <rule action='drop' direction='out'> - <ip match='no' srcipaddr='$IP' /> + <rule action='return' direction='out'> + <ip match='yes' srcipaddr='$IP' /> </rule> + <!-- drop any that don't match the source IP list --> + <rule action='drop' direction='out' /> </filter> diff --git a/examples/xml/nwfilter/no-mac-spoofing.xml b/examples/xml/nwfilter/no-mac-spoofing.xml index f210623..aee56c7 100644 --- a/examples/xml/nwfilter/no-mac-spoofing.xml +++ b/examples/xml/nwfilter/no-mac-spoofing.xml @@ -1,5 +1,9 @@ -<filter name='no-mac-spoofing' chain='ipv4'> - <rule action='drop' direction='out' priority='10'> - <mac match='no' srcmacaddr='$MAC' /> - </rule> +<filter name='no-mac-spoofing' chain='mac'> + <!-- no mac spoofing --> + <!-- drop if macaddr does not belong to guest --> + <rule action='return' direction='out' priority='350' > + <mac match='yes' srcmacaddr='$MAC'/> + </rule> + <!-- drop everything else --> + <rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-other-l2-traffic.xml b/examples/xml/nwfilter/no-other-l2-traffic.xml index 8bad86e..0501b1a 100644 --- a/examples/xml/nwfilter/no-other-l2-traffic.xml +++ b/examples/xml/nwfilter/no-other-l2-traffic.xml @@ -1,7 +1,12 @@ -<filter name='no-other-l2-traffic'> +<filter name='no-other-l2-traffic' chain='root'> - <!-- drop all other l2 traffic than for which rules have been - written for; i.e., drop all other than arp and ipv4 traffic --> - <rule action='drop' direction='inout' priority='1000'/> + <!-- drop all other than arp and ipv4 traffic --> + <rule action='accept' direction='inout'> + <mac protocolid='0x800' /> + </rule> + <rule action='accept' direction='inout'> + <mac protocolid='0x806' /> + </rule> + <rule action='drop' direction='inout' priority='1000' /> </filter> diff --git a/src/conf/nwfilter_conf.c b/src/conf/nwfilter_conf.c index e0c2fb6..31199cb 100644 --- a/src/conf/nwfilter_conf.c +++ b/src/conf/nwfilter_conf.c @@ -82,7 +82,9 @@ VIR_ENUM_IMPL(virNWFilterEbtablesTable, VIR_NWFILTER_EBTABLES_TABLE_LAST, VIR_ENUM_IMPL(virNWFilterChainSuffix, VIR_NWFILTER_CHAINSUFFIX_LAST, "root", - "arp", + "mac", + "arpmac", + "arpip", "rarp", "ipv4", "ipv6"); diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index c96851a..17e954e 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -428,7 +428,9 @@ struct _virNWFilterEntry { enum virNWFilterChainSuffixType { VIR_NWFILTER_CHAINSUFFIX_ROOT = 0, - VIR_NWFILTER_CHAINSUFFIX_ARP, + VIR_NWFILTER_CHAINSUFFIX_MAC, + VIR_NWFILTER_CHAINSUFFIX_ARPMAC, + VIR_NWFILTER_CHAINSUFFIX_ARPIP, VIR_NWFILTER_CHAINSUFFIX_RARP, VIR_NWFILTER_CHAINSUFFIX_IPv4, VIR_NWFILTER_CHAINSUFFIX_IPv6, diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index f87cfa1..3c6fca7 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -130,20 +130,24 @@ struct ushort_map { enum l3_proto_idx { - L3_PROTO_IPV4_IDX = 0, - L3_PROTO_IPV6_IDX, - L3_PROTO_ARP_IDX, + L3_PROTO_MAC_IDX = 0, + L3_PROTO_ARPMAC_IDX, + L3_PROTO_ARPIP_IDX, L3_PROTO_RARP_IDX, + L3_PROTO_IPV4_IDX, + L3_PROTO_IPV6_IDX, L3_PROTO_LAST_IDX }; #define USHORTMAP_ENTRY_IDX(IDX, ATT, VAL) [IDX] = { .attr = ATT, .val = VAL } 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(L3_PROTO_MAC_IDX, 0 , "mac"), + 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_ARPMAC_IDX,ETHERTYPE_ARP , "arpmac"), + USHORTMAP_ENTRY_IDX(L3_PROTO_ARPIP_IDX, ETHERTYPE_ARP , "arpip"), + USHORTMAP_ENTRY_IDX(L3_PROTO_RARP_IDX, ETHERTYPE_REVARP, "rarp"), USHORTMAP_ENTRY_IDX(L3_PROTO_LAST_IDX, 0 , NULL), }; @@ -1947,7 +1951,7 @@ ebtablesCreateRuleInstance(char chainPrefix, virBufferAsprintf(&buf, " -p 0x%x", (rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ARP) - ? l3_protocols[L3_PROTO_ARP_IDX].attr + ? l3_protocols[L3_PROTO_ARPMAC_IDX].attr : l3_protocols[L3_PROTO_RARP_IDX].attr); if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataHWType)) { @@ -2775,15 +2779,22 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, char rootchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_OUT_TEMP; + char protostr[16]; PRINT_ROOT_CHAIN(rootchain, chainPrefix, ifname); PRINT_CHAIN(chain, chainPrefix, ifname, l3_protocols[protoidx].val); + if (l3_protocols[protoidx].attr) + snprintf(protostr, sizeof(protostr), "-p 0x%04x ", + l3_protocols[protoidx].attr); + else + protostr[0] = '\0'; + virBufferAsprintf(buf, CMD_DEF("%s -t %s -N %s") CMD_SEPARATOR CMD_EXEC "%s" - CMD_DEF("%s -t %s -A %s -p 0x%x -j %s") CMD_SEPARATOR + CMD_DEF("%s -t %s -A %s %s -j %s") CMD_SEPARATOR CMD_EXEC "%s", @@ -2792,7 +2803,7 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, CMD_STOPONERR(stopOnError), ebtables_cmd_path, EBTABLES_DEFAULT_TABLE, - rootchain, l3_protocols[protoidx].attr, chain, + rootchain, protostr, chain, CMD_STOPONERR(stopOnError)); @@ -3365,6 +3376,11 @@ ebiptablesApplyNewRules(virConnectPtr conn ATTRIBUTE_UNUSED, if (chains_out != 0) ebtablesCreateTmpRootChain(&buf, 0, ifname, 1); + if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_MAC)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_MAC_IDX, 1); + if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_MAC)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_MAC_IDX, 1); + if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_IPv4)) ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_IPV4_IDX, 1); if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_IPv4)) @@ -3376,10 +3392,14 @@ ebiptablesApplyNewRules(virConnectPtr conn ATTRIBUTE_UNUSED, ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_IPV6_IDX, 1); /* keep arp,rarp as last */ - if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_ARP)) - ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARP_IDX, 1); - if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_ARP)) - ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARP_IDX, 1); + if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_ARPMAC)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARPMAC_IDX, 1); + if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_ARPIP)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARPIP_IDX, 1); + if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_ARPMAC)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARPMAC_IDX, 1); + if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_ARPIP)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARPIP_IDX, 1); if (chains_in & (1 << VIR_NWFILTER_CHAINSUFFIX_RARP)) ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_RARP_IDX, 1); if (chains_out & (1 << VIR_NWFILTER_CHAINSUFFIX_RARP)) -- 1.7.6.4

This patch changes rules of the form:
if ! addr drop accept to: if addr return ... drop
The patch adds a "mac" chain to do a mac address list and separates the "arp" chain into separate "arpmac" and "arpip" chains that can check multiple MAC or IP addresses in any combination. This patch itself does not support multiple addresses via the MAC and IP variables, but only changes the form of the rules to allow multiple addresses in the future.
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- examples/xml/nwfilter/Makefile.am | 4 ++ examples/xml/nwfilter/allow-arp.xml | 5 ++- examples/xml/nwfilter/allow-arpip.xml | 3 ++ examples/xml/nwfilter/allow-arpmac.xml | 3 ++ examples/xml/nwfilter/clean-traffic.xml | 6 ++-- examples/xml/nwfilter/no-arp-spoofing.xml | 19 ++-------- examples/xml/nwfilter/no-arpip-spoofing.xml | 12 ++++++ examples/xml/nwfilter/no-arpmac-spoofing.xml | 7 ++++ examples/xml/nwfilter/no-ip-spoofing.xml | 6 ++- examples/xml/nwfilter/no-mac-spoofing.xml | 12 ++++-- examples/xml/nwfilter/no-other-l2-traffic.xml | 13 +++++-- src/conf/nwfilter_conf.c | 4 ++- src/conf/nwfilter_conf.h | 4 ++- src/nwfilter/nwfilter_ebiptables_driver.c | 48 +++++++++++++++++------- 14 files changed, 99 insertions(+), 47 deletions(-) create mode 100644 examples/xml/nwfilter/allow-arpip.xml create mode 100644 examples/xml/nwfilter/allow-arpmac.xml create mode 100644 examples/xml/nwfilter/no-arpip-spoofing.xml create mode 100644 examples/xml/nwfilter/no-arpmac-spoofing.xml
diff --git a/examples/xml/nwfilter/Makefile.am b/examples/xml/nwfilter/Makefile.am index 23fd753..84aaa3c 100644 --- a/examples/xml/nwfilter/Makefile.am +++ b/examples/xml/nwfilter/Makefile.am @@ -3,12 +3,16 @@
FILTERS = \ allow-arp.xml \ + allow-arpip.xml \ + allow-arpmac.xml \ allow-dhcp-server.xml \ allow-dhcp.xml \ allow-incoming-ipv4.xml \ allow-ipv4.xml \ clean-traffic.xml \ no-arp-spoofing.xml \ + no-arpmac-spoofing.xml \ + no-arpip-spoofing.xml \ no-ip-multicast.xml \ no-ip-spoofing.xml \ no-mac-broadcast.xml \ diff --git a/examples/xml/nwfilter/allow-arp.xml b/examples/xml/nwfilter/allow-arp.xml index 63a92b2..6271ae4 100644 --- a/examples/xml/nwfilter/allow-arp.xml +++ b/examples/xml/nwfilter/allow-arp.xml @@ -1,3 +1,4 @@ -<filter name='allow-arp' chain='arp'> -<rule direction='inout' action='accept'/> +<filter name='allow-arp' chain='arpmac'> +<filterref filter='allow-arpmac.xml'/> +<filterref filter='allow-arpip.xml'/> </filter> diff --git a/examples/xml/nwfilter/allow-arpip.xml b/examples/xml/nwfilter/allow-arpip.xml new file mode 100644 index 0000000..6ddb6fe --- /dev/null +++ b/examples/xml/nwfilter/allow-arpip.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpip' chain='arpip'> +<rule direction='inout' action='accept'/> +</filter> diff --git a/examples/xml/nwfilter/allow-arpmac.xml b/examples/xml/nwfilter/allow-arpmac.xml new file mode 100644 index 0000000..54f6714 --- /dev/null +++ b/examples/xml/nwfilter/allow-arpmac.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpmac' chain='arpmac'> +<rule direction='inout' action='accept'/> +</filter> diff --git a/examples/xml/nwfilter/clean-traffic.xml b/examples/xml/nwfilter/clean-traffic.xml index 40f0ecb..9cee799 100644 --- a/examples/xml/nwfilter/clean-traffic.xml +++ b/examples/xml/nwfilter/clean-traffic.xml @@ -11,10 +11,10 @@ <!-- preventing ARP spoofing/poisoning --> <filterref filter='no-arp-spoofing'/>
-<!-- preventing any other traffic than IPv4 and ARP --> -<filterref filter='no-other-l2-traffic'/> - <!-- allow qemu to send a self-announce upon migration end --> <filterref filter='qemu-announce-self'/>
+<!-- preventing any other traffic than IPv4 and ARP --> +<filterref filter='no-other-l2-traffic'/> + </filter> diff --git a/examples/xml/nwfilter/no-arp-spoofing.xml b/examples/xml/nwfilter/no-arp-spoofing.xml index 3c83acd..1979b20 100644 --- a/examples/xml/nwfilter/no-arp-spoofing.xml +++ b/examples/xml/nwfilter/no-arp-spoofing.xml @@ -1,17 +1,4 @@ -<filter name='no-arp-spoofing' chain='arp'> -<uuid>f88f1932-debf-4aa1-9fbe-f10d3aa4bc95</uuid> -<rule action='drop' direction='out' priority='300'> -<mac match='no' srcmacaddr='$MAC'/> -</rule> - -<!-- no arp spoofing --> -<!-- drop if ipaddr or macaddr does not belong to guest --> -<rule action='drop' direction='out' priority='350'> -<arp match='no' arpsrcmacaddr='$MAC'/> -</rule> -<rule action='drop' direction='out' priority='400'> -<arp match='no' arpsrcipaddr='$IP' /> -</rule> -<!-- allow everything else --> -<rule action='accept' direction='in' priority='425' /> +<filter name='no-arp-spoofing'> +<filterref filter='no-arpmac-spoofing' /> +<filterref filter='no-arpip-spoofing' /> </filter> diff --git a/examples/xml/nwfilter/no-arpip-spoofing.xml b/examples/xml/nwfilter/no-arpip-spoofing.xml new file mode 100644 index 0000000..ee42d40 --- /dev/null +++ b/examples/xml/nwfilter/no-arpip-spoofing.xml @@ -0,0 +1,12 @@ +<filter name='no-arpip-spoofing' chain='arpip'> +<!-- no arp spoofing --> +<!-- drop if ipaddr does not belong to guest --> +<rule action='return' direction='out' priority='400'> +<arp match='yes' arpsrcipaddr='$IP' /> +</rule> +<rule action='return' direction='out' priority='410'> +<arp match='yes' arpsrcipaddr='0.0.0.0' /> +</rule> +<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-arpmac-spoofing.xml b/examples/xml/nwfilter/no-arpmac-spoofing.xml new file mode 100644 index 0000000..90499d3 --- /dev/null +++ b/examples/xml/nwfilter/no-arpmac-spoofing.xml @@ -0,0 +1,7 @@ +<filter name='no-arpmac-spoofing' chain='arpmac'> +<rule action='return' direction='out' priority='350'> +<arp match='yes' arpsrcmacaddr='$MAC'/> +</rule> +<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index b8c94c8..84e8a5e 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -1,7 +1,9 @@ <filter name='no-ip-spoofing' chain='ipv4'>
<!-- drop if srcipaddr is not the IP address of the guest --> -<rule action='drop' direction='out'> -<ip match='no' srcipaddr='$IP' /> +<rule action='return' direction='out'> +<ip match='yes' srcipaddr='$IP' /> </rule> +<!-- drop any that don't match the source IP list --> +<rule action='drop' direction='out' /> </filter> diff --git a/examples/xml/nwfilter/no-mac-spoofing.xml b/examples/xml/nwfilter/no-mac-spoofing.xml index f210623..aee56c7 100644 --- a/examples/xml/nwfilter/no-mac-spoofing.xml +++ b/examples/xml/nwfilter/no-mac-spoofing.xml @@ -1,5 +1,9 @@ -<filter name='no-mac-spoofing' chain='ipv4'> -<rule action='drop' direction='out' priority='10'> -<mac match='no' srcmacaddr='$MAC' /> -</rule> +<filter name='no-mac-spoofing' chain='mac'> +<!-- no mac spoofing --> +<!-- drop if macaddr does not belong to guest --> +<rule action='return' direction='out' priority='350'> +<mac match='yes' srcmacaddr='$MAC'/> +</rule> +<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-other-l2-traffic.xml b/examples/xml/nwfilter/no-other-l2-traffic.xml index 8bad86e..0501b1a 100644 --- a/examples/xml/nwfilter/no-other-l2-traffic.xml +++ b/examples/xml/nwfilter/no-other-l2-traffic.xml @@ -1,7 +1,12 @@ -<filter name='no-other-l2-traffic'> +<filter name='no-other-l2-traffic' chain='root'>
-<!-- drop all other l2 traffic than for which rules have been - written for; i.e., drop all other than arp and ipv4 traffic --> -<rule action='drop' direction='inout' priority='1000'/> +<!-- drop all other than arp and ipv4 traffic --> +<rule action='accept' direction='inout'> +<mac protocolid='0x800' /> +</rule> +<rule action='accept' direction='inout'> +<mac protocolid='0x806' /> +</rule> The hex numbers can be replaced with their string representation. Afaics
On 10/12/2011 03:50 PM, David L Stevens wrote: the parser/formatter consults the macProtoMap in nwfilter_conf.c and translates it into the protocol number for safer (internal) use.
+<rule action='drop' direction='inout' priority='1000' />
</filter> diff --git a/src/conf/nwfilter_conf.c b/src/conf/nwfilter_conf.c index e0c2fb6..31199cb 100644 --- a/src/conf/nwfilter_conf.c +++ b/src/conf/nwfilter_conf.c @@ -82,7 +82,9 @@ VIR_ENUM_IMPL(virNWFilterEbtablesTable, VIR_NWFILTER_EBTABLES_TABLE_LAST,
VIR_ENUM_IMPL(virNWFilterChainSuffix, VIR_NWFILTER_CHAINSUFFIX_LAST, "root", - "arp", + "mac", + "arpmac", + "arpip", "rarp", "ipv4", "ipv6"); diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index c96851a..17e954e 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -428,7 +428,9 @@ struct _virNWFilterEntry {
enum virNWFilterChainSuffixType { VIR_NWFILTER_CHAINSUFFIX_ROOT = 0, - VIR_NWFILTER_CHAINSUFFIX_ARP, + VIR_NWFILTER_CHAINSUFFIX_MAC, + VIR_NWFILTER_CHAINSUFFIX_ARPMAC, + VIR_NWFILTER_CHAINSUFFIX_ARPIP, VIR_NWFILTER_CHAINSUFFIX_RARP, VIR_NWFILTER_CHAINSUFFIX_IPv4, VIR_NWFILTER_CHAINSUFFIX_IPv6, diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index f87cfa1..3c6fca7 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -130,20 +130,24 @@ struct ushort_map {
enum l3_proto_idx { - L3_PROTO_IPV4_IDX = 0, - L3_PROTO_IPV6_IDX, - L3_PROTO_ARP_IDX, + L3_PROTO_MAC_IDX = 0, + L3_PROTO_ARPMAC_IDX, + L3_PROTO_ARPIP_IDX, L3_PROTO_RARP_IDX, + L3_PROTO_IPV4_IDX, + L3_PROTO_IPV6_IDX, L3_PROTO_LAST_IDX };
The ordering here was meant to create the chains used to evaluating the ARP protocol after IPv4 for faster processing of IPv4, which would be the bulk and efficiency would matter most for IPv4. MAC can be first. Also don't just remove the ARP table. It can stay, even if example filters were to be changed. Others may have written filters accessing the arp chain and still need it.
#define USHORTMAP_ENTRY_IDX(IDX, ATT, VAL) [IDX] = { .attr = ATT, .val = VAL }
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(L3_PROTO_MAC_IDX, 0 , "mac"), + 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_ARPMAC_IDX,ETHERTYPE_ARP , "arpmac"), + USHORTMAP_ENTRY_IDX(L3_PROTO_ARPIP_IDX, ETHERTYPE_ARP , "arpip"), + USHORTMAP_ENTRY_IDX(L3_PROTO_RARP_IDX, ETHERTYPE_REVARP, "rarp"), USHORTMAP_ENTRY_IDX(L3_PROTO_LAST_IDX, 0 , NULL), };
@@ -1947,7 +1951,7 @@ ebtablesCreateRuleInstance(char chainPrefix,
virBufferAsprintf(&buf, " -p 0x%x", (rule->prtclType == VIR_NWFILTER_RULE_PROTOCOL_ARP) - ? l3_protocols[L3_PROTO_ARP_IDX].attr + ? l3_protocols[L3_PROTO_ARPMAC_IDX].attr : l3_protocols[L3_PROTO_RARP_IDX].attr);
if (HAS_ENTRY_ITEM(&rule->p.arpHdrFilter.dataHWType)) { @@ -2775,15 +2779,22 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, char rootchain[MAX_CHAINNAME_LENGTH], chain[MAX_CHAINNAME_LENGTH]; char chainPrefix = (incoming) ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_OUT_TEMP; + char protostr[16];
PRINT_ROOT_CHAIN(rootchain, chainPrefix, ifname); PRINT_CHAIN(chain, chainPrefix, ifname, l3_protocols[protoidx].val);
+ if (l3_protocols[protoidx].attr) + snprintf(protostr, sizeof(protostr), "-p 0x%04x ", + l3_protocols[protoidx].attr); + else + protostr[0] = '\0'; + virBufferAsprintf(buf, CMD_DEF("%s -t %s -N %s") CMD_SEPARATOR CMD_EXEC "%s" - CMD_DEF("%s -t %s -A %s -p 0x%x -j %s") CMD_SEPARATOR + CMD_DEF("%s -t %s -A %s %s -j %s") CMD_SEPARATOR CMD_EXEC "%s",
@@ -2792,7 +2803,7 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, CMD_STOPONERR(stopOnError),
ebtables_cmd_path, EBTABLES_DEFAULT_TABLE, - rootchain, l3_protocols[protoidx].attr, chain, + rootchain, protostr, chain,
CMD_STOPONERR(stopOnError));
@@ -3365,6 +3376,11 @@ ebiptablesApplyNewRules(virConnectPtr conn ATTRIBUTE_UNUSED, if (chains_out != 0) ebtablesCreateTmpRootChain(&buf, 0, ifname, 1);
+ if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_MAC)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_MAC_IDX, 1); + if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_MAC)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_MAC_IDX, 1); + if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_IPv4)) ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_IPV4_IDX, 1); if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_IPv4)) @@ -3376,10 +3392,14 @@ ebiptablesApplyNewRules(virConnectPtr conn ATTRIBUTE_UNUSED, ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_IPV6_IDX, 1);
/* keep arp,rarp as last */ - if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_ARP)) - ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARP_IDX, 1); - if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_ARP)) - ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARP_IDX, 1); + if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_ARPMAC)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARPMAC_IDX, 1); + if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_ARPIP)) + ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_ARPIP_IDX, 1); + if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_ARPMAC)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARPMAC_IDX, 1); + if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_ARPIP)) + ebtablesCreateTmpSubChain(&buf, 0, ifname, L3_PROTO_ARPIP_IDX, 1); if (chains_in& (1<< VIR_NWFILTER_CHAINSUFFIX_RARP)) ebtablesCreateTmpSubChain(&buf, 1, ifname, L3_PROTO_RARP_IDX, 1); if (chains_out& (1<< VIR_NWFILTER_CHAINSUFFIX_RARP))
I believe this and the preceding hunk would be necessary with the patches I posted today. these two could go.

On 10/12/2011 03:50 PM, David L Stevens wrote:
[...]
diff --git a/examples/xml/nwfilter/Makefile.am b/examples/xml/nwfilter/Makefile.am index 23fd753..84aaa3c 100644 --- a/examples/xml/nwfilter/Makefile.am +++ b/examples/xml/nwfilter/Makefile.am @@ -3,12 +3,16 @@
FILTERS = \ allow-arp.xml \ + allow-arpip.xml \ + allow-arpmac.xml \ allow-dhcp-server.xml \ allow-dhcp.xml \ allow-incoming-ipv4.xml \ allow-ipv4.xml \ clean-traffic.xml \ no-arp-spoofing.xml \ + no-arpmac-spoofing.xml \ + no-arpip-spoofing.xml \ no-ip-multicast.xml \ no-ip-spoofing.xml \ no-mac-broadcast.xml \ diff --git a/examples/xml/nwfilter/allow-arp.xml b/examples/xml/nwfilter/allow-arp.xml index 63a92b2..6271ae4 100644 --- a/examples/xml/nwfilter/allow-arp.xml +++ b/examples/xml/nwfilter/allow-arp.xml @@ -1,3 +1,4 @@ -<filter name='allow-arp' chain='arp'> -<rule direction='inout' action='accept'/> +<filter name='allow-arp' chain='arpmac'> +<filterref filter='allow-arpmac.xml'/> +<filterref filter='allow-arpip.xml'/> </filter> So the intention here was to remove the 'arp' chain. With it staying now I suppose this patch and the allow-arpmac and allow-arpip aren't needed. diff --git a/examples/xml/nwfilter/allow-arpip.xml b/examples/xml/nwfilter/allow-arpip.xml new file mode 100644 index 0000000..6ddb6fe --- /dev/null +++ b/examples/xml/nwfilter/allow-arpip.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpip' chain='arpip'> +<rule direction='inout' action='accept'/> +</filter> Seems no necessary following above. diff --git a/examples/xml/nwfilter/allow-arpmac.xml b/examples/xml/nwfilter/allow-arpmac.xml new file mode 100644 index 0000000..54f6714 --- /dev/null +++ b/examples/xml/nwfilter/allow-arpmac.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpmac' chain='arpmac'> +<rule direction='inout' action='accept'/> +</filter> Seems not necessary following above. diff --git a/examples/xml/nwfilter/clean-traffic.xml b/examples/xml/nwfilter/clean-traffic.xml index 40f0ecb..9cee799 100644 --- a/examples/xml/nwfilter/clean-traffic.xml +++ b/examples/xml/nwfilter/clean-traffic.xml @@ -11,10 +11,10 @@ <!-- preventing ARP spoofing/poisoning --> <filterref filter='no-arp-spoofing'/>
-<!-- preventing any other traffic than IPv4 and ARP --> -<filterref filter='no-other-l2-traffic'/> - <!-- allow qemu to send a self-announce upon migration end --> <filterref filter='qemu-announce-self'/>
+<!-- preventing any other traffic than IPv4 and ARP --> +<filterref filter='no-other-l2-traffic'/> + </filter> This reshuffeling might make it more intuitive but isn't necessary.
diff --git a/examples/xml/nwfilter/no-arp-spoofing.xml b/examples/xml/nwfilter/no-arp-spoofing.xml index 3c83acd..1979b20 100644 --- a/examples/xml/nwfilter/no-arp-spoofing.xml +++ b/examples/xml/nwfilter/no-arp-spoofing.xml @@ -1,17 +1,4 @@ -<filter name='no-arp-spoofing' chain='arp'> -<uuid>f88f1932-debf-4aa1-9fbe-f10d3aa4bc95</uuid> -<rule action='drop' direction='out' priority='300'> -<mac match='no' srcmacaddr='$MAC'/> -</rule> - -<!-- no arp spoofing --> -<!-- drop if ipaddr or macaddr does not belong to guest --> -<rule action='drop' direction='out' priority='350'> -<arp match='no' arpsrcmacaddr='$MAC'/> -</rule> -<rule action='drop' direction='out' priority='400'> -<arp match='no' arpsrcipaddr='$IP' /> -</rule> -<!-- allow everything else --> -<rule action='accept' direction='in' priority='425' /> +<filter name='no-arp-spoofing'> +<filterref filter='no-arpmac-spoofing' /> +<filterref filter='no-arpip-spoofing' /> </filter> diff --git a/examples/xml/nwfilter/no-arpip-spoofing.xml b/examples/xml/nwfilter/no-arpip-spoofing.xml new file mode 100644 index 0000000..ee42d40 --- /dev/null +++ b/examples/xml/nwfilter/no-arpip-spoofing.xml @@ -0,0 +1,12 @@ +<filter name='no-arpip-spoofing' chain='arpip'> +<!-- no arp spoofing --> +<!-- drop if ipaddr does not belong to guest --> +<rule action='return' direction='out' priority='400'> +<arp match='yes' arpsrcipaddr='$IP' /> +</rule> +<rule action='return' direction='out' priority='410'> +<arp match='yes' arpsrcipaddr='0.0.0.0' /> +</rule> Under what circumstances is the stack allowed to send a 0.0.0.0 as a response to an ARP request (presumably)? Form what I see 0.0.0.0 could be any machine whose interface is not configured. At least when using DHCP the VM would broadcast the request without prior sending of an ARP request (of course) and from what I remember the DHCP server then sends
In the meantime I took some of the hunks here and build a parallel set of filters (clean-traffic-new). I'll post that series soon. I am not sure whether we want to have a parallel set in the end, though. the response back to the MAC address it has received the request from also without ARP request.
+<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-arpmac-spoofing.xml b/examples/xml/nwfilter/no-arpmac-spoofing.xml new file mode 100644 index 0000000..90499d3 --- /dev/null +++ b/examples/xml/nwfilter/no-arpmac-spoofing.xml @@ -0,0 +1,7 @@ +<filter name='no-arpmac-spoofing' chain='arpmac'> +<rule action='return' direction='out' priority='350'> +<arp match='yes' arpsrcmacaddr='$MAC'/> +</rule> +<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> +</filter> diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index b8c94c8..84e8a5e 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -1,7 +1,9 @@ <filter name='no-ip-spoofing' chain='ipv4'>
<!-- drop if srcipaddr is not the IP address of the guest --> -<rule action='drop' direction='out'> -<ip match='no' srcipaddr='$IP' /> +<rule action='return' direction='out'> +<ip match='yes' srcipaddr='$IP' /> </rule> +<!-- drop any that don't match the source IP list --> +<rule action='drop' direction='out' /> </filter> diff --git a/examples/xml/nwfilter/no-mac-spoofing.xml b/examples/xml/nwfilter/no-mac-spoofing.xml index f210623..aee56c7 100644 --- a/examples/xml/nwfilter/no-mac-spoofing.xml +++ b/examples/xml/nwfilter/no-mac-spoofing.xml @@ -1,5 +1,9 @@ -<filter name='no-mac-spoofing' chain='ipv4'> -<rule action='drop' direction='out' priority='10'> -<mac match='no' srcmacaddr='$MAC' /> -</rule> +<filter name='no-mac-spoofing' chain='mac'> +<!-- no mac spoofing --> +<!-- drop if macaddr does not belong to guest --> +<rule action='return' direction='out' priority='350'> +<mac match='yes' srcmacaddr='$MAC'/> +</rule> +<!-- drop everything else --> +<rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-other-l2-traffic.xml b/examples/xml/nwfilter/no-other-l2-traffic.xml index 8bad86e..0501b1a 100644 --- a/examples/xml/nwfilter/no-other-l2-traffic.xml +++ b/examples/xml/nwfilter/no-other-l2-traffic.xml @@ -1,7 +1,12 @@ -<filter name='no-other-l2-traffic'> +<filter name='no-other-l2-traffic' chain='root'>
-<!-- drop all other l2 traffic than for which rules have been - written for; i.e., drop all other than arp and ipv4 traffic --> -<rule action='drop' direction='inout' priority='1000'/> +<!-- drop all other than arp and ipv4 traffic --> +<rule action='accept' direction='inout'> +<mac protocolid='0x800' /> +</rule> +<rule action='accept' direction='inout'> +<mac protocolid='0x806' /> +</rule> +<rule action='drop' direction='inout' priority='1000' />
With the pending patches, this one looks a little different now. Stefan

Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/25/2011 12:01:05 PM:
So the intention here was to remove the 'arp' chain. With it staying now
I suppose this patch and the allow-arpmac and allow-arpip aren't needed.
diff --git a/examples/xml/nwfilter/allow-arpip.xml b/examples/xml/ nwfilter/allow-arpip.xml
The intention was to separate MAC and IP address matching to be their own chains to make adding and removing addresses dynamically easy. If you do this in one chain with "IP=X and MAC=Y -j ACCEPT", then you need every combination of IP and MAC address as a rule. If you separate them into MAC matching and protocol matching chains, you need one rule per MAC and IP address. After doing that, the ARP chain was empty, since that's all the standard chain had.
new file mode 100644 index 0000000..6ddb6fe --- /dev/null +++ b/examples/xml/nwfilter/allow-arpip.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpip' chain='arpip'> +<rule direction='inout' action='accept'/> +</filter> Seems no necessary following above.
I don't understand this comment.
This reshuffeling might make it more intuitive but isn't necessary.
I didn't say it was the only way to support multiple addresses. :-) It uses nMAC + nIP rules, instead of nMAC X nIP rules to match every combination of MAC and IP addresses in the original ARP chain.
+</rule> +<rule action='return' direction='out' priority='410'> +<arp match='yes' arpsrcipaddr='0.0.0.0' /> +</rule> Under what circumstances is the stack allowed to send a 0.0.0.0 as a response to an ARP request (presumably)? Form what I see 0.0.0.0 could be any machine whose interface is not configured. At least when using DHCP the VM would broadcast the request without prior sending of an ARP request (of course) and from what I remember the DHCP server then sends the response back to the MAC address it has received the request from also without ARP request.
This isn't a requirement, but in general, the filters are placing restrictions on packets that have nothing to do with spoofing and are not part of the underlying protocols. A host without an IP address can use source address "0.0.0.0" legally by the protocols in cases besides sending a DHCP request, DHCP requests need not be MAC broadcast or sent to the all-hosts broadcast address (e.g., if firmware configured a particular preferred server (unicast MAC and/or IP), or knows the subnet and uses a directed broadcast). ARP requests received from other machines need not be addressed to this VM to be processed and update the cache. By restricting these cases, the filters prevent VMs from doing protocol-legal things that have nothing to do with spoofing. It simply breaks unusual configurations unnecessarily. I was planning to submit clean-up patches to remove all these restrictions I found after my primary concern (DHCP snooping) is done. I threw in some of them along with the multiple address support in the version that did that, but they really are separate fixes that have nothing to do with either multiple address support or DHCP snooping. In the end, base anti-spoofing rules should prevent a VM from masquerading as someone else, and allow everything else. +-DLS

Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/25/2011 12:01:05 PM:
So the intention here was to remove the 'arp' chain. With it staying now I suppose this patch and the allow-arpmac and allow-arpip aren't needed.
diff --git a/examples/xml/nwfilter/allow-arpip.xml b/examples/xml/ nwfilter/allow-arpip.xml The intention was to separate MAC and IP address matching to be their own chains to make adding and removing addresses dynamically easy. If you do this in one chain with "IP=X and MAC=Y -j ACCEPT", then you need every combination of IP and MAC address as a rule. If you separate them The current (proposed) iterator does this, it produces every combination of IP and MAC list items. The question is whether this is the correct way of producing the filtering rules. On the one side one could say that
On 10/25/2011 03:41 PM, David Stevens wrote: the VM (or nested VMs) owns all the given MAC and IP addresses and can produce any combination of them. So we'll have m*n combinations -> O(n^2) evaluations. Another possibility would be to walk the lists of MAC and IP addresses parallel, i.e. produce a rule for $MAC[0] and $IP[0], another one for $MAC[n] and $IP[n], thus enforcing the association of IP and MAC addresses. We'd have O(n) evaluations, though the number of rules to evaluate should not drive how this works, but obviously 'correctness'. If we split the evaluation into two chains (one for IP , the other for MAC checking) we allow all combinations. Complexity is O(2*n) -> O(n). So what should be correct?Fixed IP - MAC association or allow any combination of them? Maybe we leave it as proposed (two chains) for now and modify the iterator to process multiple lists' elements in parallel...
into MAC matching and protocol matching chains, you need one rule per MAC and IP address. After doing that, the ARP chain was empty, since that's all the standard chain had.
Yes, fine. For backwards compatibility purposes the 'arp' chain will remain.
new file mode 100644 index 0000000..6ddb6fe --- /dev/null +++ b/examples/xml/nwfilter/allow-arpip.xml @@ -0,0 +1,3 @@ +<filter name='allow-arpip' chain='arpip'> +<rule direction='inout' action='accept'/> +</filter> Seems no necessary following above. I don't understand this comment.
This reshuffeling might make it more intuitive but isn't necessary. I didn't say it was the only way to support multiple addresses. :-) It uses nMAC + nIP rules, instead of nMAC X nIP rules to match every combination of MAC and IP addresses in the original ARP chain.
+</rule> +<rule action='return' direction='out' priority='410'> +<arp match='yes' arpsrcipaddr='0.0.0.0' /> +</rule> Under what circumstances is the stack allowed to send a 0.0.0.0 as a response to an ARP request (presumably)? Form what I see 0.0.0.0 could be any machine whose interface is not configured. At least when using DHCP the VM would broadcast the request without prior sending of an ARP request (of course) and from what I remember the DHCP server then sends the response back to the MAC address it has received the request from also without ARP request. This isn't a requirement, but in general, the filters are placing restrictions on packets that have nothing to do with spoofing and are not part of the underlying protocols. A host without an IP address can use source address "0.0.0.0" legally by the protocols in cases besides sending a DHCP request, DHCP requests need not be MAC broadcast or sent to the all-hosts broadcast address (e.g., if firmware configured a particular preferred server (unicast MAC and/or IP), or knows the subnet and uses a directed broadcast). ARP requests received from other machines need not be addressed to this VM to be processed and update the cache. I didn't test with a well-known DHCP server, so I was not sure whether
With the arp chain remaining we can keep the existing filter 'allow-arp' and don't need the one proposed in this patch. the rule would actually be used. Following the DHCP protocol fields one could broadcast and put the IP address of the server into the server address field in the DHCP protocol and if the protocol was to support it that way only that server could respond
By restricting these cases, the filters prevent VMs from doing protocol-legal things that have nothing to do with spoofing. It simply breaks unusual configurations unnecessarily.
I was planning to submit clean-up patches to remove all these restrictions I found after my primary concern (DHCP snooping) is done. I threw in some of them along with the multiple address support in the version that did that, but they really are separate fixes that have nothing to do with either multiple address support or DHCP snooping.
In the end, base anti-spoofing rules should prevent a VM from masquerading as someone else, and allow everything else.
+-DLS

This patch simplifies the table rules by setting the protocol chains policy to be "DROP" and removes the explicit "-j DROP" entries that the protocol rules had previously. It also makes "no-other-rarp-traffic.xml" obsolete. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- examples/xml/nwfilter/Makefile.am | 1 - examples/xml/nwfilter/no-arpip-spoofing.xml | 2 -- examples/xml/nwfilter/no-arpmac-spoofing.xml | 2 -- examples/xml/nwfilter/no-ip-spoofing.xml | 2 -- examples/xml/nwfilter/no-mac-spoofing.xml | 2 -- examples/xml/nwfilter/no-other-rarp-traffic.xml | 3 --- examples/xml/nwfilter/qemu-announce-self.xml | 1 - src/nwfilter/nwfilter_ebiptables_driver.c | 11 +---------- 8 files changed, 1 insertions(+), 23 deletions(-) delete mode 100644 examples/xml/nwfilter/no-other-rarp-traffic.xml diff --git a/examples/xml/nwfilter/Makefile.am b/examples/xml/nwfilter/Makefile.am index 84aaa3c..67085fa 100644 --- a/examples/xml/nwfilter/Makefile.am +++ b/examples/xml/nwfilter/Makefile.am @@ -18,7 +18,6 @@ FILTERS = \ no-mac-broadcast.xml \ no-mac-spoofing.xml \ no-other-l2-traffic.xml \ - no-other-rarp-traffic.xml \ qemu-announce-self.xml \ qemu-announce-self-rarp.xml diff --git a/examples/xml/nwfilter/no-arpip-spoofing.xml b/examples/xml/nwfilter/no-arpip-spoofing.xml index ee42d40..7ef6f0f 100644 --- a/examples/xml/nwfilter/no-arpip-spoofing.xml +++ b/examples/xml/nwfilter/no-arpip-spoofing.xml @@ -7,6 +7,4 @@ <rule action='return' direction='out' priority='410' > <arp match='yes' arpsrcipaddr='0.0.0.0' /> </rule> - <!-- drop everything else --> - <rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-arpmac-spoofing.xml b/examples/xml/nwfilter/no-arpmac-spoofing.xml index 90499d3..3834047 100644 --- a/examples/xml/nwfilter/no-arpmac-spoofing.xml +++ b/examples/xml/nwfilter/no-arpmac-spoofing.xml @@ -2,6 +2,4 @@ <rule action='return' direction='out' priority='350' > <arp match='yes' arpsrcmacaddr='$MAC'/> </rule> - <!-- drop everything else --> - <rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index 84e8a5e..2fccd12 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -4,6 +4,4 @@ <rule action='return' direction='out'> <ip match='yes' srcipaddr='$IP' /> </rule> - <!-- drop any that don't match the source IP list --> - <rule action='drop' direction='out' /> </filter> diff --git a/examples/xml/nwfilter/no-mac-spoofing.xml b/examples/xml/nwfilter/no-mac-spoofing.xml index aee56c7..e2e8c03 100644 --- a/examples/xml/nwfilter/no-mac-spoofing.xml +++ b/examples/xml/nwfilter/no-mac-spoofing.xml @@ -4,6 +4,4 @@ <rule action='return' direction='out' priority='350' > <mac match='yes' srcmacaddr='$MAC'/> </rule> - <!-- drop everything else --> - <rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-other-rarp-traffic.xml b/examples/xml/nwfilter/no-other-rarp-traffic.xml deleted file mode 100644 index 7729996..0000000 --- a/examples/xml/nwfilter/no-other-rarp-traffic.xml +++ /dev/null @@ -1,3 +0,0 @@ -<filter name='no-other-rarp-traffic' chain='rarp'> - <rule action='drop' direction='inout' priority='1000'/> -</filter> diff --git a/examples/xml/nwfilter/qemu-announce-self.xml b/examples/xml/nwfilter/qemu-announce-self.xml index 352db50..12957b5 100644 --- a/examples/xml/nwfilter/qemu-announce-self.xml +++ b/examples/xml/nwfilter/qemu-announce-self.xml @@ -8,6 +8,5 @@ <!-- accept if it was changed to rarp --> <filterref filter='qemu-announce-self-rarp'/> - <filterref filter='no-other-rarp-traffic'/> </filter> diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index 3c6fca7..e6a4880 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -2791,7 +2791,7 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, protostr[0] = '\0'; virBufferAsprintf(buf, - CMD_DEF("%s -t %s -N %s") CMD_SEPARATOR + CMD_DEF("%s -t %s -N %s -P DROP") CMD_SEPARATOR CMD_EXEC "%s" CMD_DEF("%s -t %s -A %s %s -j %s") CMD_SEPARATOR @@ -3015,15 +3015,6 @@ ebtablesApplyBasicRules(const char *ifname, PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); virBufferAsprintf(&buf, - CMD_DEF("%s -t %s -A %s -s ! %s -j DROP") CMD_SEPARATOR - CMD_EXEC - "%s", - - ebtables_cmd_path, EBTABLES_DEFAULT_TABLE, - chain, macaddr_str, - CMD_STOPONERR(1)); - - virBufferAsprintf(&buf, CMD_DEF("%s -t %s -A %s -p IPv4 -j ACCEPT") CMD_SEPARATOR CMD_EXEC "%s", -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch simplifies the table rules by setting the protocol chains policy to be "DROP" and removes the explicit "-j DROP" entries that the protocol rules had previously. It also makes "no-other-rarp-traffic.xml" obsolete. I agree with Daniel's previous comments that this could introduce compatibility problems. It would be best not to change it or if really need be later on introduce an XML attribute for a chain that allows to choose whether the default policy is accept or drop.
Stefan
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- examples/xml/nwfilter/Makefile.am | 1 - examples/xml/nwfilter/no-arpip-spoofing.xml | 2 -- examples/xml/nwfilter/no-arpmac-spoofing.xml | 2 -- examples/xml/nwfilter/no-ip-spoofing.xml | 2 -- examples/xml/nwfilter/no-mac-spoofing.xml | 2 -- examples/xml/nwfilter/no-other-rarp-traffic.xml | 3 --- examples/xml/nwfilter/qemu-announce-self.xml | 1 - src/nwfilter/nwfilter_ebiptables_driver.c | 11 +---------- 8 files changed, 1 insertions(+), 23 deletions(-) delete mode 100644 examples/xml/nwfilter/no-other-rarp-traffic.xml
diff --git a/examples/xml/nwfilter/Makefile.am b/examples/xml/nwfilter/Makefile.am index 84aaa3c..67085fa 100644 --- a/examples/xml/nwfilter/Makefile.am +++ b/examples/xml/nwfilter/Makefile.am @@ -18,7 +18,6 @@ FILTERS = \ no-mac-broadcast.xml \ no-mac-spoofing.xml \ no-other-l2-traffic.xml \ - no-other-rarp-traffic.xml \ qemu-announce-self.xml \ qemu-announce-self-rarp.xml
diff --git a/examples/xml/nwfilter/no-arpip-spoofing.xml b/examples/xml/nwfilter/no-arpip-spoofing.xml index ee42d40..7ef6f0f 100644 --- a/examples/xml/nwfilter/no-arpip-spoofing.xml +++ b/examples/xml/nwfilter/no-arpip-spoofing.xml @@ -7,6 +7,4 @@ <rule action='return' direction='out' priority='410'> <arp match='yes' arpsrcipaddr='0.0.0.0' /> </rule> -<!-- drop everything else --> -<rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-arpmac-spoofing.xml b/examples/xml/nwfilter/no-arpmac-spoofing.xml index 90499d3..3834047 100644 --- a/examples/xml/nwfilter/no-arpmac-spoofing.xml +++ b/examples/xml/nwfilter/no-arpmac-spoofing.xml @@ -2,6 +2,4 @@ <rule action='return' direction='out' priority='350'> <arp match='yes' arpsrcmacaddr='$MAC'/> </rule> -<!-- drop everything else --> -<rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index 84e8a5e..2fccd12 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -4,6 +4,4 @@ <rule action='return' direction='out'> <ip match='yes' srcipaddr='$IP' /> </rule> -<!-- drop any that don't match the source IP list --> -<rule action='drop' direction='out' /> </filter> diff --git a/examples/xml/nwfilter/no-mac-spoofing.xml b/examples/xml/nwfilter/no-mac-spoofing.xml index aee56c7..e2e8c03 100644 --- a/examples/xml/nwfilter/no-mac-spoofing.xml +++ b/examples/xml/nwfilter/no-mac-spoofing.xml @@ -4,6 +4,4 @@ <rule action='return' direction='out' priority='350'> <mac match='yes' srcmacaddr='$MAC'/> </rule> -<!-- drop everything else --> -<rule action='drop' direction='out' priority='1000' /> </filter> diff --git a/examples/xml/nwfilter/no-other-rarp-traffic.xml b/examples/xml/nwfilter/no-other-rarp-traffic.xml deleted file mode 100644 index 7729996..0000000 --- a/examples/xml/nwfilter/no-other-rarp-traffic.xml +++ /dev/null @@ -1,3 +0,0 @@ -<filter name='no-other-rarp-traffic' chain='rarp'> -<rule action='drop' direction='inout' priority='1000'/> -</filter> diff --git a/examples/xml/nwfilter/qemu-announce-self.xml b/examples/xml/nwfilter/qemu-announce-self.xml index 352db50..12957b5 100644 --- a/examples/xml/nwfilter/qemu-announce-self.xml +++ b/examples/xml/nwfilter/qemu-announce-self.xml @@ -8,6 +8,5 @@
<!-- accept if it was changed to rarp --> <filterref filter='qemu-announce-self-rarp'/> -<filterref filter='no-other-rarp-traffic'/>
</filter> diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index 3c6fca7..e6a4880 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -2791,7 +2791,7 @@ ebtablesCreateTmpSubChain(virBufferPtr buf, protostr[0] = '\0';
virBufferAsprintf(buf, - CMD_DEF("%s -t %s -N %s") CMD_SEPARATOR + CMD_DEF("%s -t %s -N %s -P DROP") CMD_SEPARATOR CMD_EXEC "%s" CMD_DEF("%s -t %s -A %s %s -j %s") CMD_SEPARATOR @@ -3015,15 +3015,6 @@ ebtablesApplyBasicRules(const char *ifname,
PRINT_ROOT_CHAIN(chain, chainPrefix, ifname); virBufferAsprintf(&buf, - CMD_DEF("%s -t %s -A %s -s ! %s -j DROP") CMD_SEPARATOR - CMD_EXEC - "%s", - - ebtables_cmd_path, EBTABLES_DEFAULT_TABLE, - chain, macaddr_str, - CMD_STOPONERR(1)); - - virBufferAsprintf(&buf, CMD_DEF("%s -t %s -A %s -p IPv4 -j ACCEPT") CMD_SEPARATOR CMD_EXEC "%s",

Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 08:50:14 AM:
I agree with Daniel's previous comments that this could introduce compatibility problems. It would be best not to change it or if really need be later on introduce an XML attribute for a chain that allows to choose whether the default policy is accept or drop.
This is not a functional change. With the multiple address support, literally all of the chains in question end with "-j DROP". By changing the default policy to "DROP", it removes the requirement to have the "-j DROP" _at_the_end_, which makes appending new rules without reference to a priority easier. I think the real sticking point here is whether we consider the existing standard chains as immutable. If, for compatibility's sake, we keep address matching as: if ($IP != ADDRESS) DROP ACCEPT then we cannot support multiple IP addresses. If we change this to: if ($IP == ADDRESS1) RETURN if ($IP == ADDRESS2) RETURN ... DROP all of the standard chains end in "-j DROP" and making the policy be DROP expresses exactly the same thing with one less rule per chain. This also allows appending more allowed addresses without using a priority to ensure that "-j DROP" is last. Any customization to the old filters that do "DROP" or "ACCEPT" will still work and "RETURN" and "CONTINUE" were not supported before, so the default, whether done by "-j DROP/ACCEPT" or the policy, don't matter-- the custom rule will behave as it does now. It's just that that "DROP/ACCEPT" will bypass subsequent checks that are now possible if doing "RETURN/CONTINUE" instead. But, of course, *any* change to the standard filters requires a review of custom filters that link into them or modify them. I think maintaining this level of compatibility simply means we cannot support multiple addresses. Doing that obviously requires changing the existing address matching filters, which therefore can't be linked in in identically the same way. However, I think any existing customization is trivial to apply after, too. Perhaps an example filter that causes a problem? +-DLS

Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 08:50:14 AM:
I agree with Daniel's previous comments that this could introduce compatibility problems. It would be best not to change it or if really need be later on introduce an XML attribute for a chain that allows to choose whether the default policy is accept or drop. This is not a functional change. With the multiple address support, literally all of the chains in question end with "-j DROP". By changing the default policy to "DROP", it removes the requirement to have the "-j DROP" _at_the_end_, which makes appending new rules without reference to a priority easier. Yes, '_at_the_end_', that's what I thought. I am not sure whether this
On 10/17/2011 01:04 PM, David Stevens wrote: particular requirement is the best way to proceed since obviously you cannot have any other rules with lesser priority after the ones doing the 'return' -- whatever those rules may be doing. The alternative would be having to instantiate the whole filter chain in the same way as the IP address learning thread does with the 'late' filter instantiation call -- that would at least get all the priorities correct and not introduce a requirement on how one has to write a filter.
I think the real sticking point here is whether we consider the existing standard chains as immutable. If, for compatibility's sake,
we keep address matching as: if ($IP != ADDRESS) DROP ACCEPT
then we cannot support multiple IP addresses. If we change this to:
if ($IP == ADDRESS1) RETURN if ($IP == ADDRESS2) RETURN ... DROP
all of the standard chains end in "-j DROP" and making the policy be DROP expresses exactly the same thing with one less rule per chain. This also allows appending more allowed addresses without using a priority to ensure that "-j DROP" is last. Yes, I understand that but I don't necessarily like the implications of
I don't consider them as immutable but should be replaced with something of the same or 'better' functionality. this.
Any customization to the old filters that do "DROP" or "ACCEPT" will still work and "RETURN" and "CONTINUE" were not supported before, so the default, whether done by "-j DROP/ACCEPT" or the policy, don't matter-- the custom rule will behave as it does now. It's just that that "DROP/ACCEPT" will bypass subsequent checks that are now possible if doing "RETURN/CONTINUE" instead. But, of course, *any* change to the standard filters requires a review of custom filters that link into them or modify them.
I think maintaining this level of compatibility simply means we cannot support multiple addresses. Doing that obviously requires changing the existing address matching filters, which therefore can't be linked in in identically the same way. However, I think any existing customization is trivial to apply after, too. Perhaps an example filter that causes a problem?
I don't have an example filter myself, but I don't know what other people may have written. An alternative would be to say that the existing filters work with the IP address learning thread and we would need to introduce new filters for support of multiple IP addresses. Yes, the current filters aren't prepare for supporting multiple IP addresses per interface. I don't think replacing the existing filters would be a problem per-se, but I don't like that the priority of the rules doing the 'RETURN' is assumed to be the lowest in the chain and we can just append anything to the end of the chain -- the filters we are writing are just examples and someone may come up with a few rules that do something with packets that were not RETURN'ed and thus needs to have rules executing behind those. Again, maybe (the less efficient but more generic way of) instantiating the filters by calling the virNWFilterInstantiateFilterLate() could solve part of the problem. Stefan
+-DLS

Yes, '_at_the_end_', that's what I thought. I am not sure whether this particular requirement is the best way to proceed since obviously you cannot have any other rules with lesser priority after the ones doing the 'return' -- whatever those rules may be doing. Not in the same chain, but if a chain is checking multiple allowed values for the same field, that's all that chain should be doing. Checking some other condition should be done after passing, e.g., the IP address checks and would be done because of the "RETURN". Current code does an ACCEPT or REJECT at that point, so *requires* modification of the existing chain. Any current filters are even worse because without RETURN and CONTINUE, they can only accept or drop a packet without further
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 10:29:08 AM: processing. With this patchset, you can apply multiple tests to the same packet with only the restriction that testing other fields in that packet require a different subchain-- something not possible at all today. If, for example, you want to allow 100 ports and a particular IP address, reject everything else, won't that require a "DROP" rule for 65436 ports so that you don't accept either based on just the port or just the IP address? Or , you'd have to combine the IP address check in every port-check rule and do it before you do the blanket "ACCEPT" in the standard rule.
An alternative would be to say that the existing filters work with the IP address learning thread and we would need to introduce new filters for support of multiple IP addresses. Yes, the current filters aren't prepare for supporting multiple IP addresses per interface.
Yes, I thought about this, but it really is just duplicating the entire set with a different name. I think especially without support for RETURN/CONTINUE, as a practical matter the only way to modify the current filters to do interesting things is to replace them. Anyone doing that will not be affected by a change to the standard rules, except for the possibly trivial addition of a "-j ACCEPT" at the end if they require a default "ACCEPT" for the modified rules.
I don't think replacing the existing filters would be a problem per-se, but I don't like that the priority of the rules doing the 'RETURN' is assumed to be the lowest in the chain and we can just append anything to
the end of the chain -- the filters we are writing are just examples and
someone may come up with a few rules that do something with packets that
were not RETURN'ed and thus needs to have rules executing behind those. Again, maybe (the less efficient but more generic way of) instantiating the filters by calling the virNWFilterInstantiateFilterLate() could solve part of the problem.
The only use I've added for addRules is the multiple address check and that should not have other random fields in the same chain. Better would be to have a different chain linked at the top after the address checks. In fact, for compatibility, it'd be possible to change the address checks to a different chain and leave an empty "ip" and "arp" chain that does only "accept". It's just that it isn't really all that compatible if what's really happening is that a customer is replacing the existing chains with modifications, rather than applying additional rules to it. In the end, any modification whatsoever to the "examples" directory requires someone who has customized a filter to look at the customization again. Addition of "RETURN"/"CONTINUE" makes that easier in the future. With the existing filters, any customization cannot be independent of the internals of the existing filters (it's adding rules in the middle of it), so the only way to maintain compatibility is to never change them. With "RETURN/CONTINUE" and your suggested "user-defined" chain(s), you can apply the standard sets with "RETURN" for acceptable packets and then apply any user-defined checks also to the same packets afterwards. I think that's the right approach, but it requires either changing the existing chains or introducing a completely parallel set and not using the original filters at all, except for sites that want the old stuff only. +-DLS

Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 10:29:08 AM:
Yes, '_at_the_end_', that's what I thought. I am not sure whether this particular requirement is the best way to proceed since obviously you cannot have any other rules with lesser priority after the ones doing the 'return' -- whatever those rules may be doing. Not in the same chain, but if a chain is checking multiple allowed values for the same field, that's all that chain should be doing. Sure, and what if I wanted to do something with packets that were not RETURN'ed, where would I treat them besides *after* the RETURN? Checking some other condition should be done after passing, e.g., the IP address checks and would be done because of the "RETURN". Current code does an ACCEPT or REJECT at that point, so *requires* modification of the existing chain. Any current filters are even worse because without RETURN and CONTINUE, they can only accept or drop a packet without further processing. Not arguing against that. With this patchset, you can apply multiple tests to the same packet with only the restriction that testing other fields in that packet require a different subchain-- something not possible at all today. If, for example, you want to allow 100 ports and a particular IP address, reject everything else, won't that require a "DROP" rule for 65436 ports so that you don't accept either based on just the port or just the IP address? Or , you'd have to combine the IP address check in every port-check rule and do it before you do the blanket "ACCEPT" in the standard rule.
An alternative would be to say that the existing filters work with the IP address learning thread and we would need to introduce new filters for support of multiple IP addresses. Yes, the current filters aren't prepare for supporting multiple IP addresses per interface. Yes, I thought about this, but it really is just duplicating the entire set with a different name. I think especially without support for RETURN/CONTINUE, as a practical matter the only way to modify the current filters to do interesting things is to replace them. Anyone doing that will not be affected by a change to the standard rules, except for the possibly trivial addition of a "-j ACCEPT" at the end if they require a default "ACCEPT" for the modified rules.
I don't think replacing the existing filters would be a problem per-se, but I don't like that the priority of the rules doing the 'RETURN' is assumed to be the lowest in the chain and we can just append anything to the end of the chain -- the filters we are writing are just examples and someone may come up with a few rules that do something with packets that were not RETURN'ed and thus needs to have rules executing behind those. Again, maybe (the less efficient but more generic way of) instantiating the filters by calling the virNWFilterInstantiateFilterLate() could solve part of the problem. The only use I've added for addRules is the multiple address check and that should not have other random fields in the same chain. Better would be to have a different chain linked at the What if in the future we were to support ebtables logging and wanted to log any packet that has not been handled with a RETURN, thus for example logging MAC spoofing -- just as an example. I don't see how one would do
On 10/17/2011 05:22 PM, David Stevens wrote: that without adding rules into the mac chain *after* the rules that do the RETURN for packets with acceptable source MAC addresses and all others get logged. I would not call these 'random fields' being evaluated, but that the implementation of addRules() requires the filters to be limited in a way that may at some point become a problem for some use cases and someone has to reimplement it then. Stefan

This patch adds the internal capability to add rules to existing chains instead of using temporary chains and to generate placeholders for chains that are referenced without generating a rule for them immediately. Finally, it includes variable matching for filter instantiation (i.e., instantiate only when a given variable is present in a filter, or only when it is not). Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/conf/nwfilter_conf.h | 4 +- src/nwfilter/nwfilter_ebiptables_driver.c | 93 +++++++++++++++++++++-------- src/nwfilter/nwfilter_gentech_driver.c | 32 +++++++++- 3 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 17e954e..4348378 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -525,7 +525,9 @@ typedef int (*virNWFilterRuleCreateInstance)(virConnectPtr conn, virNWFilterRuleDefPtr rule, const char *ifname, virNWFilterHashTablePtr vars, - virNWFilterRuleInstPtr res); + virNWFilterRuleInstPtr res, + bool usetemp, + bool dummy); typedef int (*virNWFilterRuleApplyNewRules)(virConnectPtr conn, const char *ifname, diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index e6a4880..918625c 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -1136,6 +1136,7 @@ iptablesEnforceDirection(int directionIn, * @isIPv6 : Whether this is an IPv6 rule * @maySkipICMP : whether this rule may under certain circumstances skip * the ICMP rule from being created + * @dummy : generate rule placeholder without installing * * Convert a single rule into its representation for later instantiation * @@ -1154,7 +1155,8 @@ _iptablesCreateRuleInstance(int directionIn, const char *match, bool defMatch, const char *accept_target, bool isIPv6, - bool maySkipICMP) + bool maySkipICMP, + bool dummy) { char chain[MAX_CHAINNAME_LENGTH]; char number[20]; @@ -1181,6 +1183,13 @@ _iptablesCreateRuleInstance(int directionIn, PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname); + if (dummy) { + virBufferAsprintf(&buf, CMD_DEF_PRE "%s -- %s -%%c %s %%s", + "echo", iptables_cmd, chain); + bufUsed = virBufferUse(&buf); + goto prskip; + } + switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_TCP: case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: @@ -1521,6 +1530,8 @@ _iptablesCreateRuleInstance(int directionIn, return -1; } +prskip: + if ((srcMacSkipped && bufUsed == virBufferUse(&buf)) || skipRule) { virBufferFreeAndReset(&buf); @@ -1636,7 +1647,9 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool isIPv6) + bool isIPv6, + bool usetemp, + bool dummy) { int rc; int directionIn = 0; @@ -1668,7 +1681,7 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, return 1; } - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; if (create) { rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, @@ -1680,7 +1693,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); VIR_FREE(matchState); if (rc) @@ -1700,7 +1714,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, return 1; } - chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : + CHAINPREFIX_HOST_OUT; if (create) { rc = _iptablesCreateRuleInstance(!directionIn, chainPrefix, @@ -1712,7 +1727,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "ACCEPT", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); VIR_FREE(matchState); @@ -1736,7 +1752,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, if (create) { chainPrefix[0] = 'H'; - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : + CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1747,7 +1764,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); VIR_FREE(matchState); } @@ -1761,7 +1779,9 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool isIPv6) + bool isIPv6, + bool usetemp, + bool dummy) { int rc; int directionIn = 0; @@ -1777,7 +1797,9 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); } if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) || @@ -1800,7 +1822,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, else matchState = NULL; - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1811,7 +1833,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); if (rc) return rc; @@ -1822,7 +1845,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, else matchState = NULL; - chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : CHAINPREFIX_HOST_OUT; rc = _iptablesCreateRuleInstance(!directionIn, chainPrefix, nwfilter, @@ -1833,7 +1856,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "ACCEPT", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); if (rc) return rc; @@ -1844,7 +1868,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState = NULL; chainPrefix[0] = 'H'; - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1855,7 +1879,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); return rc; } @@ -1886,7 +1911,8 @@ ebtablesCreateRuleInstance(char chainPrefix, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool reverse) + bool reverse, + bool dummy) { char macaddr[VIR_MAC_STRING_BUFLEN], ipaddr[INET_ADDRSTRLEN], @@ -1909,6 +1935,11 @@ ebtablesCreateRuleInstance(char chainPrefix, PRINT_CHAIN(chain, chainPrefix, ifname, virNWFilterChainSuffixTypeToString(nwfilter->chainsuffix)); + if (dummy) { + virBufferAsprintf(&buf, CMD_DEF_PRE "%s -- -t %s -%%c %s %%s", + "echo", EBTABLES_DEFAULT_TABLE, chain); + goto prskip; + } switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_MAC: @@ -2317,6 +2348,8 @@ ebtablesCreateRuleInstance(char chainPrefix, return -1; } +prskip: + switch (rule->action) { case VIR_NWFILTER_RULE_ACTION_REJECT: /* REJECT not supported */ @@ -2374,11 +2407,15 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, virNWFilterRuleDefPtr rule, const char *ifname, virNWFilterHashTablePtr vars, - virNWFilterRuleInstPtr res) + virNWFilterRuleInstPtr res, + bool usetemp, + bool dummy) { int rc = 0; bool isIPv6; + char chainPrefix; + chainPrefix = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_IP: case VIR_NWFILTER_RULE_PROTOCOL_MAC: @@ -2389,26 +2426,30 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_OUT || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { - rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_IN_TEMP, + rc = ebtablesCreateRuleInstance(chainPrefix, nwfilter, rule, ifname, vars, res, - rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT); + rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT, + dummy); if (rc) return rc; } + chainPrefix = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : + CHAINPREFIX_HOST_OUT; if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { - rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_OUT_TEMP, + rc = ebtablesCreateRuleInstance(chainPrefix, nwfilter, rule, ifname, vars, res, - false); + false, + dummy); } break; @@ -2427,7 +2468,9 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); break; case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: @@ -2444,7 +2487,9 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); break; case VIR_NWFILTER_RULE_PROTOCOL_LAST: diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 7891983..79350ac 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -284,7 +284,9 @@ virNWFilterRuleInstantiate(virConnectPtr conn, virNWFilterDefPtr filter, virNWFilterRuleDefPtr rule, const char *ifname, - virNWFilterHashTablePtr vars) + virNWFilterHashTablePtr vars, + bool usetemp, + bool dummy) { int rc; int i; @@ -297,8 +299,8 @@ virNWFilterRuleInstantiate(virConnectPtr conn, ret->techdriver = techdriver; - rc = techdriver->createRuleInstance(conn, nettype, filter, - rule, ifname, vars, ret); + rc = techdriver->createRuleInstance(conn, nettype, filter, rule, ifname, + vars, ret, usetemp, dummy); if (rc) { for (i = 0; i < ret->ndata; i++) @@ -354,6 +356,8 @@ err_exit: * @ifname: The name of the interface to apply the rules to * @vars: A map holding variable names and values used for instantiating * the filter and its subfilters. + * @matchvar: if non-null, variable name to match + * @notmatch: if matchvar set, match filters that do not reference matchvar * @nEntries: number of virNWFilterInst objects collected * @insts: pointer to array for virNWFilterIns object pointers * @useNewFilter: instruct whether to use a newDef pointer rather than a @@ -377,6 +381,8 @@ _virNWFilterInstantiateRec(virConnectPtr conn, virNWFilterDefPtr filter, const char *ifname, virNWFilterHashTablePtr vars, + const char *matchvar, + bool notmatch, int *nEntries, virNWFilterRuleInstPtr **insts, enum instCase useNewFilter, bool *foundNewFilter, @@ -387,18 +393,34 @@ _virNWFilterInstantiateRec(virConnectPtr conn, int i; virNWFilterRuleInstPtr inst; virNWFilterDefPtr next_filter; + bool usetemp, dummy; for (i = 0; i < filter->nentries; i++) { virNWFilterRuleDefPtr rule = filter->filterEntries[i]->rule; virNWFilterIncludeDefPtr inc = filter->filterEntries[i]->include; if (rule) { + usetemp = 1; + dummy = 0; + if (matchvar) { + int j; + + for (j = 0; j < rule->nvars; ++j) + if (strcmp(rule->vars[j], matchvar) == 0) + break; + /* use temp chains only on base rule install */ + usetemp = notmatch; + /* skip if not found; notmatch reverses the sense */ + dummy = (j == rule->nvars) ^ notmatch; + } inst = virNWFilterRuleInstantiate(conn, techdriver, nettype, filter, rule, ifname, - vars); + vars, + usetemp, + dummy); if (!inst) { rc = 1; break; @@ -456,6 +478,7 @@ _virNWFilterInstantiateRec(virConnectPtr conn, next_filter, ifname, tmpvars, + matchvar, notmatch, nEntries, insts, useNewFilter, foundNewFilter, @@ -684,6 +707,7 @@ virNWFilterInstantiate(virConnectPtr conn, filter, ifname, vars, + 0, 0, &nEntries, &insts, useNewFilter, foundNewFilter, driver); -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds the internal capability to add rules to existing chains instead of using temporary chains and to generate placeholders for chains that are referenced without generating a rule for them immediately. Finally, it includes variable matching for filter instantiation (i.e., instantiate only when a given variable is present in a filter, or only when it is not).
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- src/conf/nwfilter_conf.h | 4 +- src/nwfilter/nwfilter_ebiptables_driver.c | 93 +++++++++++++++++++++-------- src/nwfilter/nwfilter_gentech_driver.c | 32 +++++++++- 3 files changed, 100 insertions(+), 29 deletions(-)
diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 17e954e..4348378 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -525,7 +525,9 @@ typedef int (*virNWFilterRuleCreateInstance)(virConnectPtr conn, virNWFilterRuleDefPtr rule, const char *ifname, virNWFilterHashTablePtr vars, - virNWFilterRuleInstPtr res); + virNWFilterRuleInstPtr res, + bool usetemp, + bool dummy);
typedef int (*virNWFilterRuleApplyNewRules)(virConnectPtr conn, const char *ifname, diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index e6a4880..918625c 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -1136,6 +1136,7 @@ iptablesEnforceDirection(int directionIn, * @isIPv6 : Whether this is an IPv6 rule * @maySkipICMP : whether this rule may under certain circumstances skip * the ICMP rule from being created + * @dummy : generate rule placeholder without installing * * Convert a single rule into its representation for later instantiation * @@ -1154,7 +1155,8 @@ _iptablesCreateRuleInstance(int directionIn, const char *match, bool defMatch, const char *accept_target, bool isIPv6, - bool maySkipICMP) + bool maySkipICMP, + bool dummy) { char chain[MAX_CHAINNAME_LENGTH]; char number[20]; @@ -1181,6 +1183,13 @@ _iptablesCreateRuleInstance(int directionIn,
PRINT_IPT_ROOT_CHAIN(chain, chainPrefix, ifname);
+ if (dummy) { + virBufferAsprintf(&buf, CMD_DEF_PRE "%s -- %s -%%c %s %%s", + "echo", iptables_cmd, chain); + bufUsed = virBufferUse(&buf); + goto prskip; + } + switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_TCP: case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: @@ -1521,6 +1530,8 @@ _iptablesCreateRuleInstance(int directionIn, return -1; }
+prskip: + if ((srcMacSkipped&& bufUsed == virBufferUse(&buf)) || skipRule) { virBufferFreeAndReset(&buf); @@ -1636,7 +1647,9 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool isIPv6) + bool isIPv6, + bool usetemp, + bool dummy) { int rc; int directionIn = 0; @@ -1668,7 +1681,7 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, return 1; }
- chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; if (create) { rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, @@ -1680,7 +1693,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy);
VIR_FREE(matchState); if (rc) @@ -1700,7 +1714,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, return 1; }
- chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : + CHAINPREFIX_HOST_OUT; if (create) { rc = _iptablesCreateRuleInstance(!directionIn, chainPrefix, @@ -1712,7 +1727,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "ACCEPT", isIPv6, - maySkipICMP); + maySkipICMP, + dummy);
VIR_FREE(matchState);
@@ -1736,7 +1752,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter,
if (create) { chainPrefix[0] = 'H'; - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : + CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1747,7 +1764,8 @@ iptablesCreateRuleInstanceStateCtrl(virNWFilterDefPtr nwfilter, matchState, false, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); VIR_FREE(matchState); }
@@ -1761,7 +1779,9 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool isIPv6) + bool isIPv6, + bool usetemp, + bool dummy) { int rc; int directionIn = 0; @@ -1777,7 +1797,9 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); }
if ((rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN) || @@ -1800,7 +1822,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, else matchState = NULL;
- chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1811,7 +1833,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); if (rc) return rc;
@@ -1822,7 +1845,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, else matchState = NULL;
- chainPrefix[1] = CHAINPREFIX_HOST_OUT_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : CHAINPREFIX_HOST_OUT; rc = _iptablesCreateRuleInstance(!directionIn, chainPrefix, nwfilter, @@ -1833,7 +1856,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "ACCEPT", isIPv6, - maySkipICMP); + maySkipICMP, + dummy); if (rc) return rc;
@@ -1844,7 +1868,7 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState = NULL;
chainPrefix[0] = 'H'; - chainPrefix[1] = CHAINPREFIX_HOST_IN_TEMP; + chainPrefix[1] = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; rc = _iptablesCreateRuleInstance(directionIn, chainPrefix, nwfilter, @@ -1855,7 +1879,8 @@ iptablesCreateRuleInstance(virNWFilterDefPtr nwfilter, matchState, true, "RETURN", isIPv6, - maySkipICMP); + maySkipICMP, + dummy);
return rc; } @@ -1886,7 +1911,8 @@ ebtablesCreateRuleInstance(char chainPrefix, const char *ifname, virNWFilterHashTablePtr vars, virNWFilterRuleInstPtr res, - bool reverse) + bool reverse, + bool dummy) { char macaddr[VIR_MAC_STRING_BUFLEN], ipaddr[INET_ADDRSTRLEN], @@ -1909,6 +1935,11 @@ ebtablesCreateRuleInstance(char chainPrefix, PRINT_CHAIN(chain, chainPrefix, ifname, virNWFilterChainSuffixTypeToString(nwfilter->chainsuffix));
+ if (dummy) { + virBufferAsprintf(&buf, CMD_DEF_PRE "%s -- -t %s -%%c %s %%s", + "echo", EBTABLES_DEFAULT_TABLE, chain); As above in the iptables case you should also add the ebtables_cmd into
Following the above I am not sure what this will be used for as part of this extension. the string (... %s -t %s ...).
+ goto prskip; + }
switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_MAC: @@ -2317,6 +2348,8 @@ ebtablesCreateRuleInstance(char chainPrefix, return -1; }
+prskip: + switch (rule->action) { case VIR_NWFILTER_RULE_ACTION_REJECT: /* REJECT not supported */ @@ -2374,11 +2407,15 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, virNWFilterRuleDefPtr rule, const char *ifname, virNWFilterHashTablePtr vars, - virNWFilterRuleInstPtr res) + virNWFilterRuleInstPtr res, + bool usetemp, + bool dummy) { int rc = 0; bool isIPv6; + char chainPrefix;
+ chainPrefix = usetemp ? CHAINPREFIX_HOST_IN_TEMP : CHAINPREFIX_HOST_IN; switch (rule->prtclType) { case VIR_NWFILTER_RULE_PROTOCOL_IP: case VIR_NWFILTER_RULE_PROTOCOL_MAC: @@ -2389,26 +2426,30 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED,
if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_OUT || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { - rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_IN_TEMP, + rc = ebtablesCreateRuleInstance(chainPrefix, nwfilter, rule, ifname, vars, res, - rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT); + rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT, + dummy); if (rc) return rc; }
+ chainPrefix = usetemp ? CHAINPREFIX_HOST_OUT_TEMP : + CHAINPREFIX_HOST_OUT; if (rule->tt == VIR_NWFILTER_RULE_DIRECTION_IN || rule->tt == VIR_NWFILTER_RULE_DIRECTION_INOUT) { - rc = ebtablesCreateRuleInstance(CHAINPREFIX_HOST_OUT_TEMP, + rc = ebtablesCreateRuleInstance(chainPrefix, nwfilter, rule, ifname, vars, res, - false); + false, + dummy); } break;
@@ -2427,7 +2468,9 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); break;
case VIR_NWFILTER_RULE_PROTOCOL_TCPoIPV6: @@ -2444,7 +2487,9 @@ ebiptablesCreateRuleInstance(virConnectPtr conn ATTRIBUTE_UNUSED, ifname, vars, res, - isIPv6); + isIPv6, + usetemp, + dummy); break;
case VIR_NWFILTER_RULE_PROTOCOL_LAST: diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 7891983..79350ac 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -284,7 +284,9 @@ virNWFilterRuleInstantiate(virConnectPtr conn, virNWFilterDefPtr filter, virNWFilterRuleDefPtr rule, const char *ifname, - virNWFilterHashTablePtr vars) + virNWFilterHashTablePtr vars, + bool usetemp, + bool dummy) { int rc; int i; @@ -297,8 +299,8 @@ virNWFilterRuleInstantiate(virConnectPtr conn,
ret->techdriver = techdriver;
- rc = techdriver->createRuleInstance(conn, nettype, filter, - rule, ifname, vars, ret); + rc = techdriver->createRuleInstance(conn, nettype, filter, rule, ifname, + vars, ret, usetemp, dummy);
if (rc) { for (i = 0; i< ret->ndata; i++) @@ -354,6 +356,8 @@ err_exit: * @ifname: The name of the interface to apply the rules to * @vars: A map holding variable names and values used for instantiating * the filter and its subfilters. + * @matchvar: if non-null, variable name to match + * @notmatch: if matchvar set, match filters that do not reference matchvar Can you write what happens if a match occurs versus if it does not occur? * @nEntries: number of virNWFilterInst objects collected * @insts: pointer to array for virNWFilterIns object pointers * @useNewFilter: instruct whether to use a newDef pointer rather than a @@ -377,6 +381,8 @@ _virNWFilterInstantiateRec(virConnectPtr conn, virNWFilterDefPtr filter, const char *ifname, virNWFilterHashTablePtr vars, + const char *matchvar, + bool notmatch, int *nEntries, virNWFilterRuleInstPtr **insts, enum instCase useNewFilter, bool *foundNewFilter, @@ -387,18 +393,34 @@ _virNWFilterInstantiateRec(virConnectPtr conn, int i; virNWFilterRuleInstPtr inst; virNWFilterDefPtr next_filter; + bool usetemp, dummy;
for (i = 0; i< filter->nentries; i++) { virNWFilterRuleDefPtr rule = filter->filterEntries[i]->rule; virNWFilterIncludeDefPtr inc = filter->filterEntries[i]->include; if (rule) { + usetemp = 1; + dummy = 0; + if (matchvar) { + int j; + + for (j = 0; j< rule->nvars; ++j) + if (strcmp(rule->vars[j], matchvar) == 0) + break; + /* use temp chains only on base rule install */ + usetemp = notmatch; + /* skip if not found; notmatch reverses the sense */ + dummy = (j == rule->nvars) ^ notmatch; + } inst = virNWFilterRuleInstantiate(conn, techdriver, nettype, filter, rule, ifname, - vars); + vars, + usetemp, + dummy); if (!inst) { rc = 1; break; @@ -456,6 +478,7 @@ _virNWFilterInstantiateRec(virConnectPtr conn, next_filter, ifname, tmpvars, + matchvar, notmatch, nEntries, insts, useNewFilter, foundNewFilter, @@ -684,6 +707,7 @@ virNWFilterInstantiate(virConnectPtr conn, filter, ifname, vars, + 0, 0, &nEntries,&insts, useNewFilter, foundNewFilter, driver);

Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 09:07:12 AM:
On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds the internal capability to add rules to existing chains instead of using temporary chains and to generate placeholders for chains that are referenced without generating a rule for them immediately. Finally, it includes variable matching for filter instantiation (i.e., instantiate only when a given variable is present in a filter, or only when it is not).
Following the above I am not sure what this will be used for as part of this extension.
This is used to add rules to existing chains when a new IP address is discovered (i.e., a DHCP ACK from a server occurs). The existing code builds the entire chain as a temporary chain and then swaps it in, which is only appropriate at start-up. For DHCP snooping, we want to add and remove rules that reference "IP" using a particular value (the address for the ACK or lease expiration) without affecting other rules that don't reference IP or have a different address value. "removeRules" was already there, but "addRules" was not. +-DLS

Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 09:07:12 AM:
On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds the internal capability to add rules to existing chains instead of using temporary chains and to generate placeholders
for
chains that are referenced without generating a rule for them immediately. Finally, it includes variable matching for filter instantiation (i.e., instantiate only when a given variable is present in a filter, or only when it is not).
Following the above I am not sure what this will be used for as part of this extension. This is used to add rules to existing chains when a new IP address is discovered (i.e., a DHCP ACK from a server occurs). The existing code builds the entire chain as a temporary chain and then swaps it in, which is only appropriate at start-up. For DHCP snooping, we want to add and remove rules that reference "IP" using a particular value (the address for the ACK or lease expiration) without affecting other rules that don't reference IP or have a different address value. "removeRules" was already there, but "addRules" was not. Yes, then I understood this correctly. See the other mails regarding the
On 10/17/2011 01:23 PM, David Stevens wrote: problems I am seeing with it. If there was a way to figure out at what position to insert a rule into an existing chain, i.e. at position 5, rather than always at the end, we could use this addRules() call, otherwise I find it very limiting. Stefan
+-DLS

Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 10:31:29 AM:
was not. Yes, then I understood this correctly. See the other mails regarding the
problems I am seeing with it. If there was a way to figure out at what position to insert a rule into an existing chain, i.e. at position 5, rather than always at the end, we could use this addRules() call, otherwise I find it very limiting.
I'm not sure if I answered this already for you or not, but you can -- by using the priority in the rule. If we don't use the policy and so have to have a "-j DROP" at the end, then we'd want the original filter to use "-1" (if I'm remembering correctly -- 1 before end??). You can specify the rule be added at any point; "IP" rules would all have the same priority, because they originate from the same line in the filter, but you can use the priority to offset from the end or beginning, or any fixed point in the chain. +-DLS PS - I haven't tried using negative priorities with nwfilter, but ebtables/iptables supports it, at least.

On 10/17/2011 01:58 PM, David Stevens wrote:
Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 10:31:29 AM:
was not. Yes, then I understood this correctly. See the other mails regarding the problems I am seeing with it. If there was a way to figure out at what position to insert a rule into an existing chain, i.e. at position 5, rather than always at the end, we could use this addRules() call, otherwise I find it very limiting. I'm not sure if I answered this already for you or not, but you can -- by using the priority in the rule. If we don't use the policy and so have to have a "-j DROP" at the end, then we'd want the original filter to use "-1" (if I'm remembering correctly -- 1 before end??). You can specify the rule be added at any point; "IP" rules would all have the same priority, because they originate from the same line in the filter, but you can use the priority to offset from the end or beginning, or any fixed point in the chain.
+-DLS
PS - I haven't tried using negative priorities with nwfilter, but ebtables/iptables supports it, at least. The ebtables / iptables insertion of rules is based on position of the rule relative to other existing rules and has nothing to do with nwfilter priority which servers sorting of rules relative to each other beyond what their occurrence in the XML provides. So the priority doesn't map directly into the position of the rule as ebtables/iptables needs it.
Stefan

This patch adds the capability of adding individual rules to existing chains. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/conf/nwfilter_conf.h | 6 ++ src/nwfilter/nwfilter_ebiptables_driver.c | 73 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 0 deletions(-) diff --git a/src/conf/nwfilter_conf.h b/src/conf/nwfilter_conf.h index 4348378..12d1e0f 100644 --- a/src/conf/nwfilter_conf.h +++ b/src/conf/nwfilter_conf.h @@ -540,6 +540,11 @@ typedef int (*virNWFilterRuleTeardownNewRules)(virConnectPtr conn, typedef int (*virNWFilterRuleTeardownOldRules)(virConnectPtr conn, const char *ifname); +typedef int (*virNWFilterRuleAddRules)(virConnectPtr conn, + const char *ifname, + int nruleInstances, + void **_inst); + typedef int (*virNWFilterRuleRemoveRules)(virConnectPtr conn, const char *ifname, int nruleInstances, @@ -580,6 +585,7 @@ struct _virNWFilterTechDriver { virNWFilterRuleApplyNewRules applyNewRules; virNWFilterRuleTeardownNewRules tearNewRules; virNWFilterRuleTeardownOldRules tearOldRules; + virNWFilterRuleAddRules addRules; virNWFilterRuleRemoveRules removeRules; virNWFilterRuleAllTeardown allTeardown; virNWFilterRuleFreeInstanceData freeRuleInstance; diff --git a/src/nwfilter/nwfilter_ebiptables_driver.c b/src/nwfilter/nwfilter_ebiptables_driver.c index 918625c..1169e5a 100644 --- a/src/nwfilter/nwfilter_ebiptables_driver.c +++ b/src/nwfilter/nwfilter_ebiptables_driver.c @@ -3695,6 +3695,78 @@ err_exit: return rc; } +/** + * ebiptablesAddRules: + * @conn : pointer to virConnect object + * @ifname : the name of the interface to which the rules apply + * @nRuleInstance : the number of given rules + * @_inst : array of rule instantiation data + * + * Add all rules one after the other + * + * Return 0 on success, 1 if execution of one or more cleanup + * commands failed. + */ +static int +ebiptablesAddRules(virConnectPtr conn, + const char *ifname, + int nruleInstances, + void **_inst) +{ + int i; + int cli_status; + ebiptablesRuleInstPtr *inst = (ebiptablesRuleInstPtr *)_inst; + virBuffer buf = VIR_BUFFER_INITIALIZER; + bool haveIptables = false; + bool haveIp6tables = false; + + for (i = 0; i < nruleInstances; i++) { + sa_assert (inst); + switch (inst[i]->ruleType) { + case RT_EBTABLES: + ebiptablesInstCommand(&buf, + inst[i]->commandTemplate, + 'A', -1, 1); + break; + case RT_IPTABLES: + if (inst[i]->ruleType == RT_IPTABLES) + iptablesInstCommand(&buf, + inst[i]->commandTemplate, + 'A', -1, 1); + haveIptables = true; + break; + case RT_IP6TABLES: + if (inst[i]->ruleType == RT_IP6TABLES) + iptablesInstCommand(&buf, + inst[i]->commandTemplate, + 'A', -1, 1); + haveIp6tables = true; + break; + } + } + + if (ebiptablesExecCLI(&buf, &cli_status) || cli_status != 0) + goto err_exit; + + if (haveIptables) + iptablesCheckBridgeNFCallEnabled(false); + + if (haveIp6tables) + iptablesCheckBridgeNFCallEnabled(true); + + return 0; + +err_exit: + (void) ebiptablesRemoveRules(conn, ifname, nruleInstances, _inst); + + virNWFilterReportError(VIR_ERR_BUILD_FIREWALL, + _("Some rules could not be created for " + "interface %s."), + ifname); + + return 1; +} + /** * ebiptablesAllTeardown: @@ -3751,6 +3823,7 @@ virNWFilterTechDriver ebiptables_driver = { .tearNewRules = ebiptablesTearNewRules, .tearOldRules = ebiptablesTearOldRules, .allTeardown = ebiptablesAllTeardown, + .addRules = ebiptablesAddRules, .removeRules = ebiptablesRemoveRules, .freeRuleInstance = ebiptablesFreeRuleInstance, .displayRuleInstance = ebiptablesDisplayRuleInstance, -- 1.7.6.4

This patch adds a function that applies or deletes filter rules to existing chains. Rules referencing the given variable are instantiated with the given value, or optionally deleted. For example, passing variable "IP" with different values will install rules using the IP variable with each of the different values. These rules can later be removed by calling this function with the same variable and value and "delete" argument set to "1". Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_gentech_driver.c | 86 ++++++++++++++++++++++++++++++++ src/nwfilter/nwfilter_gentech_driver.h | 11 ++++ 2 files changed, 97 insertions(+), 0 deletions(-) diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 79350ac..563a1f3 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -620,6 +620,92 @@ virNWFilterRuleInstancesToArray(int nEntries, /** + * virNWFilterChangeVar: + * @conn: pointer to virConnect object + * @techdriver: The driver to use for instantiation + * @filter: The filter to instantiate + * @ifname: The name of the interface to apply the rules to + * @vars: A map holding variable names and values used for instantiating + * the filter and its subfilters. + * @var: name of variable to change + * @value: value of variable to change + * @delete: =0 to create or =1 to delete the rules + * + * Returns 0 on success, a value otherwise. + * + * Instantiate or delete a filter and all subfilters with variable "var" + * set to value "value". + * The name of the interface to which the rules belong must be + * provided. + * + * Call this function while holding the NWFilter filter update lock + */ +int +virNWFilterChangeVar(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType nettype, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver, + const char *var, + char *value, + bool delete) +{ + int rc; + int j, nptrs; + int nEntries = 0; + virNWFilterRuleInstPtr *insts = NULL; + void **ptrs = NULL; + bool foundNewFilter = 0; + + if (virNWFilterHashTablePut(vars, var, value, 1)) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Cound not add " + "variable \"%s\" to hashmap"), var); + return 1; + } + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + NWFILTER_STD_VAR_IP, 0, + &nEntries, &insts, + INSTANTIATE_ALWAYS, &foundNewFilter, + driver); + if (rc) + goto err_exit; + rc = virNWFilterRuleInstancesToArray(nEntries, insts, &ptrs, &nptrs); + if (rc) + goto err_exit; + + if (virNWFilterHashTableRemoveEntry(vars, var) < 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Could not remove " + "variable \"%s\" from hashmap"), var); + return 1; + } + + if (virNWFilterLockIface(ifname)) + goto err_exit; + + if (delete) + rc = techdriver->removeRules(conn, ifname, nptrs, ptrs); + else + rc = techdriver->addRules(conn, ifname, nptrs, ptrs); + virNWFilterUnlockIface(ifname); + VIR_FREE(ptrs); + +err_exit: + + for (j = 0; j < nEntries; j++) + virNWFilterRuleInstFree(insts[j]); + VIR_FREE(insts); + return rc; +} + + +/** * virNWFilterInstantiate: * @conn: pointer to virConnect object * @techdriver: The driver to use for instantiation diff --git a/src/nwfilter/nwfilter_gentech_driver.h b/src/nwfilter/nwfilter_gentech_driver.h index fa86030..34e95c7 100644 --- a/src/nwfilter/nwfilter_gentech_driver.h +++ b/src/nwfilter/nwfilter_gentech_driver.h @@ -48,6 +48,17 @@ int virNWFilterRollbackUpdateFilter(virConnectPtr conn, int virNWFilterTearOldFilter(virConnectPtr conn, const virDomainNetDefPtr net); +int virNWFilterChangeVar(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType nettype, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver, + const char *var, + char *value, + bool delete); + int virNWFilterInstantiateFilterLate(virConnectPtr conn, const char *ifname, int ifindex, -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds a function that applies or deletes filter rules to existing chains. Rules referencing the given variable are instantiated with the given value, or optionally deleted. For example, passing variable "IP" with different values will install rules using the IP variable with each of the different values. These rules can later be removed by calling this function with the same variable and value and "delete" argument set to "1".
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_gentech_driver.c | 86 ++++++++++++++++++++++++++++++++ src/nwfilter/nwfilter_gentech_driver.h | 11 ++++ 2 files changed, 97 insertions(+), 0 deletions(-)
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 79350ac..563a1f3 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -620,6 +620,92 @@ virNWFilterRuleInstancesToArray(int nEntries,
/** + * virNWFilterChangeVar: + * @conn: pointer to virConnect object + * @techdriver: The driver to use for instantiation + * @filter: The filter to instantiate + * @ifname: The name of the interface to apply the rules to + * @vars: A map holding variable names and values used for instantiating + * the filter and its subfilters. + * @var: name of variable to change + * @value: value of variable to change + * @delete: =0 to create or =1 to delete the rules + * + * Returns 0 on success, a value otherwise. + * + * Instantiate or delete a filter and all subfilters with variable "var" + * set to value "value". + * The name of the interface to which the rules belong must be + * provided. + * + * Call this function while holding the NWFilter filter update lock + */ +int +virNWFilterChangeVar(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType nettype, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver, + const char *var, + char *value, + bool delete) +{ + int rc; + int j, nptrs; + int nEntries = 0; + virNWFilterRuleInstPtr *insts = NULL; + void **ptrs = NULL; + bool foundNewFilter = 0; + + if (virNWFilterHashTablePut(vars, var, value, 1)) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Cound not add " + "variable \"%s\" to hashmap"), var); + return 1; + } + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + NWFILTER_STD_VAR_IP, 0, +&nEntries,&insts, + INSTANTIATE_ALWAYS,&foundNewFilter, + driver); Given the NWFILTER_STD_VAR_IP parameter, what does it give us at this point? + if (rc) + goto err_exit; + rc = virNWFilterRuleInstancesToArray(nEntries, insts,&ptrs,&nptrs); + if (rc) + goto err_exit; + + if (virNWFilterHashTableRemoveEntry(vars, var)< 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Could not remove " + "variable \"%s\" from hashmap"), var); + return 1; + } + + if (virNWFilterLockIface(ifname)) + goto err_exit; + + if (delete) + rc = techdriver->removeRules(conn, ifname, nptrs, ptrs); + else + rc = techdriver->addRules(conn, ifname, nptrs, ptrs); I am wondering about this addRules() and whether the rules are being added to the end of a chain and thus the rules' assumed priority would have to be such that these rules can actually always be the last ones?
+ virNWFilterUnlockIface(ifname); + VIR_FREE(ptrs); + +err_exit: + + for (j = 0; j< nEntries; j++) + virNWFilterRuleInstFree(insts[j]); + VIR_FREE(insts); + return rc; +} + + +/** * virNWFilterInstantiate: * @conn: pointer to virConnect object * @techdriver: The driver to use for instantiation diff --git a/src/nwfilter/nwfilter_gentech_driver.h b/src/nwfilter/nwfilter_gentech_driver.h index fa86030..34e95c7 100644 --- a/src/nwfilter/nwfilter_gentech_driver.h +++ b/src/nwfilter/nwfilter_gentech_driver.h @@ -48,6 +48,17 @@ int virNWFilterRollbackUpdateFilter(virConnectPtr conn, int virNWFilterTearOldFilter(virConnectPtr conn, const virDomainNetDefPtr net);
+int virNWFilterChangeVar(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType nettype, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver, + const char *var, + char *value, + bool delete); + int virNWFilterInstantiateFilterLate(virConnectPtr conn, const char *ifname, int ifindex,

+int +virNWFilterChangeVar(virConnectPtr conn, .... + if (virNWFilterHashTablePut(vars, var, value, 1)) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Cound not add " + "variable \"%s\" to hashmap"), var); + return 1; + } + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + NWFILTER_STD_VAR_IP, 0, +&nEntries,&insts, + INSTANTIATE_ALWAYS,&foundNewFilter, + driver); Given the NWFILTER_STD_VAR_IP parameter, what does it give us at this
Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/17/2011 09:17:21 AM: point? I'm not sure I understand the question. NWFILTER_STD_VAR_IP (aka "IP") is the variable we want to match, so this will only affect rules that reference "IP". Rules that don't match are not included in the instantiation, and so are unaffected.
+ if (delete) + rc = techdriver->removeRules(conn, ifname, nptrs, ptrs); + else + rc = techdriver->addRules(conn, ifname, nptrs, ptrs); I am wondering about this addRules() and whether the rules are being added to the end of a chain and thus the rules' assumed priority would have to be such that these rules can actually always be the last ones?
Where they are added depends on the original filter. If they have a priority associated with them, it'll use that. It's no different than the original code's instantiation of rules referencing "IP" (other than that this allows multiple occurrences). Without a priority, they appear at the end of the chain. +-DLS

This patch adds DHCP Snooping support to libvirt. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- examples/xml/nwfilter/no-ip-spoofing.xml | 5 + src/Makefile.am | 2 + src/nwfilter/nwfilter_dhcpsnoop.c | 602 ++++++++++++++++++++++++++++++ src/nwfilter/nwfilter_dhcpsnoop.h | 36 ++ src/nwfilter/nwfilter_driver.c | 5 + src/nwfilter/nwfilter_gentech_driver.c | 83 +++-- 6 files changed, 708 insertions(+), 25 deletions(-) create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index 2fccd12..2ae9500 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -4,4 +4,9 @@ <rule action='return' direction='out'> <ip match='yes' srcipaddr='$IP' /> </rule> + <!-- allow DHCP requests --> + <rule action='return' direction='out'> + <ip match='yes' srcipaddr='0.0.0.0' protocol='udp' srcportstart='68' + srcportend='68' /> + </rule> </filter> diff --git a/src/Makefile.am b/src/Makefile.am index 738ee91..f6d3fdd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -473,6 +473,8 @@ NWFILTER_DRIVER_SOURCES = \ nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \ nwfilter/nwfilter_gentech_driver.c \ nwfilter/nwfilter_gentech_driver.h \ + nwfilter/nwfilter_dhcpsnoop.c \ + nwfilter/nwfilter_dhcpsnoop.h \ nwfilter/nwfilter_ebiptables_driver.c \ nwfilter/nwfilter_ebiptables_driver.h \ nwfilter/nwfilter_learnipaddr.c \ diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c new file mode 100644 index 0000000..f784a29 --- /dev/null +++ b/src/nwfilter/nwfilter_dhcpsnoop.c @@ -0,0 +1,602 @@ + +/* + * nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM + * on an interface + * + * Copyright (C) 2011 IBM Corp. + * Copyright (C) 2011 David L Stevens + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David L Stevens <dlstevens@us.ibm.com> + * Based in part on work by Stefan Berger <stefanb@us.ibm.com> + */ + +#include <config.h> + +#ifdef HAVE_LIBPCAP +#include <pcap.h> +#endif + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <signal.h> + +#include <arpa/inet.h> +#include <net/ethernet.h> +#include <netinet/ip.h> +#include <netinet/udp.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <intprops.h> + +#include "internal.h" + +#include "buf.h" +#include "memory.h" +#include "logging.h" +#include "datatypes.h" +#include "interface.h" +#include "virterror_internal.h" +#include "threads.h" +#include "conf/nwfilter_params.h" +#include "conf/domain_conf.h" +#include "nwfilter_gentech_driver.h" +#include "nwfilter_ebiptables_driver.h" +#include "nwfilter_dhcpsnoop.h" + +#define VIR_FROM_THIS VIR_FROM_NWFILTER + +static virHashTablePtr SnoopReqs; + +struct virNWFilterSnoopReq { + virConnectPtr conn; + virNWFilterTechDriverPtr techdriver; + enum virDomainNetType nettype; + virNWFilterDefPtr filter; + const char *ifname; + virNWFilterHashTablePtr vars; + virNWFilterDriverStatePtr driver; + pthread_t thread; + /* start and end of lease list, ordered by lease time */ + struct iplease *start; + struct iplease *end; + bool die; +}; + +#define POLL_INTERVAL 10*1000 /* 10 secs */ + +struct iplease { + uint32_t ipl_ipaddr; + uint32_t ipl_server; + struct virNWFilterSnoopReq *ipl_req; + unsigned int ipl_timeout; + /* timer list */ + struct iplease *ipl_prev; + struct iplease *ipl_next; +}; + +static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr); +static void ipl_update(struct iplease *pl, uint32_t timeout); + + +/* + * ipl_ladd - add an IP lease to a list + */ +static void +ipl_ladd(struct iplease *plnew, struct iplease **start, struct iplease **end) +{ + struct iplease *pl; + + plnew->ipl_next = plnew->ipl_prev = 0; + if (!*start) { + plnew->ipl_prev = plnew->ipl_next = 0; + *start = *end = plnew; + return; + } + for (pl = *end; pl && plnew->ipl_timeout < pl->ipl_timeout; + pl = pl->ipl_prev) + /* empty */ ; + if (!pl) { + plnew->ipl_next = *start; + *start = plnew; + } else { + plnew->ipl_next = pl->ipl_next; + pl->ipl_next = plnew; + } + plnew->ipl_prev = pl; + if (plnew->ipl_next) + plnew->ipl_next->ipl_prev = plnew; + else + *end = plnew; +} + +/* + * ipl_tadd - add an IP lease to the timer list + */ +static void +ipl_tadd(struct iplease *plnew) +{ + struct virNWFilterSnoopReq *req = plnew->ipl_req; + + ipl_ladd(plnew, &req->start, &req->end); +} + +/* + * ipl_add - create or update an IP lease + */ +static void +ipl_add(struct iplease *plnew) +{ + struct iplease *pl; + int rc; + char ipbuf[20]; /* dotted decimal IP addr string */ + char *ipstr; + + pl = ipl_getbyip(plnew->ipl_req->start, plnew->ipl_ipaddr); + if (pl) { + ipl_update(pl, plnew->ipl_timeout); + return; + } + if (VIR_ALLOC(pl) < 0) { + virReportOOMError(); + return; + } + *pl = *plnew; + + if (!inet_ntop(AF_INET, &pl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_add inet_ntop " "failed (0x%08X)"), + pl->ipl_ipaddr); + VIR_FREE(pl); + return; + + } + + ipstr = strdup(ipbuf); + if (!ipstr) { + VIR_FREE(pl); + virReportOOMError(); + return; + } + rc = virNWFilterChangeVar(pl->ipl_req->conn, + pl->ipl_req->techdriver, + pl->ipl_req->nettype, + pl->ipl_req->filter, + pl->ipl_req->ifname, + pl->ipl_req->vars, + pl->ipl_req->driver, "IP", ipstr, 0); + if (rc) { + VIR_FREE(ipstr); + VIR_FREE(pl); + return; + } + ipl_tadd(pl); +} + +/* + * ipl_tdel - remove an IP lease from a list + */ +static void +ipl_ldel(struct iplease *ipl, struct iplease **start, struct iplease **end) +{ + if (ipl->ipl_prev) + ipl->ipl_prev->ipl_next = ipl->ipl_next; + else + *start = ipl->ipl_next; + if (ipl->ipl_next) + ipl->ipl_next->ipl_prev = ipl->ipl_prev; + else + *end = ipl->ipl_prev; + ipl->ipl_next = ipl->ipl_prev = 0; +} + +/* + * ipl_tdel - remove an IP lease from the timer list + */ +static void +ipl_tdel(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req = ipl->ipl_req; + + ipl_ldel(ipl, &req->start, &req->end); + ipl->ipl_timeout = 0; +} + +/* + * ipl_del - delete an IP lease + */ +static void +ipl_del(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req; + char ipbuf[20]; /* dotted decimal IP addr string */ + char *ipstr; + + if (!ipl) + return; + + req = ipl->ipl_req; + + ipl_tdel(ipl); + + if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { + ipstr = strdup(ipbuf); + if (virNWFilterChangeVar(req->conn, + req->techdriver, + req->nettype, + req->filter, + req->ifname, + req->vars, req->driver, "IP", ipstr, 1)) + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_del virNWFilterChangeVar failed; " + "filter not deleted")); + } else + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_del inet_ntop " "failed (0x%08X)"), + ipl->ipl_ipaddr); + VIR_FREE(ipl); +} + +/* + * ipl_update - update the timeout on an IP lease + */ +static void +ipl_update(struct iplease *ipl, uint32_t timeout) +{ + ipl_tdel(ipl); + ipl->ipl_timeout = timeout; + ipl_tadd(ipl); + return; +} + +/* + * ipl_getbyip - lookup IP lease by IP address + */ +static struct iplease * +ipl_getbyip(struct iplease *start, uint32_t ipaddr) +{ + struct iplease *pl; + + for (pl = start; pl && pl->ipl_ipaddr != ipaddr; pl = pl->ipl_next) + /* empty */ ; + return pl; +} + +#define GRACE 5 + +/* + * ipl_trun - run the IP lease timeout list + */ +static unsigned int +ipl_trun(struct virNWFilterSnoopReq *req) +{ + uint32_t now; + + now = time(0); + while (req->start && req->start->ipl_timeout <= now) + ipl_del(req->start); + return 0; +} + +typedef unsigned char Eaddr[6]; + +struct eth { + Eaddr eh_dst; + Eaddr eh_src; + unsigned short eh_type; + unsigned char eh_data[0]; +}; + +struct dhcp { + unsigned char d_op; + unsigned char d_htype; + unsigned char d_hlen; + unsigned char d_hops; + unsigned int d_xid; + unsigned short d_secs; + unsigned short d_flags; + unsigned int d_ciaddr; + unsigned int d_yiaddr; + unsigned int d_siaddr; + unsigned int d_giaddr; + unsigned char d_chaddr[16]; + char d_sname[64]; + char d_file[128]; + unsigned char d_opts[0]; +}; + +/* DHCP options */ + +#define DHCPO_PAD 0 +#define DHCPO_LEASE 51 /* lease time in secs */ +#define DHCPO_MTYPE 53 /* message type */ +#define DHCPO_END 255 /* end of options */ + +/* DHCP message types */ +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPRELEASE 7 + +unsigned char dhcp_magic[4] = { 99, 130, 83, 99 }; + +static int +dhcp_getopt(struct dhcp *pd, int len, int *pmtype, int *pleasetime) +{ + int oind, olen; + int oend; + + olen = len - sizeof *pd; + oind = 0; + + if (olen < 4) /* bad magic */ + return -1; + for (oind = 0; oind < sizeof dhcp_magic; ++oind) + if (pd->d_opts[oind] != dhcp_magic[oind]) + return -1; /* bad magic */ + + oend = 0; + + *pmtype = *pleasetime = 0; + + while (oind < olen) { + switch (pd->d_opts[oind]) { + case DHCPO_LEASE: + if (olen - oind < 6) + goto malformed; + if (*pleasetime) + return -1; /* duplicate lease time */ + *pleasetime = + ntohl(*(unsigned int *) (pd->d_opts + oind + 2)); + break; + case DHCPO_MTYPE: + if (olen - oind < 3) + goto malformed; + if (*pmtype) + return -1; /* duplicate message type */ + *pmtype = pd->d_opts[oind + 2]; + break; + case DHCPO_PAD: + oind++; + continue; + + case DHCPO_END: + oend = 1; + break; + default: + if (olen - oind < 2) + goto malformed; + } + if (oend) + break; + oind += pd->d_opts[oind + 1] + 2; + } + return 0; + malformed: + VIR_WARN("got lost in the options!"); + return -1; +} + +static void +dhcpdecode(struct virNWFilterSnoopReq *req, struct eth *pep, int len) +{ + struct iphdr *pip; + struct udphdr *pup; + struct dhcp *pd; + struct iplease ipl, *pipl; + int mtype, leasetime; + + /* go through the protocol headers */ + pip = (struct iphdr *) pep->eh_data; + len -= sizeof(*pep); + pup = (struct udphdr *) ((char *) pip + (pip->ihl << 2)); + len -= pip->ihl << 2; + pd = (struct dhcp *) ((char *) pup + sizeof(*pup)); + len -= sizeof(*pup); + if (len < 0) + return; /* dhcpdecode: invalid packet length */ + if (dhcp_getopt(pd, len, &mtype, &leasetime) < 0) + return; + + memset(&ipl, 0, sizeof(ipl)); + ipl.ipl_ipaddr = pd->d_yiaddr; + ipl.ipl_server = pd->d_siaddr; + if (leasetime == ~0) + ipl.ipl_timeout = ~0; + else + ipl.ipl_timeout = time(0) + leasetime; + ipl.ipl_req = req; + + switch (mtype) { + case DHCPACK: + ipl_add(&ipl); + break; + case DHCPDECLINE: + case DHCPRELEASE: + pipl = ipl_getbyip(req->start, ipl.ipl_ipaddr); + ipl_del(pipl); + break; + default: + break; + } +} + +#define PBUFSIZE 576 /* >= IP/TCP/DHCP headers */ + +static pcap_t * +dhcpopen(const char *intf) +{ + pcap_t *handle; + struct bpf_program fp; + char filter[64]; + char pcap_errbuf[PCAP_ERRBUF_SIZE]; + + handle = pcap_open_live(intf, PBUFSIZE, 0, POLL_INTERVAL, pcap_errbuf); + if (handle == NULL) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_open_live: %s"), pcap_errbuf); + return 0; + } + + sprintf(filter, "port 67 or dst port 68"); + if (pcap_compile(handle, &fp, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_compile: %s"), pcap_geterr(handle)); + return 0; + } + if (pcap_setfilter(handle, &fp) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_setfilter: %s"), pcap_geterr(handle)); + return 0; + } + pcap_freecode(&fp); + return handle; +} + +static void * +virNWFilterDHCPSnoop(void *req0) +{ + struct virNWFilterSnoopReq *req = req0; + pcap_t *handle; + struct pcap_pkthdr hdr; + struct eth *packet; + struct iplease *ipl; + int ifindex; + + handle = dhcpopen(req->ifname); + if (!handle) + return 0; + + ifindex = if_nametoindex(req->ifname); + + while (1) { + if (req->die) + break; + ipl_trun(req); + + packet = (struct eth *) pcap_next(handle, &hdr); + + if (!packet) { + if (ifaceCheck(false, req->ifname, NULL, ifindex)) + virNWFilterDHCPSnoopEnd(req->ifname); + continue; + } + + dhcpdecode(req, packet, hdr.caplen); + } + /* free all leases */ + for (ipl = req->start; ipl; ipl = req->start) + ipl_del(ipl); + + /* free all req data */ + VIR_FREE(req->ifname); + virNWFilterHashTableFree(req->vars); + VIR_FREE(req); + return 0; +} + +int +virNWFilterDHCPSnoopReq(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver ATTRIBUTE_UNUSED, + enum virDomainNetType nettype ATTRIBUTE_UNUSED, + virNWFilterDefPtr filter ATTRIBUTE_UNUSED, + const char *ifname ATTRIBUTE_UNUSED, + virNWFilterHashTablePtr vars ATTRIBUTE_UNUSED, + virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req; + + req = virHashLookup(SnoopReqs, ifname); + if (req) + return 0; + if (VIR_ALLOC(req) < 0) { + virReportOOMError(); + return 1; + } + + req->conn = conn; + req->techdriver = techdriver; + req->nettype = nettype; + req->filter = filter; + req->ifname = strdup(ifname); + req->vars = virNWFilterHashTableCreate(0); + if (!req->vars) { + virReportOOMError(); + return 1; + } + if (virNWFilterHashTablePutAll(vars, req->vars)) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq: can't copy variables" + " on if %s"), ifname); + return 1; + } + req->driver = driver; + req->start = req->end = 0; + + if (virHashAddEntry(SnoopReqs, ifname, req)) { + VIR_FREE(req); + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq req add failed on" + "interface \"%s\""), ifname); + return 1; + } + if (pthread_create(&req->thread, NULL, virNWFilterDHCPSnoop, req) != 0) { + (void) virHashRemoveEntry(SnoopReqs, ifname); + VIR_FREE(req); + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq pthread_create failed" + " oninterface \"%s\""), ifname); + return 1; + } + return 0; +} + +/* + * freeReq - hash table free function to kill a request + */ +static void +freeReq(void *req0, const void *name ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req = (struct virNWFilterSnoopReq *) req0; + + if (!req) + return; + + req->die = 1; +} + +int +virNWFilterDHCPSnoopInit(void) +{ + if (SnoopReqs) + return 0; + SnoopReqs = virHashCreate(0, freeReq); + if (!SnoopReqs) { + virReportOOMError(); + return -1; + } + return 0; +} + +void +virNWFilterDHCPSnoopEnd(const char *ifname) +{ + if (!SnoopReqs) + return; + if (ifname) + virHashRemoveEntry(SnoopReqs, ifname); + else /* free all of them */ + virHashFree(SnoopReqs); +} diff --git a/src/nwfilter/nwfilter_dhcpsnoop.h b/src/nwfilter/nwfilter_dhcpsnoop.h new file mode 100644 index 0000000..248b1a0 --- /dev/null +++ b/src/nwfilter/nwfilter_dhcpsnoop.h @@ -0,0 +1,36 @@ +/* + * nwfilter_dhcpsnoop.h: support DHCP snooping for a VM on an interface + * + * Copyright (C) 2010 IBM Corp. + * Copyright (C) 2010 David L Stevens + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David L Stevens <dlstevens@us.ibm.com> + */ + +#ifndef __NWFILTER_DHCPSNOOP_H +# define __NWFILTER_DHCPSNOOP_H + +int virNWFilterDHCPSnoopInit(void); +int virNWFilterDHCPSnoopReq(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver); +void virNWFilterDHCPSnoopEnd(const char *ifname); +#endif /* __NWFILTER_DHCPSNOOP_H */ diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c index a735059..57eb52c 100644 --- a/src/nwfilter/nwfilter_driver.c +++ b/src/nwfilter/nwfilter_driver.c @@ -39,6 +39,7 @@ #include "nwfilter_gentech_driver.h" #include "configmake.h" +#include "nwfilter_dhcpsnoop.h" #include "nwfilter_learnipaddr.h" #define VIR_FROM_THIS VIR_FROM_NWFILTER @@ -66,6 +67,8 @@ static int nwfilterDriverStartup(int privileged) { char *base = NULL; + if (virNWFilterDHCPSnoopInit() < 0) + return -1; if (virNWFilterLearnInit() < 0) return -1; @@ -127,6 +130,7 @@ alloc_err_exit: conf_init_err: virNWFilterTechDriversShutdown(); + virNWFilterDHCPSnoopEnd(0); virNWFilterLearnShutdown(); return -1; @@ -201,6 +205,7 @@ nwfilterDriverShutdown(void) { virNWFilterConfLayerShutdown(); virNWFilterTechDriversShutdown(); + virNWFilterDHCPSnoopEnd(0); virNWFilterLearnShutdown(); nwfilterDriverLock(driverState); diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 563a1f3..577b48d 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -33,6 +33,7 @@ #include "virterror_internal.h" #include "nwfilter_gentech_driver.h" #include "nwfilter_ebiptables_driver.h" +#include "nwfilter_dhcpsnoop.h" #include "nwfilter_learnipaddr.h" @@ -42,6 +43,8 @@ #define NWFILTER_STD_VAR_MAC "MAC" #define NWFILTER_STD_VAR_IP "IP" +#define NWFILTER_DFLT_LEARN "DHCP" + static int _virNWFilterTeardownFilter(const char *ifname); @@ -747,6 +750,8 @@ virNWFilterInstantiate(virConnectPtr conn, void **ptrs = NULL; int instantiate = 1; char *buf; + const char *learning = NWFILTER_DFLT_LEARN; + bool reportIP = false; virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0); if (!missing_vars) { @@ -764,39 +769,65 @@ virNWFilterInstantiate(virConnectPtr conn, if (rc) goto err_exit; + learning = virHashLookup(vars->hashTable, "ip_learning"); + if (virHashSize(missing_vars->hashTable) == 1) { if (virHashLookup(missing_vars->hashTable, NWFILTER_STD_VAR_IP) != NULL) { - if (virNWFilterLookupLearnReq(ifindex) == NULL) { - rc = virNWFilterLearnIPAddress(techdriver, - ifname, - ifindex, - linkdev, - nettype, macaddr, - filter->name, - vars, driver, - DETECT_DHCP|DETECT_STATIC); + if (strcasecmp(learning, "none") == 0) { /* no learning */ + reportIP = true; + goto err_unresolvable_vars; } - goto err_exit; - } - goto err_unresolvable_vars; + if (strcasecmp(learning, "dhcp") == 0) { + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + NWFILTER_STD_VAR_IP, 1, + &nEntries, &insts, + useNewFilter, foundNewFilter, + driver); + if (!rc) + rc = virNWFilterDHCPSnoopReq(conn, techdriver, nettype, + filter, ifname, vars, driver); + } else if (strcasecmp(learning, "any") == 0) { + if (virNWFilterLookupLearnReq(ifindex) == NULL) { + rc = virNWFilterLearnIPAddress(techdriver, + ifname, + ifindex, + linkdev, + nettype, macaddr, + filter->name, + vars, driver, + DETECT_DHCP|DETECT_STATIC); + } + goto err_exit; + } else { + rc = 1; + virNWFilterReportError(VIR_ERR_PARSE_FAILED, _("filter '%s' " + "learning value '%s' invalid."), + filter->name, learning); + } + } else + goto err_unresolvable_vars; } else if (virHashSize(missing_vars->hashTable) > 1) { goto err_unresolvable_vars; } else if (!forceWithPendingReq && virNWFilterLookupLearnReq(ifindex) != NULL) { goto err_exit; - } - - rc = _virNWFilterInstantiateRec(conn, - techdriver, - nettype, - filter, - ifname, - vars, - 0, 0, - &nEntries, &insts, - useNewFilter, foundNewFilter, - driver); + } else + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + 0, 0, + &nEntries, &insts, + useNewFilter, foundNewFilter, + driver); if (rc) goto err_exit; @@ -848,7 +879,7 @@ err_exit: err_unresolvable_vars: - buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, false); + buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, reportIP); if (buf) { virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot instantiate filter due to unresolvable " @@ -1180,6 +1211,8 @@ _virNWFilterTeardownFilter(const char *ifname) return 1; } + virNWFilterDHCPSnoopEnd(ifname); + virNWFilterTerminateLearnReq(ifname); if (virNWFilterLockIface(ifname)) -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds DHCP Snooping support to libvirt.
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- examples/xml/nwfilter/no-ip-spoofing.xml | 5 + src/Makefile.am | 2 + src/nwfilter/nwfilter_dhcpsnoop.c | 602 ++++++++++++++++++++++++++++++ src/nwfilter/nwfilter_dhcpsnoop.h | 36 ++ src/nwfilter/nwfilter_driver.c | 5 + src/nwfilter/nwfilter_gentech_driver.c | 83 +++-- 6 files changed, 708 insertions(+), 25 deletions(-) create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h
diff --git a/examples/xml/nwfilter/no-ip-spoofing.xml b/examples/xml/nwfilter/no-ip-spoofing.xml index 2fccd12..2ae9500 100644 --- a/examples/xml/nwfilter/no-ip-spoofing.xml +++ b/examples/xml/nwfilter/no-ip-spoofing.xml @@ -4,4 +4,9 @@ <rule action='return' direction='out'> <ip match='yes' srcipaddr='$IP' /> </rule> +<!-- allow DHCP requests --> +<rule action='return' direction='out'> +<ip match='yes' srcipaddr='0.0.0.0' protocol='udp' srcportstart='68' + srcportend='68' /> +</rule> </filter> diff --git a/src/Makefile.am b/src/Makefile.am index 738ee91..f6d3fdd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -473,6 +473,8 @@ NWFILTER_DRIVER_SOURCES = \ nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \ nwfilter/nwfilter_gentech_driver.c \ nwfilter/nwfilter_gentech_driver.h \ + nwfilter/nwfilter_dhcpsnoop.c \ + nwfilter/nwfilter_dhcpsnoop.h \ nwfilter/nwfilter_ebiptables_driver.c \ nwfilter/nwfilter_ebiptables_driver.h \ nwfilter/nwfilter_learnipaddr.c \ diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c new file mode 100644 index 0000000..f784a29 --- /dev/null +++ b/src/nwfilter/nwfilter_dhcpsnoop.c @@ -0,0 +1,602 @@ + +/* + * nwfilter_dhcpsnoop.c: support for DHCP snooping used by a VM + * on an interface + * + * Copyright (C) 2011 IBM Corp. + * Copyright (C) 2011 David L Stevens + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David L Stevens<dlstevens@us.ibm.com> + * Based in part on work by Stefan Berger<stefanb@us.ibm.com> + */ + +#include<config.h> + +#ifdef HAVE_LIBPCAP +#include<pcap.h> +#endif + +#include<fcntl.h> +#include<sys/ioctl.h> +#include<signal.h> + +#include<arpa/inet.h> +#include<net/ethernet.h> +#include<netinet/ip.h> +#include<netinet/udp.h> +#include<net/if.h> +#include<net/if_arp.h> +#include<intprops.h> + +#include "internal.h" + +#include "buf.h" +#include "memory.h" +#include "logging.h" +#include "datatypes.h" +#include "interface.h" +#include "virterror_internal.h" +#include "threads.h" +#include "conf/nwfilter_params.h" +#include "conf/domain_conf.h" +#include "nwfilter_gentech_driver.h" +#include "nwfilter_ebiptables_driver.h" +#include "nwfilter_dhcpsnoop.h" + +#define VIR_FROM_THIS VIR_FROM_NWFILTER + +static virHashTablePtr SnoopReqs; + +struct virNWFilterSnoopReq { + virConnectPtr conn; + virNWFilterTechDriverPtr techdriver; + enum virDomainNetType nettype; + virNWFilterDefPtr filter; + const char *ifname; + virNWFilterHashTablePtr vars; + virNWFilterDriverStatePtr driver; + pthread_t thread; + /* start and end of lease list, ordered by lease time */ + struct iplease *start; + struct iplease *end; + bool die; +}; + +#define POLL_INTERVAL 10*1000 /* 10 secs */ + +struct iplease { + uint32_t ipl_ipaddr; + uint32_t ipl_server; + struct virNWFilterSnoopReq *ipl_req; + unsigned int ipl_timeout; + /* timer list */ + struct iplease *ipl_prev; + struct iplease *ipl_next; +}; + +static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr); +static void ipl_update(struct iplease *pl, uint32_t timeout); + + +/* + * ipl_ladd - add an IP lease to a list + */ +static void +ipl_ladd(struct iplease *plnew, struct iplease **start, struct iplease **end) +{ + struct iplease *pl; + + plnew->ipl_next = plnew->ipl_prev = 0; + if (!*start) { + plnew->ipl_prev = plnew->ipl_next = 0; same as done 2 lines above? + *start = *end = plnew; + return; + } + for (pl = *end; pl&& plnew->ipl_timeout< pl->ipl_timeout; + pl = pl->ipl_prev) + /* empty */ ; + if (!pl) { + plnew->ipl_next = *start; + *start = plnew; + } else { + plnew->ipl_next = pl->ipl_next; + pl->ipl_next = plnew; + } + plnew->ipl_prev = pl; + if (plnew->ipl_next) + plnew->ipl_next->ipl_prev = plnew; + else + *end = plnew; +} + +/* + * ipl_tadd - add an IP lease to the timer list + */ +static void +ipl_tadd(struct iplease *plnew) +{ + struct virNWFilterSnoopReq *req = plnew->ipl_req; + + ipl_ladd(plnew,&req->start,&req->end); +} + +/* + * ipl_add - create or update an IP lease + */ +static void +ipl_add(struct iplease *plnew) +{ + struct iplease *pl; + int rc; + char ipbuf[20]; /* dotted decimal IP addr string */ + char *ipstr; + + pl = ipl_getbyip(plnew->ipl_req->start, plnew->ipl_ipaddr); + if (pl) { + ipl_update(pl, plnew->ipl_timeout); + return; + } + if (VIR_ALLOC(pl)< 0) { + virReportOOMError(); + return; + } + *pl = *plnew; + + if (!inet_ntop(AF_INET,&pl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_add inet_ntop " "failed (0x%08X)"), + pl->ipl_ipaddr); + VIR_FREE(pl); + return; + + } + + ipstr = strdup(ipbuf); + if (!ipstr) { + VIR_FREE(pl); + virReportOOMError(); + return; + } + rc = virNWFilterChangeVar(pl->ipl_req->conn, + pl->ipl_req->techdriver, + pl->ipl_req->nettype, + pl->ipl_req->filter, + pl->ipl_req->ifname, + pl->ipl_req->vars, + pl->ipl_req->driver, "IP", ipstr, 0); + if (rc) { + VIR_FREE(ipstr); + VIR_FREE(pl); + return; + } + ipl_tadd(pl); +} + +/* + * ipl_tdel - remove an IP lease from a list + */ +static void +ipl_ldel(struct iplease *ipl, struct iplease **start, struct iplease **end) +{ + if (ipl->ipl_prev) + ipl->ipl_prev->ipl_next = ipl->ipl_next; + else + *start = ipl->ipl_next; + if (ipl->ipl_next) + ipl->ipl_next->ipl_prev = ipl->ipl_prev; + else + *end = ipl->ipl_prev; + ipl->ipl_next = ipl->ipl_prev = 0; +} + +/* + * ipl_tdel - remove an IP lease from the timer list + */ +static void +ipl_tdel(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req = ipl->ipl_req; + + ipl_ldel(ipl,&req->start,&req->end); + ipl->ipl_timeout = 0; +} + +/* + * ipl_del - delete an IP lease + */ +static void +ipl_del(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req; + char ipbuf[20]; /* dotted decimal IP addr string */ + char *ipstr; + + if (!ipl) + return; + + req = ipl->ipl_req; + + ipl_tdel(ipl); + + if (inet_ntop(AF_INET,&ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { + ipstr = strdup(ipbuf); + if (virNWFilterChangeVar(req->conn, + req->techdriver, + req->nettype, + req->filter, + req->ifname, + req->vars, req->driver, "IP", ipstr, 1)) + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_del virNWFilterChangeVar failed; " + "filter not deleted")); + } else + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("ipl_del inet_ntop " "failed (0x%08X)"), + ipl->ipl_ipaddr); + VIR_FREE(ipl); +} + +/* + * ipl_update - update the timeout on an IP lease + */ +static void +ipl_update(struct iplease *ipl, uint32_t timeout) +{ + ipl_tdel(ipl); + ipl->ipl_timeout = timeout; + ipl_tadd(ipl); + return; +} + +/* + * ipl_getbyip - lookup IP lease by IP address + */ +static struct iplease * +ipl_getbyip(struct iplease *start, uint32_t ipaddr) +{ + struct iplease *pl; + + for (pl = start; pl&& pl->ipl_ipaddr != ipaddr; pl = pl->ipl_next) + /* empty */ ; + return pl; +} + +#define GRACE 5 + +/* + * ipl_trun - run the IP lease timeout list + */ +static unsigned int +ipl_trun(struct virNWFilterSnoopReq *req) +{ + uint32_t now; + + now = time(0); + while (req->start&& req->start->ipl_timeout<= now) + ipl_del(req->start); + return 0; +} + +typedef unsigned char Eaddr[6]; + +struct eth { + Eaddr eh_dst; + Eaddr eh_src; + unsigned short eh_type; + unsigned char eh_data[0]; +}; + +struct dhcp { + unsigned char d_op; + unsigned char d_htype; + unsigned char d_hlen; + unsigned char d_hops; + unsigned int d_xid; + unsigned short d_secs; + unsigned short d_flags; + unsigned int d_ciaddr; + unsigned int d_yiaddr; + unsigned int d_siaddr; + unsigned int d_giaddr; + unsigned char d_chaddr[16]; + char d_sname[64]; + char d_file[128]; + unsigned char d_opts[0]; +}; + +/* DHCP options */ + +#define DHCPO_PAD 0 +#define DHCPO_LEASE 51 /* lease time in secs */ +#define DHCPO_MTYPE 53 /* message type */ +#define DHCPO_END 255 /* end of options */ + +/* DHCP message types */ +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPRELEASE 7 + +unsigned char dhcp_magic[4] = { 99, 130, 83, 99 }; + +static int +dhcp_getopt(struct dhcp *pd, int len, int *pmtype, int *pleasetime) +{ + int oind, olen; + int oend; + + olen = len - sizeof *pd; + oind = 0; + + if (olen< 4) /* bad magic */ + return -1; + for (oind = 0; oind< sizeof dhcp_magic; ++oind) + if (pd->d_opts[oind] != dhcp_magic[oind]) + return -1; /* bad magic */ + Why not just use memcmp() ? + oend = 0; + + *pmtype = *pleasetime = 0; + + while (oind< olen) { + switch (pd->d_opts[oind]) { + case DHCPO_LEASE: + if (olen - oind< 6) + goto malformed; + if (*pleasetime) + return -1; /* duplicate lease time */ + *pleasetime = + ntohl(*(unsigned int *) (pd->d_opts + oind + 2)); I'd cast to (uint32_t *). + break; + case DHCPO_MTYPE: + if (olen - oind< 3) + goto malformed; + if (*pmtype) + return -1; /* duplicate message type */ + *pmtype = pd->d_opts[oind + 2]; + break; + case DHCPO_PAD: + oind++; + continue; + + case DHCPO_END: + oend = 1; + break; + default: + if (olen - oind< 2) + goto malformed; + } + if (oend) + break; + oind += pd->d_opts[oind + 1] + 2; + } + return 0; + malformed: + VIR_WARN("got lost in the options!"); + return -1; +} + +static void +dhcpdecode(struct virNWFilterSnoopReq *req, struct eth *pep, int len) +{ + struct iphdr *pip; + struct udphdr *pup; + struct dhcp *pd; + struct iplease ipl, *pipl; + int mtype, leasetime; + + /* go through the protocol headers */ + pip = (struct iphdr *) pep->eh_data; + len -= sizeof(*pep); + pup = (struct udphdr *) ((char *) pip + (pip->ihl<< 2)); + len -= pip->ihl<< 2; + pd = (struct dhcp *) ((char *) pup + sizeof(*pup)); + len -= sizeof(*pup); + if (len< 0) + return; /* dhcpdecode: invalid packet length */ + if (dhcp_getopt(pd, len,&mtype,&leasetime)< 0) + return; + + memset(&ipl, 0, sizeof(ipl)); + ipl.ipl_ipaddr = pd->d_yiaddr; + ipl.ipl_server = pd->d_siaddr; + if (leasetime == ~0) + ipl.ipl_timeout = ~0; + else + ipl.ipl_timeout = time(0) + leasetime; + ipl.ipl_req = req; + + switch (mtype) { + case DHCPACK: + ipl_add(&ipl); + break; + case DHCPDECLINE: + case DHCPRELEASE: + pipl = ipl_getbyip(req->start, ipl.ipl_ipaddr); + ipl_del(pipl); + break; + default: + break; + } +} + +#define PBUFSIZE 576 /*>= IP/TCP/DHCP headers */ + +static pcap_t * +dhcpopen(const char *intf) +{ + pcap_t *handle; + struct bpf_program fp; + char filter[64]; + char pcap_errbuf[PCAP_ERRBUF_SIZE]; + + handle = pcap_open_live(intf, PBUFSIZE, 0, POLL_INTERVAL, pcap_errbuf); + if (handle == NULL) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_open_live: %s"), pcap_errbuf); + return 0; + } + + sprintf(filter, "port 67 or dst port 68"); + if (pcap_compile(handle,&fp, filter, 1, PCAP_NETMASK_UNKNOWN) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_compile: %s"), pcap_geterr(handle)); + return 0; + } + if (pcap_setfilter(handle,&fp) != 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pcap_setfilter: %s"), pcap_geterr(handle)); + return 0; + } + pcap_freecode(&fp); + return handle; +} + +static void * +virNWFilterDHCPSnoop(void *req0) +{ + struct virNWFilterSnoopReq *req = req0; + pcap_t *handle; + struct pcap_pkthdr hdr; + struct eth *packet; + struct iplease *ipl; + int ifindex; + + handle = dhcpopen(req->ifname); + if (!handle) + return 0; + + ifindex = if_nametoindex(req->ifname); + + while (1) { + if (req->die) + break; + ipl_trun(req); + + packet = (struct eth *) pcap_next(handle,&hdr); + + if (!packet) { + if (ifaceCheck(false, req->ifname, NULL, ifindex)) + virNWFilterDHCPSnoopEnd(req->ifname); + continue; + } + + dhcpdecode(req, packet, hdr.caplen); + } + /* free all leases */ + for (ipl = req->start; ipl; ipl = req->start) + ipl_del(ipl); + + /* free all req data */ + VIR_FREE(req->ifname); + virNWFilterHashTableFree(req->vars); + VIR_FREE(req); + return 0; +} + +int +virNWFilterDHCPSnoopReq(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver ATTRIBUTE_UNUSED, + enum virDomainNetType nettype ATTRIBUTE_UNUSED, + virNWFilterDefPtr filter ATTRIBUTE_UNUSED, + const char *ifname ATTRIBUTE_UNUSED, + virNWFilterHashTablePtr vars ATTRIBUTE_UNUSED, + virNWFilterDriverStatePtr driver ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req; + + req = virHashLookup(SnoopReqs, ifname); + if (req) + return 0; + if (VIR_ALLOC(req)< 0) { + virReportOOMError(); + return 1; + } + + req->conn = conn; + req->techdriver = techdriver; + req->nettype = nettype; + req->filter = filter; + req->ifname = strdup(ifname); + req->vars = virNWFilterHashTableCreate(0); + if (!req->vars) { + virReportOOMError(); + Free req + req->ifname? return 1; + } + if (virNWFilterHashTablePutAll(vars, req->vars)) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq: can't copy variables" + " on if %s"), ifname); Same as above? Consider an error_exit label to goto. + return 1; + } + req->driver = driver; + req->start = req->end = 0; + + if (virHashAddEntry(SnoopReqs, ifname, req)) { Free req->ifname. + VIR_FREE(req); + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq req add failed on" + "interface \"%s\""), ifname); + return 1; + } + if (pthread_create(&req->thread, NULL, virNWFilterDHCPSnoop, req) != 0) { + (void) virHashRemoveEntry(SnoopReqs, ifname); Free req->ifname. + VIR_FREE(req); + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("virNWFilterDHCPSnoopReq pthread_create failed" + " oninterface \"%s\""), ifname); + return 1; + } + return 0; +} + +/* + * freeReq - hash table free function to kill a request + */ +static void +freeReq(void *req0, const void *name ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req = (struct virNWFilterSnoopReq *) req0; + + if (!req) + return; + + req->die = 1; +} + +int +virNWFilterDHCPSnoopInit(void) +{ + if (SnoopReqs) + return 0; + SnoopReqs = virHashCreate(0, freeReq); + if (!SnoopReqs) { + virReportOOMError(); + return -1; + } + return 0; +} + +void +virNWFilterDHCPSnoopEnd(const char *ifname) +{ + if (!SnoopReqs) + return; + if (ifname) + virHashRemoveEntry(SnoopReqs, ifname); + else /* free all of them */ + virHashFree(SnoopReqs); +} diff --git a/src/nwfilter/nwfilter_dhcpsnoop.h b/src/nwfilter/nwfilter_dhcpsnoop.h new file mode 100644 index 0000000..248b1a0 --- /dev/null +++ b/src/nwfilter/nwfilter_dhcpsnoop.h @@ -0,0 +1,36 @@ +/* + * nwfilter_dhcpsnoop.h: support DHCP snooping for a VM on an interface + * + * Copyright (C) 2010 IBM Corp. + * Copyright (C) 2010 David L Stevens + * + * 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David L Stevens<dlstevens@us.ibm.com> + */ + +#ifndef __NWFILTER_DHCPSNOOP_H +# define __NWFILTER_DHCPSNOOP_H + +int virNWFilterDHCPSnoopInit(void); +int virNWFilterDHCPSnoopReq(virConnectPtr conn, + virNWFilterTechDriverPtr techdriver, + enum virDomainNetType, + virNWFilterDefPtr filter, + const char *ifname, + virNWFilterHashTablePtr vars, + virNWFilterDriverStatePtr driver); +void virNWFilterDHCPSnoopEnd(const char *ifname); +#endif /* __NWFILTER_DHCPSNOOP_H */ diff --git a/src/nwfilter/nwfilter_driver.c b/src/nwfilter/nwfilter_driver.c index a735059..57eb52c 100644 --- a/src/nwfilter/nwfilter_driver.c +++ b/src/nwfilter/nwfilter_driver.c @@ -39,6 +39,7 @@ #include "nwfilter_gentech_driver.h" #include "configmake.h"
+#include "nwfilter_dhcpsnoop.h" #include "nwfilter_learnipaddr.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER @@ -66,6 +67,8 @@ static int nwfilterDriverStartup(int privileged) { char *base = NULL;
+ if (virNWFilterDHCPSnoopInit()< 0) + return -1; if (virNWFilterLearnInit()< 0) return -1;
@@ -127,6 +130,7 @@ alloc_err_exit:
conf_init_err: virNWFilterTechDriversShutdown(); + virNWFilterDHCPSnoopEnd(0); Pass 'NULL' to indicate a pointer. virNWFilterLearnShutdown();
return -1; @@ -201,6 +205,7 @@ nwfilterDriverShutdown(void) {
virNWFilterConfLayerShutdown(); virNWFilterTechDriversShutdown(); + virNWFilterDHCPSnoopEnd(0); Same as above. virNWFilterLearnShutdown();
nwfilterDriverLock(driverState); diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 563a1f3..577b48d 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -33,6 +33,7 @@ #include "virterror_internal.h" #include "nwfilter_gentech_driver.h" #include "nwfilter_ebiptables_driver.h" +#include "nwfilter_dhcpsnoop.h" #include "nwfilter_learnipaddr.h"
@@ -42,6 +43,8 @@ #define NWFILTER_STD_VAR_MAC "MAC" #define NWFILTER_STD_VAR_IP "IP"
+#define NWFILTER_DFLT_LEARN "DHCP" + static int _virNWFilterTeardownFilter(const char *ifname);
@@ -747,6 +750,8 @@ virNWFilterInstantiate(virConnectPtr conn, void **ptrs = NULL; int instantiate = 1; char *buf; + const char *learning = NWFILTER_DFLT_LEARN; + bool reportIP = false;
virNWFilterHashTablePtr missing_vars = virNWFilterHashTableCreate(0); if (!missing_vars) { @@ -764,39 +769,65 @@ virNWFilterInstantiate(virConnectPtr conn, if (rc) goto err_exit;
+ learning = virHashLookup(vars->hashTable, "ip_learning"); + if (virHashSize(missing_vars->hashTable) == 1) { if (virHashLookup(missing_vars->hashTable, NWFILTER_STD_VAR_IP) != NULL) { - if (virNWFilterLookupLearnReq(ifindex) == NULL) { - rc = virNWFilterLearnIPAddress(techdriver, - ifname, - ifindex, - linkdev, - nettype, macaddr, - filter->name, - vars, driver, - DETECT_DHCP|DETECT_STATIC); + if (strcasecmp(learning, "none") == 0) { /* no learning */ + reportIP = true; + goto err_unresolvable_vars; } - goto err_exit; - } - goto err_unresolvable_vars; + if (strcasecmp(learning, "dhcp") == 0) { + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + NWFILTER_STD_VAR_IP, 1, +&nEntries,&insts, + useNewFilter, foundNewFilter, + driver); + if (!rc) + rc = virNWFilterDHCPSnoopReq(conn, techdriver, nettype, + filter, ifname, vars, driver); + } else if (strcasecmp(learning, "any") == 0) { + if (virNWFilterLookupLearnReq(ifindex) == NULL) { + rc = virNWFilterLearnIPAddress(techdriver, + ifname, + ifindex, + linkdev, + nettype, macaddr, + filter->name, + vars, driver, + DETECT_DHCP|DETECT_STATIC); + } + goto err_exit; + } else { + rc = 1; + virNWFilterReportError(VIR_ERR_PARSE_FAILED, _("filter '%s' " + "learning value '%s' invalid."), + filter->name, learning); + } + } else + goto err_unresolvable_vars; } else if (virHashSize(missing_vars->hashTable)> 1) { goto err_unresolvable_vars; } else if (!forceWithPendingReq&& virNWFilterLookupLearnReq(ifindex) != NULL) { goto err_exit; - } - - rc = _virNWFilterInstantiateRec(conn, - techdriver, - nettype, - filter, - ifname, - vars, - 0, 0, -&nEntries,&insts, - useNewFilter, foundNewFilter, - driver); + } else + rc = _virNWFilterInstantiateRec(conn, + techdriver, + nettype, + filter, + ifname, + vars, + 0, 0, +&nEntries,&insts, + useNewFilter, foundNewFilter, + driver);
if (rc) goto err_exit; @@ -848,7 +879,7 @@ err_exit:
err_unresolvable_vars:
- buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, false); + buf = virNWFilterPrintVars(missing_vars->hashTable, ", ", false, reportIP); if (buf) { virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("Cannot instantiate filter due to unresolvable " @@ -1180,6 +1211,8 @@ _virNWFilterTeardownFilter(const char *ifname) return 1; }
+ virNWFilterDHCPSnoopEnd(ifname); + virNWFilterTerminateLearnReq(ifname);
if (virNWFilterLockIface(ifname))
Some nits to address. Otherwise looks ok, but didn't try it. Stefan

+ +#include<config.h> + +#ifdef HAVE_LIBPCAP +#include<pcap.h> +#endif HAVE_LIBPCAP also has to be handled in the parts that actually call the
On 10/12/2011 03:50 PM, David L Stevens wrote: pcap library API and provide a way of failing if libpcap is not available.

This patch adds support for saving DHCP snooping leases to an on-disk file and restoring saved leases that are still active on restart. Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_dhcpsnoop.c | 370 +++++++++++++++++++++++++++++++++++-- 1 files changed, 353 insertions(+), 17 deletions(-) diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c index f784a29..eedf550 100644 --- a/src/nwfilter/nwfilter_dhcpsnoop.c +++ b/src/nwfilter/nwfilter_dhcpsnoop.c @@ -56,10 +56,21 @@ #include "nwfilter_gentech_driver.h" #include "nwfilter_ebiptables_driver.h" #include "nwfilter_dhcpsnoop.h" +#include "configmake.h" #define VIR_FROM_THIS VIR_FROM_NWFILTER +#define LEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.leases" +#define TMPLEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.ltmp" +static int lease_fd = -1; +static int nleases = 0; /* number of active leases */ +static int wleases = 0; /* number of written leases */ + static virHashTablePtr SnoopReqs; +static pthread_mutex_t SnoopLock; + +#define snoop_lock() { pthread_mutex_lock(&SnoopLock); } +#define snoop_unlock() { pthread_mutex_unlock(&SnoopLock); } struct virNWFilterSnoopReq { virConnectPtr conn; @@ -90,7 +101,14 @@ struct iplease { static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr); static void ipl_update(struct iplease *pl, uint32_t timeout); - + +static struct iflease *getiflease(const char *ifname); +static void lease_open(void); +static void lease_close(void); +static void lease_load(void); +static void lease_save(struct iplease *ipl); +static void lease_refresh(void); +static void lease_restore(struct virNWFilterSnoopReq *req); /* * ipl_ladd - add an IP lease to a list @@ -150,6 +168,7 @@ ipl_add(struct iplease *plnew) ipl_update(pl, plnew->ipl_timeout); return; } + nleases++; if (VIR_ALLOC(pl) < 0) { virReportOOMError(); return; @@ -184,6 +203,7 @@ ipl_add(struct iplease *plnew) return; } ipl_tadd(pl); + lease_save(pl); } /* @@ -231,6 +251,7 @@ ipl_del(struct iplease *ipl) req = ipl->ipl_req; ipl_tdel(ipl); + lease_save(ipl); if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { ipstr = strdup(ipbuf); @@ -248,6 +269,7 @@ ipl_del(struct iplease *ipl) _("ipl_del inet_ntop " "failed (0x%08X)"), ipl->ipl_ipaddr); VIR_FREE(ipl); + nleases--; } /* @@ -259,6 +281,7 @@ ipl_update(struct iplease *ipl, uint32_t timeout) ipl_tdel(ipl); ipl->ipl_timeout = timeout; ipl_tadd(ipl); + lease_save(ipl); return; } @@ -275,8 +298,6 @@ ipl_getbyip(struct iplease *start, uint32_t ipaddr) return pl; } -#define GRACE 5 - /* * ipl_trun - run the IP lease timeout list */ @@ -465,6 +486,19 @@ dhcpopen(const char *intf) return handle; } +/* + * snoopReqFree - release a snoop Req + */ +static void +snoopReqFree(struct virNWFilterSnoopReq *req) +{ + if (req->ifname) + VIR_FREE(req->ifname); + if (req->vars) + virNWFilterHashTableFree(req->vars); + VIR_FREE(req); +} + static void * virNWFilterDHCPSnoop(void *req0) { @@ -479,12 +513,19 @@ virNWFilterDHCPSnoop(void *req0) if (!handle) return 0; + /* restore any saved leases for this interface */ + snoop_lock(); + lease_restore(req); + snoop_unlock(); + ifindex = if_nametoindex(req->ifname); while (1) { if (req->die) break; + snoop_lock(); ipl_trun(req); + snoop_unlock(); packet = (struct eth *) pcap_next(handle, &hdr); @@ -494,16 +535,18 @@ virNWFilterDHCPSnoop(void *req0) continue; } + snoop_lock(); dhcpdecode(req, packet, hdr.caplen); + snoop_unlock(); } + + snoop_lock(); /* free all leases */ for (ipl = req->start; ipl; ipl = req->start) ipl_del(ipl); + snoop_unlock(); - /* free all req data */ - VIR_FREE(req->ifname); - virNWFilterHashTableFree(req->vars); - VIR_FREE(req); + snoopReqFree(req); return 0; } @@ -518,9 +561,12 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn, { struct virNWFilterSnoopReq *req; + snoop_lock(); req = virHashLookup(SnoopReqs, ifname); - if (req) + snoop_unlock(); + if (req) { return 0; + } if (VIR_ALLOC(req) < 0) { virReportOOMError(); return 1; @@ -533,28 +579,30 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn, req->ifname = strdup(ifname); req->vars = virNWFilterHashTableCreate(0); if (!req->vars) { + snoopReqFree(req); virReportOOMError(); return 1; } if (virNWFilterHashTablePutAll(vars, req->vars)) { virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, - _("virNWFilterDHCPSnoopReq: can't copy variables" - " on if %s"), ifname); + _("virNWFilterDHCPSnoopReq: can't copy " + "variables on if %s"), ifname); + snoopReqFree(req); return 1; } req->driver = driver; req->start = req->end = 0; if (virHashAddEntry(SnoopReqs, ifname, req)) { - VIR_FREE(req); + snoopReqFree(req); virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, - _("virNWFilterDHCPSnoopReq req add failed on" - "interface \"%s\""), ifname); + _("virNWFilterDHCPSnoopReq req add " + "failed oninterface \"%s\""), ifname); return 1; } if (pthread_create(&req->thread, NULL, virNWFilterDHCPSnoop, req) != 0) { (void) virHashRemoveEntry(SnoopReqs, ifname); - VIR_FREE(req); + (void) snoopReqFree(req); virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("virNWFilterDHCPSnoopReq pthread_create failed" " oninterface \"%s\""), ifname); @@ -577,12 +625,41 @@ freeReq(void *req0, const void *name ATTRIBUTE_UNUSED) req->die = 1; } +static void +lease_close(void) +{ + (void) close(lease_fd); + lease_fd = -1; +} + +static void +lease_open(void) +{ + lease_close(); + + lease_fd = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND, 0644); +} + int virNWFilterDHCPSnoopInit(void) { if (SnoopReqs) return 0; + + if (pthread_mutex_init(&SnoopLock, 0) < 0) + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pthread_mutex_init: %s"), + strerror(errno)); + snoop_lock(); + + lease_load(); + + lease_open(); + SnoopReqs = virHashCreate(0, freeReq); + + snoop_unlock(); + if (!SnoopReqs) { virReportOOMError(); return -1; @@ -595,8 +672,267 @@ virNWFilterDHCPSnoopEnd(const char *ifname) { if (!SnoopReqs) return; - if (ifname) - virHashRemoveEntry(SnoopReqs, ifname); - else /* free all of them */ + snoop_lock(); + if (!ifname) { /* free all of them */ virHashFree(SnoopReqs); + lease_refresh(); + } else + virHashRemoveEntry(SnoopReqs, ifname); + lease_close(); + snoop_unlock(); +} + + +/* lease file handling */ + +struct iflease { + char *ifl_ifname; + struct iplease *ifl_start; + struct iplease *ifl_end; + struct iflease *ifl_prev; + struct iflease *ifl_next; +}; + +struct iflease *leases; + +static int +lease_write(int lfd, const char *ifname, struct iplease *ipl) +{ + char lbuf[256],ipstr[16],dhcpstr[16]; + + if (inet_ntop(AF_INET, &ipl->ipl_ipaddr, ipstr, sizeof ipstr) == 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("inet_ntop(0x%08X) failed"), ipl->ipl_ipaddr); + return -1; + } + if (inet_ntop(AF_INET, &ipl->ipl_server, dhcpstr, sizeof dhcpstr) == 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("inet_ntop(0x%08X) failed"), ipl->ipl_server); + return -1; + } + /* time intf ip dhcpserver */ + snprintf(lbuf, sizeof(lbuf), "%u %s %s %s\n", ipl->ipl_timeout, + ifname, ipstr, dhcpstr); + if (write(lfd, lbuf, strlen(lbuf)) < 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease file write failed: %s"), + strerror(errno)); + return -1; + } + (void) fsync(lfd); + return 0; +} + +static void +lease_save(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req = ipl->ipl_req; + struct iflease *ifl; + + /* add to the lease file list */ + ifl = getiflease(ipl->ipl_req->ifname); + if (ifl) { + struct iplease *ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr); + + if (ifipl) { + if (ipl->ipl_timeout) { + ifipl->ipl_timeout = ipl->ipl_timeout; + ifipl->ipl_server = ipl->ipl_server; + } else { + ipl_ldel(ifipl, &ifl->ifl_start, &ifl->ifl_end); + VIR_FREE(ifipl); + } + } else if (!VIR_ALLOC(ifipl)) { + ifipl->ipl_ipaddr = ipl->ipl_ipaddr; + ifipl->ipl_server = ipl->ipl_server; + ifipl->ipl_timeout = ipl->ipl_timeout; + ipl_ladd(ifipl, &ifl->ifl_start, &ifl->ifl_end); + } + } + if (lease_fd < 0) + lease_open(); + if (lease_write(lease_fd, req->ifname, ipl) < 0) + return; + /* keep dead leases at < ~95% of file size */ + if (++wleases >= nleases*20) + lease_load(); /* load & refresh lease file */ +} + +static void +lease_restore(struct virNWFilterSnoopReq *req) +{ + struct iflease *ifl; + struct iplease *ipl; + + ifl = getiflease(req->ifname); + if (!ifl) + return; + for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next) { + ipl->ipl_req = req; + ipl_add(ipl); + } +} + +static struct iflease * +getiflease(const char *ifname) +{ + struct iflease *ifl; + + for (ifl=leases; ifl; ifl=ifl->ifl_next) + if (strcmp(ifname, ifl->ifl_ifname) == 0) + return ifl; + if (VIR_ALLOC(ifl)) { + virReportOOMError(); + return 0; + } + ifl->ifl_ifname = strdup(ifname); + ifl->ifl_next = leases; + leases = ifl; + return ifl; +} + +static void +lease_refresh(void) +{ + struct iflease *ifl; + struct iplease *ipl; + int tfd; + + (void) unlink(TMPLEASEFILE); + /* lease file loaded, delete old one */ + tfd = open(TMPLEASEFILE, O_CREAT|O_RDWR|O_TRUNC|O_EXCL, 0644); + if (tfd < 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("open(\"%s\"): %s"), + TMPLEASEFILE, strerror(errno)); + return; + } + for (ifl=leases; ifl; ifl=ifl->ifl_next) + for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next) + if (lease_write(tfd, ifl->ifl_ifname, ipl) < 0) + break; + (void) close(tfd); + if (rename(TMPLEASEFILE, LEASEFILE) < 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("rename(\"%s\", \"%s\"): %s"), + TMPLEASEFILE, LEASEFILE, strerror(errno)); + (void) unlink(TMPLEASEFILE); + } + wleases = 0; + lease_open(); +} + +static void +LoadSnoopReqIter(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req = payload; + struct iplease *ipl, *ifipl; + struct iflease *ifl; + + ifl = getiflease(req->ifname); + if (!ifl) + return; + for (ipl = req->start; ipl; ipl = ipl->ipl_next) { + ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr); + if (ifipl) { + if (ifipl->ipl_timeout < ipl->ipl_timeout) { + ifipl->ipl_timeout = ipl->ipl_timeout; + ifipl->ipl_server = ipl->ipl_server; + } + continue; + } + if (VIR_ALLOC(ifipl)) { + virReportOOMError(); + continue; + } + ifipl->ipl_ipaddr = ipl->ipl_ipaddr; + ifipl->ipl_server = ipl->ipl_server; + ifipl->ipl_timeout = ipl->ipl_timeout; + ipl_ladd(ifipl, &ifl->ifl_start, &ifl->ifl_end); + } +} + +static void +lease_load(void) +{ + char line[256], ifname[16], ipstr[16], srvstr[16]; + uint32_t ipaddr, svaddr; + FILE *fp; + int ln = 0; + time_t timeout, now; + struct iflease *ifl; + struct iplease *ipl; + + fp = fopen(LEASEFILE, "r"); + time(&now); + while (fp && fgets(line, sizeof(line), fp)) { + if (line[strlen(line)-1] != '\n') { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease_load lease file line %d corrupt"), + ln); + break; + } + ln++; + if (sscanf(line, "%lu %16s %16s %16s", (unsigned long *)&timeout, + ifname, ipstr, srvstr) < 4) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease_load lease file line %d corrupt"), + ln); + break;; + } + if (timeout && timeout < now) + continue; + ifl = getiflease(ifname); + if (!ifl) + break; + + if (inet_pton(AF_INET, ipstr, &ipaddr) <= 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("line %d corrupt ipaddr \"%s\""), + ln, ipstr); + VIR_FREE(ipl); + continue; + } + (void) inet_pton(AF_INET, srvstr, &svaddr); + + ipl = ipl_getbyip(ifl->ifl_start, ipaddr); + if (ipl) { + if (timeout && timeout < ipl->ipl_timeout) + continue; /* out of order lease? skip. */ + ipl->ipl_timeout = timeout; + ipl->ipl_server = svaddr; + continue; + } + if (!timeout) + continue; /* don't add new lease deletions */ + if (VIR_ALLOC(ipl)) { + virReportOOMError(); + break; + } + ipl->ipl_ipaddr = ipaddr; + ipl->ipl_server = svaddr; + ipl->ipl_timeout = timeout; + ipl_ladd(ipl, &ifl->ifl_start, &ifl->ifl_end); + } + /* also load any active leases from memory, in case lease writes may + * have failed. + */ + if (SnoopReqs) + virHashForEach(SnoopReqs, LoadSnoopReqIter, 0); + /* remove any deleted leases */ + for (ifl = leases; ifl; ifl = ifl->ifl_next) { + struct iplease *iplnext; + + for (ipl = ifl->ifl_start; ipl; ipl = iplnext) { + iplnext = ipl->ipl_next; + if (ipl->ipl_timeout == 0) { + ipl_ldel(ipl, &ifl->ifl_start, &ifl->ifl_end); + VIR_FREE(ipl); + } + } + } + + lease_refresh(); } -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds support for saving DHCP snooping leases to an on-disk file and restoring saved leases that are still active on restart.
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_dhcpsnoop.c | 370 +++++++++++++++++++++++++++++++++++-- 1 files changed, 353 insertions(+), 17 deletions(-)
diff --git a/src/nwfilter/nwfilter_dhcpsnoop.c b/src/nwfilter/nwfilter_dhcpsnoop.c index f784a29..eedf550 100644 --- a/src/nwfilter/nwfilter_dhcpsnoop.c +++ b/src/nwfilter/nwfilter_dhcpsnoop.c @@ -56,10 +56,21 @@ #include "nwfilter_gentech_driver.h" #include "nwfilter_ebiptables_driver.h" #include "nwfilter_dhcpsnoop.h" +#include "configmake.h"
#define VIR_FROM_THIS VIR_FROM_NWFILTER
+#define LEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.leases" +#define TMPLEASEFILE LOCALSTATEDIR "/run/libvirt/network/nwfilter.ltmp" +static int lease_fd = -1; +static int nleases = 0; /* number of active leases */ +static int wleases = 0; /* number of written leases */ + static virHashTablePtr SnoopReqs; +static pthread_mutex_t SnoopLock; + +#define snoop_lock() { pthread_mutex_lock(&SnoopLock); } +#define snoop_unlock() { pthread_mutex_unlock(&SnoopLock); }
struct virNWFilterSnoopReq { virConnectPtr conn; @@ -90,7 +101,14 @@ struct iplease {
static struct iplease *ipl_getbyip(struct iplease *start, uint32_t ipaddr); static void ipl_update(struct iplease *pl, uint32_t timeout); - + +static struct iflease *getiflease(const char *ifname); +static void lease_open(void); +static void lease_close(void); +static void lease_load(void); +static void lease_save(struct iplease *ipl); +static void lease_refresh(void); +static void lease_restore(struct virNWFilterSnoopReq *req);
/* * ipl_ladd - add an IP lease to a list @@ -150,6 +168,7 @@ ipl_add(struct iplease *plnew) ipl_update(pl, plnew->ipl_timeout); return; } + nleases++; if (VIR_ALLOC(pl)< 0) { virReportOOMError(); return; @@ -184,6 +203,7 @@ ipl_add(struct iplease *plnew) return; } ipl_tadd(pl); + lease_save(pl); }
/* @@ -231,6 +251,7 @@ ipl_del(struct iplease *ipl) req = ipl->ipl_req;
ipl_tdel(ipl); + lease_save(ipl);
if (inet_ntop(AF_INET,&ipl->ipl_ipaddr, ipbuf, sizeof(ipbuf))) { ipstr = strdup(ipbuf); @@ -248,6 +269,7 @@ ipl_del(struct iplease *ipl) _("ipl_del inet_ntop " "failed (0x%08X)"), ipl->ipl_ipaddr); VIR_FREE(ipl); + nleases--; }
/* @@ -259,6 +281,7 @@ ipl_update(struct iplease *ipl, uint32_t timeout) ipl_tdel(ipl); ipl->ipl_timeout = timeout; ipl_tadd(ipl); + lease_save(ipl); return; }
@@ -275,8 +298,6 @@ ipl_getbyip(struct iplease *start, uint32_t ipaddr) return pl; }
-#define GRACE 5 - /* * ipl_trun - run the IP lease timeout list */ @@ -465,6 +486,19 @@ dhcpopen(const char *intf) return handle; }
+/* + * snoopReqFree - release a snoop Req + */ +static void +snoopReqFree(struct virNWFilterSnoopReq *req) +{ + if (req->ifname) + VIR_FREE(req->ifname); + if (req->vars) + virNWFilterHashTableFree(req->vars); + VIR_FREE(req); +} + static void * virNWFilterDHCPSnoop(void *req0) { @@ -479,12 +513,19 @@ virNWFilterDHCPSnoop(void *req0) if (!handle) return 0;
+ /* restore any saved leases for this interface */ + snoop_lock(); + lease_restore(req); + snoop_unlock(); + ifindex = if_nametoindex(req->ifname);
while (1) { if (req->die) break; + snoop_lock(); ipl_trun(req); + snoop_unlock();
packet = (struct eth *) pcap_next(handle,&hdr);
@@ -494,16 +535,18 @@ virNWFilterDHCPSnoop(void *req0) continue; }
+ snoop_lock(); dhcpdecode(req, packet, hdr.caplen); + snoop_unlock(); } + + snoop_lock(); /* free all leases */ for (ipl = req->start; ipl; ipl = req->start) ipl_del(ipl); + snoop_unlock();
- /* free all req data */ - VIR_FREE(req->ifname); - virNWFilterHashTableFree(req->vars); - VIR_FREE(req); + snoopReqFree(req); return 0; }
@@ -518,9 +561,12 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn, { struct virNWFilterSnoopReq *req;
+ snoop_lock(); req = virHashLookup(SnoopReqs, ifname); - if (req) + snoop_unlock(); + if (req) { return 0; + } if (VIR_ALLOC(req)< 0) { virReportOOMError(); return 1; @@ -533,28 +579,30 @@ virNWFilterDHCPSnoopReq(virConnectPtr conn, req->ifname = strdup(ifname); req->vars = virNWFilterHashTableCreate(0); if (!req->vars) { + snoopReqFree(req); Following the lookup into the hashtable above you cannot just free the request. I suppose you'd first have to remove it from the hash table -- or did this maybe happen in the lines in between? This seems more like fix to the previous patch? The snoopReqFree() should really be introduced in the previous patch...
virReportOOMError(); return 1; } if (virNWFilterHashTablePutAll(vars, req->vars)) { virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, - _("virNWFilterDHCPSnoopReq: can't copy variables" - " on if %s"), ifname); + _("virNWFilterDHCPSnoopReq: can't copy " + "variables on if %s"), ifname); + snoopReqFree(req); return 1; } req->driver = driver; req->start = req->end = 0;
if (virHashAddEntry(SnoopReqs, ifname, req)) { - VIR_FREE(req); + snoopReqFree(req); virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, - _("virNWFilterDHCPSnoopReq req add failed on" - "interface \"%s\""), ifname); + _("virNWFilterDHCPSnoopReq req add " + "failed oninterface \"%s\""), ifname); return 1; } if (pthread_create(&req->thread, NULL, virNWFilterDHCPSnoop, req) != 0) { (void) virHashRemoveEntry(SnoopReqs, ifname); - VIR_FREE(req); + (void) snoopReqFree(req);
no need to void the result since it doesn't return anything
virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, _("virNWFilterDHCPSnoopReq pthread_create failed" " oninterface \"%s\""), ifname); @@ -577,12 +625,41 @@ freeReq(void *req0, const void *name ATTRIBUTE_UNUSED) req->die = 1; }
+static void +lease_close(void) +{ + (void) close(lease_fd); + lease_fd = -1; +}
Replace with VIR_FORCE_CLOSE().
+ +static void +lease_open(void) +{ + lease_close(); + + lease_fd = open(LEASEFILE, O_CREAT|O_RDWR|O_APPEND, 0644); +} + int virNWFilterDHCPSnoopInit(void) { if (SnoopReqs) return 0; Since locking is done below I'd test for SnoopReq with the snoop lock being held otherwise you could (theoretically) have two threads running into the code below, at least the code looks that way. + + if (pthread_mutex_init(&SnoopLock, 0)< 0) + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("pthread_mutex_init: %s"), + strerror(errno)); + snoop_lock(); Move this lock to the top. + + lease_load(); + + lease_open(); + SnoopReqs = virHashCreate(0, freeReq); + + snoop_unlock(); + if (!SnoopReqs) { virReportOOMError(); return -1; @@ -595,8 +672,267 @@ virNWFilterDHCPSnoopEnd(const char *ifname) { if (!SnoopReqs) return; - if (ifname) - virHashRemoveEntry(SnoopReqs, ifname); - else /* free all of them */ + snoop_lock(); + if (!ifname) { /* free all of them */ virHashFree(SnoopReqs); + lease_refresh(); + } else + virHashRemoveEntry(SnoopReqs, ifname); + lease_close(); + snoop_unlock(); +} + + +/* lease file handling */ + +struct iflease { + char *ifl_ifname; + struct iplease *ifl_start; + struct iplease *ifl_end; + struct iflease *ifl_prev; + struct iflease *ifl_next; +}; + +struct iflease *leases; + +static int +lease_write(int lfd, const char *ifname, struct iplease *ipl) +{ + char lbuf[256],ipstr[16],dhcpstr[16]; + + if (inet_ntop(AF_INET,&ipl->ipl_ipaddr, ipstr, sizeof ipstr) == 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("inet_ntop(0x%08X) failed"), ipl->ipl_ipaddr); + return -1; + } + if (inet_ntop(AF_INET,&ipl->ipl_server, dhcpstr, sizeof dhcpstr) == 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("inet_ntop(0x%08X) failed"), ipl->ipl_server); + return -1; + } + /* time intf ip dhcpserver */ + snprintf(lbuf, sizeof(lbuf), "%u %s %s %s\n", ipl->ipl_timeout, + ifname, ipstr, dhcpstr); + if (write(lfd, lbuf, strlen(lbuf))< 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease file write failed: %s"), + strerror(errno)); + return -1; + } + (void) fsync(lfd); What if this call fails? + return 0; +} + +static void +lease_save(struct iplease *ipl) +{ + struct virNWFilterSnoopReq *req = ipl->ipl_req; + struct iflease *ifl; + + /* add to the lease file list */ + ifl = getiflease(ipl->ipl_req->ifname); + if (ifl) { + struct iplease *ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr); + + if (ifipl) { + if (ipl->ipl_timeout) { + ifipl->ipl_timeout = ipl->ipl_timeout; + ifipl->ipl_server = ipl->ipl_server; + } else { + ipl_ldel(ifipl,&ifl->ifl_start,&ifl->ifl_end); + VIR_FREE(ifipl); + } + } else if (!VIR_ALLOC(ifipl)) { + ifipl->ipl_ipaddr = ipl->ipl_ipaddr; + ifipl->ipl_server = ipl->ipl_server; + ifipl->ipl_timeout = ipl->ipl_timeout; + ipl_ladd(ifipl,&ifl->ifl_start,&ifl->ifl_end); + } + } + if (lease_fd< 0) + lease_open(); + if (lease_write(lease_fd, req->ifname, ipl)< 0) + return; + /* keep dead leases at< ~95% of file size */ + if (++wleases>= nleases*20) + lease_load(); /* load& refresh lease file */ +} + +static void +lease_restore(struct virNWFilterSnoopReq *req) +{ + struct iflease *ifl; + struct iplease *ipl; + + ifl = getiflease(req->ifname); + if (!ifl) + return; + for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next) { + ipl->ipl_req = req; + ipl_add(ipl); + } +} + +static struct iflease * +getiflease(const char *ifname) +{ + struct iflease *ifl; + + for (ifl=leases; ifl; ifl=ifl->ifl_next) + if (strcmp(ifname, ifl->ifl_ifname) == 0) + return ifl; + if (VIR_ALLOC(ifl)) { + virReportOOMError(); + return 0; + } + ifl->ifl_ifname = strdup(ifname); + ifl->ifl_next = leases; + leases = ifl; + return ifl; +} + +static void +lease_refresh(void) +{ + struct iflease *ifl; + struct iplease *ipl; + int tfd; + + (void) unlink(TMPLEASEFILE); + /* lease file loaded, delete old one */ + tfd = open(TMPLEASEFILE, O_CREAT|O_RDWR|O_TRUNC|O_EXCL, 0644); + if (tfd< 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("open(\"%s\"): %s"), + TMPLEASEFILE, strerror(errno)); + return; + } + for (ifl=leases; ifl; ifl=ifl->ifl_next) + for (ipl = ifl->ifl_start; ipl; ipl = ipl->ipl_next) + if (lease_write(tfd, ifl->ifl_ifname, ipl)< 0) + break; In case of error, shouldn't it unlink the file and return and error ? + (void) close(tfd); VIR_CLOSE() and handle error case ? + if (rename(TMPLEASEFILE, LEASEFILE)< 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("rename(\"%s\", \"%s\"): %s"), + TMPLEASEFILE, LEASEFILE, strerror(errno)); + (void) unlink(TMPLEASEFILE); + } + wleases = 0; + lease_open(); +} + +static void +LoadSnoopReqIter(void *payload, + const void *name ATTRIBUTE_UNUSED, + void *data ATTRIBUTE_UNUSED) +{ + struct virNWFilterSnoopReq *req = payload; + struct iplease *ipl, *ifipl; + struct iflease *ifl; + + ifl = getiflease(req->ifname); + if (!ifl) + return; + for (ipl = req->start; ipl; ipl = ipl->ipl_next) { + ifipl = ipl_getbyip(ifl->ifl_start, ipl->ipl_ipaddr); + if (ifipl) { + if (ifipl->ipl_timeout< ipl->ipl_timeout) { + ifipl->ipl_timeout = ipl->ipl_timeout; + ifipl->ipl_server = ipl->ipl_server; + } + continue; + } + if (VIR_ALLOC(ifipl)) { + virReportOOMError(); + continue; + } + ifipl->ipl_ipaddr = ipl->ipl_ipaddr; + ifipl->ipl_server = ipl->ipl_server; + ifipl->ipl_timeout = ipl->ipl_timeout; + ipl_ladd(ifipl,&ifl->ifl_start,&ifl->ifl_end); + } +} + +static void +lease_load(void) +{ + char line[256], ifname[16], ipstr[16], srvstr[16]; + uint32_t ipaddr, svaddr; + FILE *fp; + int ln = 0; + time_t timeout, now; + struct iflease *ifl; + struct iplease *ipl; + + fp = fopen(LEASEFILE, "r"); Didn't find the fclose().. + time(&now); + while (fp&& fgets(line, sizeof(line), fp)) { + if (line[strlen(line)-1] != '\n') { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease_load lease file line %d corrupt"), + ln); + break; + } + ln++; + if (sscanf(line, "%lu %16s %16s %16s", (unsigned long *)&timeout, + ifname, ipstr, srvstr)< 4) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("lease_load lease file line %d corrupt"), + ln); + break;; + } + if (timeout&& timeout< now) + continue; + ifl = getiflease(ifname); + if (!ifl) + break; + + if (inet_pton(AF_INET, ipstr,&ipaddr)<= 0) { + virNWFilterReportError(VIR_ERR_INTERNAL_ERROR, + _("line %d corrupt ipaddr \"%s\""), + ln, ipstr); + VIR_FREE(ipl); + continue; + } + (void) inet_pton(AF_INET, srvstr,&svaddr); + + ipl = ipl_getbyip(ifl->ifl_start, ipaddr); + if (ipl) { + if (timeout&& timeout< ipl->ipl_timeout) + continue; /* out of order lease? skip. */ + ipl->ipl_timeout = timeout; + ipl->ipl_server = svaddr; + continue; + } + if (!timeout) + continue; /* don't add new lease deletions */ + if (VIR_ALLOC(ipl)) { + virReportOOMError(); + break; + } + ipl->ipl_ipaddr = ipaddr; + ipl->ipl_server = svaddr; + ipl->ipl_timeout = timeout; + ipl_ladd(ipl,&ifl->ifl_start,&ifl->ifl_end); + } + /* also load any active leases from memory, in case lease writes may + * have failed. + */ + if (SnoopReqs) + virHashForEach(SnoopReqs, LoadSnoopReqIter, 0); + /* remove any deleted leases */ + for (ifl = leases; ifl; ifl = ifl->ifl_next) { + struct iplease *iplnext; + + for (ipl = ifl->ifl_start; ipl; ipl = iplnext) { + iplnext = ipl->ipl_next; + if (ipl->ipl_timeout == 0) { + ipl_ldel(ipl,&ifl->ifl_start,&ifl->ifl_end); + VIR_FREE(ipl); + } + } + } + + lease_refresh(); } For every lease I guess you'd have to store the VLAN ID as well since VMs on different VLANs can have the same IP address. I am not sure what effect this has on the whole code. Maybe your snooping would have to look at the MAC header and extract VLAN identifier(s) [nested!] as well and store them here. I wonder about the MAC address that an IP address is associated and whether that should be recorded as well and used for determining whether a lease has expired.
Stefan

This patch adds support for multiple static IP addresses in a comma-separated list. For example: <interface type='network'> <filterref filter='clean-traffic'> <parameter name='ip_learning' value='none'/> <parameter name='IP' value='10.0.0.1,10.1.0.7,10.0.3.8,10.0.9.244'/> </filterref> ... Signed-off-by: David L Stevens <dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_gentech_driver.c | 26 ++++++++++++++++++++++++++ 1 files changed, 26 insertions(+), 0 deletions(-) diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 577b48d..8f74a01 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -920,6 +920,8 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, char *str_macaddr = NULL; const char *ipaddr; char *str_ipaddr = NULL; + char *ipaddrlist = NULL; + char *sep; techdriver = virNWFilterTechDriverForName(drvname); @@ -983,6 +985,17 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, goto err_exit_vars1; } + ipaddr = virHashLookup(filterparams->hashTable, NWFILTER_STD_VAR_IP); + if (ipaddr) { + sep = strchr(ipaddr, ','); + if (sep) { + str_ipaddr = strndup(ipaddr, sep-ipaddr); + ipaddrlist = strdup(sep + 1); + virNWFilterHashTablePut(vars, NWFILTER_STD_VAR_IP, str_ipaddr, 1); + } + } + str_ipaddr = NULL; + filter = obj->def; switch (useNewFilter) { @@ -1011,6 +1024,19 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, driver, forceWithPendingReq); + /* add the rest of the IP list */ + for (ipaddr = ipaddrlist; ipaddr; ipaddr = sep) { + sep = strchr(ipaddr, ','); + if (sep) { + str_ipaddr = strndup(ipaddr, sep-ipaddr); + sep++; /* skip ',' */ + } else + str_ipaddr = strdup(ipaddr); + virNWFilterChangeVar(conn, techdriver, nettype, filter, ifname, vars, + driver, NWFILTER_STD_VAR_IP, str_ipaddr, 0); + + } + str_ipaddr = NULL; virNWFilterHashTableFree(vars); err_exit_vars1: -- 1.7.6.4

On 10/12/2011 03:50 PM, David L Stevens wrote:
This patch adds support for multiple static IP addresses in a comma-separated list. For example:
<interface type='network'> <filterref filter='clean-traffic'> <parameter name='ip_learning' value='none'/> <parameter name='IP' value='10.0.0.1,10.1.0.7,10.0.3.8,10.0.9.244'/> </filterref> ...
Signed-off-by: David L Stevens<dlstevens@us.ibm.com> --- src/nwfilter/nwfilter_gentech_driver.c | 26 ++++++++++++++++++++++++++ 1 files changed, 26 insertions(+), 0 deletions(-)
diff --git a/src/nwfilter/nwfilter_gentech_driver.c b/src/nwfilter/nwfilter_gentech_driver.c index 577b48d..8f74a01 100644 --- a/src/nwfilter/nwfilter_gentech_driver.c +++ b/src/nwfilter/nwfilter_gentech_driver.c @@ -920,6 +920,8 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, char *str_macaddr = NULL; const char *ipaddr; char *str_ipaddr = NULL; + char *ipaddrlist = NULL; + char *sep;
techdriver = virNWFilterTechDriverForName(drvname);
@@ -983,6 +985,17 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, goto err_exit_vars1; }
+ ipaddr = virHashLookup(filterparams->hashTable, NWFILTER_STD_VAR_IP); + if (ipaddr) { + sep = strchr(ipaddr, ','); + if (sep) { + str_ipaddr = strndup(ipaddr, sep-ipaddr); + ipaddrlist = strdup(sep + 1); Check for str_ipaddr or ipaddrlist being NULL. + virNWFilterHashTablePut(vars, NWFILTER_STD_VAR_IP, str_ipaddr, 1); + } + } + str_ipaddr = NULL; + filter = obj->def;
switch (useNewFilter) { @@ -1011,6 +1024,19 @@ __virNWFilterInstantiateFilter(virConnectPtr conn, driver, forceWithPendingReq);
+ /* add the rest of the IP list */ + for (ipaddr = ipaddrlist; ipaddr; ipaddr = sep) { + sep = strchr(ipaddr, ','); + if (sep) { + str_ipaddr = strndup(ipaddr, sep-ipaddr); Test for NULL result. + sep++; /* skip ',' */ + } else + str_ipaddr = strdup(ipaddr); Also here. + virNWFilterChangeVar(conn, techdriver, nettype, filter, ifname, vars, + driver, NWFILTER_STD_VAR_IP, str_ipaddr, 0); + + } + str_ipaddr = NULL; virNWFilterHashTableFree(vars);
err_exit_vars1:

David, I have unfortunately missed v2 of this and in the meantime (since after V1) I had been thinking about this a bit. The problem we're having at the moment is that it's not possible to evaluate fields of packets that may have more than one possible value. This is the general problem, the specific one being allowing multiple MAC or IP addresses. This problem requires us to enable more tables along with jumps to those tables. I think we should solve this in a more general way. What we seem to need for this are tables that are connected to the 'root table' of an interface and tables that are not connected to the 'root table'. So for now we handle arp, rarp, ipv4 and ipv6 from that 'root' table using '-p arp -j <table>' for example to jump to an ARP-specific table for example, the protocol being the decision point here ('-p'). So now maybe what we should do is allow user to define tables with prefixes 'arp', 'ipv4' and 'ipv6' and have all of them connected to the root table and jump into them using '-p'. There could be an arp table, an 'arp-xyz' table and all of them would be connected to that root table -- the question is just in what order. Maybe alphabetical order, with arp and rarp still being always treated after ipv4 and ipv6. Then to realize the other 'loose tables' they could maybe all be required to have a prefix 'ud-' for 'user-defined'. Those would then just be created but not accessed from the 'root table' of an interface but from those connected to an interface's 'root table'. Does this sound reasonable ? Stefan On 10/12/2011 03:50 PM, David L Stevens wrote:
This series of patches adds DHCP snooping support to libvirt. This version saves leases on disk for restoration after a libvirtd restart and allows selection of different ip_learning methods by setting filter parameter "ip_learning" to one of "any" (existing IP learning code) "none" (static only addresses) or "DHCP" (DHCP Snooping).
This code does not (yet) support passing lease information across a migration. A migrated guest requires a DHCP ACK (e.g., via ifdown/ifup on the guest) to send/receive traffic for DHCP-learned addresses after a migration.
Differences from v2: added support for multiple static IP addresses using a comma-separated list.
David L Stevens (10): support continue/return allow required ARP packets reverse sense of address matching make default chain policy "DROP" allow chain modification support addRules support variable value changing add DHCP snooping add leasefile support support multiple static IP addresses
examples/xml/nwfilter/Makefile.am | 5 +- examples/xml/nwfilter/allow-arp.xml | 5 +- examples/xml/nwfilter/allow-arpip.xml | 3 + examples/xml/nwfilter/allow-arpmac.xml | 3 + examples/xml/nwfilter/clean-traffic.xml | 6 +- examples/xml/nwfilter/no-arp-spoofing.xml | 38 +- examples/xml/nwfilter/no-arpip-spoofing.xml | 10 + examples/xml/nwfilter/no-arpmac-spoofing.xml | 5 + examples/xml/nwfilter/no-ip-spoofing.xml | 9 +- examples/xml/nwfilter/no-mac-spoofing.xml | 10 +- examples/xml/nwfilter/no-other-l2-traffic.xml | 13 +- examples/xml/nwfilter/no-other-rarp-traffic.xml | 3 - examples/xml/nwfilter/qemu-announce-self.xml | 1 - src/Makefile.am | 2 + src/conf/nwfilter_conf.c | 12 +- src/conf/nwfilter_conf.h | 16 +- src/nwfilter/nwfilter_dhcpsnoop.c | 938 +++++++++++++++++++++++ src/nwfilter/nwfilter_dhcpsnoop.h | 36 + src/nwfilter/nwfilter_driver.c | 5 + src/nwfilter/nwfilter_ebiptables_driver.c | 225 +++++-- src/nwfilter/nwfilter_gentech_driver.c | 225 +++++- src/nwfilter/nwfilter_gentech_driver.h | 11 + 22 files changed, 1445 insertions(+), 136 deletions(-) create mode 100644 examples/xml/nwfilter/allow-arpip.xml create mode 100644 examples/xml/nwfilter/allow-arpmac.xml create mode 100644 examples/xml/nwfilter/no-arpip-spoofing.xml create mode 100644 examples/xml/nwfilter/no-arpmac-spoofing.xml delete mode 100644 examples/xml/nwfilter/no-other-rarp-traffic.xml create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.c create mode 100644 src/nwfilter/nwfilter_dhcpsnoop.h

Stefan Berger <stefanb@linux.vnet.ibm.com> wrote on 10/12/2011 02:02:59 PM:
The problem we're having at the moment is that it's not possible to evaluate fields of packets that may have more than one possible value. This is the general problem, the specific one being allowing multiple MAC or IP addresses.
Stefan, Yes, this is why for this patchset I've added "RETURN" and made the address checks be "if match return" and a default drop at the end. This code already supports multiple IP addresses for DHCP snooping, static IP addresses (new to this version) and a combination of the two (if both "IP" is set and "ip_learning=dhcp". Sample output using multiple static addresses below. The same model can be applied to user-generated filters with: <do a series of checks using RETURN for acceptable packets> -j DROP If the user filter does "RETURN", it'll apply other tests as well. If it does "ACCEPT"/"DROP", it'll accept or drop despite any other conditions. I'm not sure I see any need for other tables here, though-- can you elaborate? +-DLS lab1.dls 226 # ebtables -t nat -L Bridge table: nat Bridge chain: PREROUTING, entries: 1, policy: ACCEPT -i vnet0 -j libvirt-I-vnet0 Bridge chain: OUTPUT, entries: 0, policy: ACCEPT Bridge chain: POSTROUTING, entries: 1, policy: ACCEPT -o vnet0 -j libvirt-O-vnet0 Bridge chain: libvirt-I-vnet0, entries: 9, policy: ACCEPT -j I-vnet0-mac -p IPv4 -j I-vnet0-ipv4 -p ARP -j I-vnet0-arpmac -p ARP -j I-vnet0-arpip -p 0x8035 -j I-vnet0-rarp -p 0x835 -j ACCEPT -p IPv4 -j ACCEPT -p ARP -j ACCEPT -j DROP Bridge chain: libvirt-O-vnet0, entries: 5, policy: ACCEPT -p IPv4 -j O-vnet0-ipv4 -p 0x8035 -j O-vnet0-rarp -p IPv4 -j ACCEPT -p ARP -j ACCEPT -j DROP Bridge chain: I-vnet0-mac, entries: 1, policy: DROP -s 54:0:0:0:0:1 -j RETURN Bridge chain: I-vnet0-ipv4, entries: 5, policy: DROP -p IPv4 --ip-src 10.0.0.1 -j RETURN -p IPv4 --ip-src 0.0.0.0 --ip-proto udp --ip-sport 68 -j RETURN -p IPv4 --ip-src 11.0.0.0/24 -j RETURN -p IPv4 --ip-src 10.0.0.3 -j RETURN -p IPv4 --ip-src 10.0.0.4 -j RETURN Bridge chain: O-vnet0-ipv4, entries: 1, policy: DROP -j ACCEPT Bridge chain: I-vnet0-arpmac, entries: 1, policy: DROP -p ARP --arp-mac-src 54:0:0:0:0:1 -j RETURN Bridge chain: I-vnet0-arpip, entries: 5, policy: DROP -p ARP --arp-ip-src 10.0.0.1 -j RETURN -p ARP --arp-ip-src 0.0.0.0 -j RETURN -p ARP --arp-ip-src 11.0.0.0/24 -j RETURN -p ARP --arp-ip-src 10.0.0.3 -j RETURN -p ARP --arp-ip-src 10.0.0.4 -j RETURN Bridge chain: I-vnet0-rarp, entries: 1, policy: DROP -p 0x8035 -s 54:0:0:0:0:1 -d Broadcast --arp-op Request_Reverse --arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 --arp-mac-src 54:0:0:0:0:1 --arp-mac-dst 54:0:0:0:0:1 -j ACCEPT Bridge chain: O-vnet0-rarp, entries: 1, policy: DROP -p 0x8035 -d Broadcast --arp-op Request_Reverse --arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 --arp-mac-src 54:0:0:0:0:1 --arp-mac-dst 54:0:0:0:0:1 -j ACCEPT

On 10/12/2011 05:25 PM, David Stevens wrote:
Stefan Berger<stefanb@linux.vnet.ibm.com> wrote on 10/12/2011 02:02:59 PM:
The problem we're having at the moment is that it's not possible to evaluate fields of packets that may have more than one possible value. This is the general problem, the specific one being allowing multiple MAC or IP addresses.
Stefan, Yes, this is why for this patchset I've added "RETURN" and made the address checks be "if match return" and a default drop at the end. This code already supports multiple IP addresses for DHCP snooping, static IP addresses (new to this version) and a combination of the two (if both "IP" is set and "ip_learning=dhcp". Sample output using multiple static addresses below. The same model can be applied to user-generated filters with:
<do a series of checks using RETURN for acceptable packets> -j DROP
If the user filter does "RETURN", it'll apply other tests as well. If it does "ACCEPT"/"DROP", it'll accept or drop despite any other conditions. I'm not sure I see any need for other tables here, though-- can you elaborate? +-DLS
Basically I would like to get away from having a hardcoded representation of the names of filters inside the code. We now have 4 such filters, arp, rarp, ipv4 and ipv6 and you are adding 2 more arpmac and arpip. For ipv6 we would again need to add more. I think the names of the filters should be picked up from the XML and then in some way sorted and have them generate the rules -j I-vnet0-mac -p IPv4 -j I-vnet0-ipv4 -p ARP -j I-vnet0-arpmac -p ARP -j I-vnet0-arpip -p 0x8035 -j I-vnet0-rarp in the right order. i.e., ipv4 before ipv6 before arpmac before arpip before rapr and 'arp' somewhere in between. This and later on an introduction of an action 'jump' or 'goto' with a target would then provide more flexibility for the user to build even more complex filters. I'll look into getting rid of the bit fields that I have been using so far and try to collect the names of filters. I guess they would have to be given an implicit 'priority' so they are ordered in that way above -- so maybe some of the filters are known to the system and have an implicit priority while others will have to be given a priority. Comments? Stefan
lab1.dls 226 # ebtables -t nat -L Bridge table: nat
Bridge chain: PREROUTING, entries: 1, policy: ACCEPT -i vnet0 -j libvirt-I-vnet0
Bridge chain: OUTPUT, entries: 0, policy: ACCEPT
Bridge chain: POSTROUTING, entries: 1, policy: ACCEPT -o vnet0 -j libvirt-O-vnet0
Bridge chain: libvirt-I-vnet0, entries: 9, policy: ACCEPT -j I-vnet0-mac -p IPv4 -j I-vnet0-ipv4 -p ARP -j I-vnet0-arpmac -p ARP -j I-vnet0-arpip -p 0x8035 -j I-vnet0-rarp -p 0x835 -j ACCEPT -p IPv4 -j ACCEPT -p ARP -j ACCEPT -j DROP
Bridge chain: libvirt-O-vnet0, entries: 5, policy: ACCEPT -p IPv4 -j O-vnet0-ipv4 -p 0x8035 -j O-vnet0-rarp -p IPv4 -j ACCEPT -p ARP -j ACCEPT -j DROP
Bridge chain: I-vnet0-mac, entries: 1, policy: DROP -s 54:0:0:0:0:1 -j RETURN
Bridge chain: I-vnet0-ipv4, entries: 5, policy: DROP -p IPv4 --ip-src 10.0.0.1 -j RETURN -p IPv4 --ip-src 0.0.0.0 --ip-proto udp --ip-sport 68 -j RETURN -p IPv4 --ip-src 11.0.0.0/24 -j RETURN -p IPv4 --ip-src 10.0.0.3 -j RETURN -p IPv4 --ip-src 10.0.0.4 -j RETURN
Bridge chain: O-vnet0-ipv4, entries: 1, policy: DROP -j ACCEPT
Bridge chain: I-vnet0-arpmac, entries: 1, policy: DROP -p ARP --arp-mac-src 54:0:0:0:0:1 -j RETURN
Bridge chain: I-vnet0-arpip, entries: 5, policy: DROP -p ARP --arp-ip-src 10.0.0.1 -j RETURN -p ARP --arp-ip-src 0.0.0.0 -j RETURN -p ARP --arp-ip-src 11.0.0.0/24 -j RETURN -p ARP --arp-ip-src 10.0.0.3 -j RETURN -p ARP --arp-ip-src 10.0.0.4 -j RETURN
Bridge chain: I-vnet0-rarp, entries: 1, policy: DROP -p 0x8035 -s 54:0:0:0:0:1 -d Broadcast --arp-op Request_Reverse --arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 --arp-mac-src 54:0:0:0:0:1 --arp-mac-dst 54:0:0:0:0:1 -j ACCEPT
Bridge chain: O-vnet0-rarp, entries: 1, policy: DROP -p 0x8035 -d Broadcast --arp-op Request_Reverse --arp-ip-src 0.0.0.0 --arp-ip-dst 0.0.0.0 --arp-mac-src 54:0:0:0:0:1 --arp-mac-dst 54:0:0:0:0:1 -j ACCEPT
participants (3)
-
David L Stevens
-
David Stevens
-
Stefan Berger