From patchwork Mon Feb 22 10:30:01 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 586102 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from archives.nicira.com (archives.nicira.com [96.126.127.54]) by ozlabs.org (Postfix) with ESMTP id E4482140779 for ; Mon, 22 Feb 2016 21:30:08 +1100 (AEDT) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 35795102E0; Mon, 22 Feb 2016 02:30:08 -0800 (PST) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e3.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id 3C580102DD for ; Mon, 22 Feb 2016 02:30:07 -0800 (PST) Received: from bar5.cudamail.com (localhost [127.0.0.1]) by mx1e3.cudamail.com (Postfix) with ESMTPS id BC79C420680 for ; Mon, 22 Feb 2016 03:30:06 -0700 (MST) X-ASG-Debug-ID: 1456137004-09eadd537a41bb0001-byXFYA Received: from mx3-pf1.cudamail.com ([192.168.14.2]) by bar5.cudamail.com with ESMTP id 2BDiB9DHiR5Esawc (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Mon, 22 Feb 2016 03:30:04 -0700 (MST) X-Barracuda-Envelope-From: nusiddiq@redhat.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.2 Received: from unknown (HELO mx1.redhat.com) (209.132.183.28) by mx3-pf1.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted); 22 Feb 2016 10:30:04 -0000 Received-SPF: pass (mx3-pf1.cudamail.com: SPF record at _spf1.redhat.com designates 209.132.183.28 as permitted sender) X-Barracuda-Apparent-Source-IP: 209.132.183.28 X-Barracuda-RBL-IP: 209.132.183.28 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) by mx1.redhat.com (Postfix) with ESMTPS id BD98D8F2FD for ; Mon, 22 Feb 2016 10:30:03 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (dhcp-0-174.blr.redhat.com [10.70.1.174]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u1MAU1dh027026 for ; Mon, 22 Feb 2016 05:30:02 -0500 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-V1-221002871 X-CudaMail-DTE: 022216 X-CudaMail-Originating-IP: 209.132.183.28 Organization: Red Hat X-ASG-Orig-Subj: [##CM-V1-221002871##][PATCHv2 2/2] ovn: Add l3 port security for IPv4 and IPv6 To: dev@openvswitch.org Message-ID: <56CAE329.9010807@redhat.com> Date: Mon, 22 Feb 2016 16:00:01 +0530 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.5.0 MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-Barracuda-Connect: UNKNOWN[192.168.14.2] X-Barracuda-Start-Time: 1456137004 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-ASG-Whitelist: Header =?UTF-8?B?eFwtY3VkYW1haWxcLXdoaXRlbGlzdFwtdG8=?= X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 Subject: [ovs-dev] [PATCHv2 2/2] ovn: Add l3 port security for IPv4 and IPv6 X-BeenThere: dev@openvswitch.org X-Mailman-Version: 2.1.16 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dev-bounces@openvswitch.org Sender: "dev" For every port security defined for a logical port, add following lflows in "ls_in_port_sec" and "ls_out_port_sec" stage - A priority 90 flow to allow ipv4 traffic for known ip addresses and (broadcast ip - for ingress, mainly for dhcp) - A priority 80 flow to drop all ipv4 traffic. - For ingress, a priority 90 flow to allow arp traffic for known ip addresses and priority 80 flow to drop all arp traffic - A priority 90 flow to allow ipv6 traffic for known ipv6 addresses if port security has ipv6 address(es) defined - A priority 80 flow to drop all ipv6 traffic. - A priority 50 flow to allow all traffic on that port with the matching eth address Eg. if the port security is "00:00:00:00:00:01 10.0.0.2 fe80::eaff:fe28:3390" priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && arp && arp.sha == 00:00:00:00:00:01 && (arp.spa == 10.0.0.2)), action=(next;) priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip4 && ((ip4.src == 0.0.0.0 && ip4.dst == 255.255.255.255) || ip4.src == 10.0.0.3)), action=(next;) priority=90, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip6 && (ip6.src == fe80::200:ff:fe00:1 || ip6.src == :: || ip6.src == fe80::eaff:fe28:3390)), action=(next;) priority=80, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && (arp || ip4)), action=(drop;) priority=80, match=(inport == "portname" && eth.src == 00:00:00:00:00:01 && ip6), action=(drop;) priority=50, match=(inport == "portname" && eth.src == 00:00:00:00:00:01), action=(next;) Co-Authored-by: Ben Pfaff Signed-off-by: Numan Siddique --- lib/packets.h | 16 +++ ovn/northd/ovn-northd.8.xml | 84 ++++++++++++--- ovn/northd/ovn-northd.c | 245 ++++++++++++++++++++++++++++++++++++------- ovn/ovn-nb.xml | 141 ++++++++++++++++++++++--- tests/ovn.at | 248 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 668 insertions(+), 66 deletions(-) diff --git a/lib/packets.h b/lib/packets.h index bf12937..be6b6b3 100644 --- a/lib/packets.h +++ b/lib/packets.h @@ -986,6 +986,22 @@ in6_addr_solicited_node(struct in6_addr *addr, const struct in6_addr *ip6) memcpy(&addr->s6_addr[13], &ip6->s6_addr[13], 3); } +/* + * Generates ipv6 link local address from the given eth addr + * with prefix 'fe80::/64' and stores it in 'lla' + */ +static inline void +in6_generate_lla(struct eth_addr ea, struct in6_addr *lla) +{ + union ovs_16aligned_in6_addr *taddr = (void *) lla; + memset(taddr->be16, 0, sizeof(taddr->be16)); + taddr->be16[0] = htons(0xfe80); + taddr->be16[4] = htons(((ea.ea[0] ^ 0x02) << 8) | ea.ea[1]); + taddr->be16[5] = htons(ea.ea[2] << 8 | 0x00ff); + taddr->be16[6] = htons(0xfe << 8 | ea.ea[3]); + taddr->be16[7] = ea.be16[2]; +} + static inline void ipv6_multicast_to_ethernet(struct eth_addr *eth, const struct in6_addr *ip6) { diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index 1b2912e..45e9237 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -124,12 +124,64 @@
  • - Priority 50 flows that implement ingress port security for each enabled - logical port. For logical ports on which port security is enabled, - these match the inport and the valid eth.src - address(es) and advance only those packets to the next flow table. For - logical ports on which port security is not enabled, these advance all - packets that match the inport. +

    + Port security flows to allow traffic for each enabled logical port. + For logical ports on which port security is not enabled, a priority 50 + flow is added to allow all packets that match the inport. +

    + +

    + For logical ports on which port security is enabled, following flows + are added. +

    + +

    + For each element in the port security set, +

    + +
      +
    • + Priority 90 flow is added to allow ipv4 traffic if it has ipv4 + addresses which match the inport, valid + eth.src and valid ip4.src address(es). +
    • + +
    • + Priority 90 flow is added to allow arp traffic if it has ipv4 + addresses which match the inport, valid + eth.src, valid arp.sha and valid + arp.spa address(es). +
    • + +
    • + Priority 90 flow is added to allow ipv6 traffic if it has + ipv6 address(es) which match the inport, valid + eth.src and valid ip6.src address(es). +
    • + +
    • + Priority 80 flow is added to drop all ipv4 traffic if it has + ipv4/ipv6 address(es) which match the inport and + eth.src +
    • + +
    • + Priority 80 flow is added to drop all arp traffic if it has ipv4 + addresses which match the inport and + eth.src +
    • + +
    • + Priority 80 flow is added to drop all ipv6 traffic if it has + ipv4/ipv6 address(es) which match the inport and + eth.src +
    • + +
    • + Priority 50 flow is added to allow all traffic which match the + inport and the valid eth.src +
    • +
  • @@ -270,15 +322,17 @@ output;

    This is similar to the ingress port security logic in ingress table 0, but with important differences. Most obviously, outport and - eth.dst are checked instead of inport and - eth.src. Second, packets directed to broadcast or multicast - eth.dst are always accepted instead of being subject to the - port security rules; this is implemented through a priority-100 flow that - matches on eth.mcast with action output;. - Finally, to ensure that even broadcast and multicast packets are not - delivered to disabled logical ports, a priority-150 flow for each - disabled logical outport overrides the priority-100 flow - with a drop; action. + eth.dst, ip4.dst and ip6.dstare + checked instead of inport, eth.src, + ip4.src and ip6.src. + Second, packets directed to broadcast or multicast eth.dst + are always accepted instead of being subject to the port security rules; + this is implemented through a priority-100 flow that matches on + eth.mcast with action output;. Third, flows + related to arp are not added. Finally, to ensure that even + broadcast and multicast packets are not delivered to disabled logical + ports, a priority-150 flow for each disabled logical outport + overrides the priority-100 flow with a drop; action.

    Logical Router Datapaths

    diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index b2b1a45..e416a07 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -1020,33 +1020,216 @@ extract_lport_addresses(char *address, struct lport_addresses *laddrs, return true; } -/* Appends port security constraints on L2 address field 'eth_addr_field' - * (e.g. "eth.src" or "eth.dst") to 'match'. 'port_security', with - * 'n_port_security' elements, is the collection of port_security constraints - * from an OVN_NB Logical_Port row. */ static void -build_port_security(const char *eth_addr_field, - char **port_security, size_t n_port_security, - struct ds *match) +build_port_security_arp_flow( + struct ds *match, char *port_name, struct eth_addr ea, + struct ipv4_netaddr *ipv4_addrs, int n_ipv4_addrs) { - size_t base_len = match->length; - ds_put_format(match, " && %s == {", eth_addr_field); + ds_put_format(match, "inport == %s && eth.src == "ETH_ADDR_FMT" && arp" + " && arp.sha == "ETH_ADDR_FMT" && (", port_name, + ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(ea)); + for(int i = 0; i < n_ipv4_addrs; i++) { + if (i) { + ds_put_cstr(match, " || "); + } + ds_put_format(match, "arp.spa == "IP_FMT, IP_ARGS(ipv4_addrs[i].addr)); + } + ds_put_cstr(match, ")"); +} - size_t n = 0; - for (size_t i = 0; i < n_port_security; i++) { - struct eth_addr ea; +static void +build_port_security_ipv4_flow(enum ovn_pipeline pipeline, struct ds *match, + struct ipv4_netaddr *ipv4_addrs, + int n_ipv4_addrs) +{ + char *ipv4_addr_field; + if (pipeline == P_IN) { + ipv4_addr_field = "ip4.src"; + /* For ingress pipeline allow broadcast traffic (for dhcp) */ + ds_put_cstr(match, " && ip4 && ((ip4.src == 0.0.0.0 &&" + " ip4.dst == 255.255.255.255) || "); + } + else { + ipv4_addr_field = "ip4.dst"; + ds_put_cstr(match, " && ip4 && ("); + } - if (eth_addr_from_string(port_security[i], &ea)) { - ds_put_format(match, ETH_ADDR_FMT, ETH_ADDR_ARGS(ea)); - ds_put_char(match, ' '); - n++; + for(int i = 0; i < n_ipv4_addrs; i++) { + if (i) { + ds_put_cstr(match, " || "); } + ds_put_format(match, "%s == "IP_FMT, ipv4_addr_field, + IP_ARGS(ipv4_addrs[i].addr)); + } + ds_put_cstr(match, ")"); +} + +static void +build_port_security_ipv6_flow( + enum ovn_pipeline pipeline, struct ds *match, struct eth_addr ea, + struct ipv6_netaddr *ipv6_addrs, int n_ipv6_addrs) +{ + char *ip6_addr_field; + if (pipeline == P_IN) { + ip6_addr_field = "ip6.src"; + } + else { + ip6_addr_field = "ip6.dst"; + } + + char ip6_str[INET6_ADDRSTRLEN + 1]; + /* Allow link local address */ + struct in6_addr lla; + in6_generate_lla(ea, &lla); + memset(ip6_str, 0, sizeof(ip6_str)); + ipv6_string_mapped(ip6_str, &lla); + ds_put_format(match, " && ip6 && (%s == %s", ip6_addr_field, ip6_str); + + /* Allow ip6.src=:: and ip6.dst=ff00::/8 for ND packets */ + if (pipeline == P_IN) { + ds_put_cstr(match, " || ip6.src == ::"); + } + else { + ds_put_cstr(match, " || ip6.dst == ff00::/8"); + } + for(int i = 0; i < n_ipv6_addrs; i++) { + memset(ip6_str, 0, sizeof(ip6_str)); + ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); + ds_put_format(match, " || %s == %s", ip6_addr_field, ip6_str); + } + ds_put_cstr(match, ")"); +} + +/* + * Build port security constraints on L2 and L3 address fields and add + * logical flows to S_SWITCH_(IN/OUT)_PORT_SEC stage. + * + * If there is no port security enabled for the logical port add a flow + * to allow all the traffic. + * For each port security of the logical port + * - For ingress pipine, add a priority 90 flow to allow arp packets for + * known ipv4 addresses. + * - Add a priority 90 flow to allow ipv4 packets for known ipv4 addresses + * - Add a priority 80 flow to drop arp (for ingress) and ip packets + * - Add a priority 90 flow to allow all ipv6 packets if port security + * has ipv6 address(es). + * - Add a priority 80 flow to drop ipv6 packets. + * - Add a priority 50 flow to allow all other traffic with the matching + * ethernet address. + */ +static void +build_port_security(enum ovn_pipeline pipeline, struct ovn_port *op, + struct hmap *lflows) +{ + char *eth_addr_field; + char *port_direction; + enum ovn_stage stage; + char *action; + if (pipeline == P_IN) { + eth_addr_field = "eth.src"; + port_direction = "inport"; + stage = S_SWITCH_IN_PORT_SEC; + action = "next;"; + } + else { + eth_addr_field = "eth.dst"; + port_direction = "outport"; + stage = S_SWITCH_OUT_PORT_SEC; + action = "output;"; + } + + if (!op->nbs->n_port_security) { + /* port security is disabled on this lport. */ + char *match = xasprintf("%s == %s", port_direction, op->json_key); + ovn_lflow_add(lflows, op->od, stage, 50, match, action); + free(match); + return; } - ds_chomp(match, ' '); - ds_put_cstr(match, "}"); - if (!n) { - match->length = base_len; + struct ds match = DS_EMPTY_INITIALIZER; + for (size_t i = 0; i < op->nbs->n_port_security; i++) { + struct lport_addresses ps; + if (!extract_lport_addresses(op->nbs->port_security[i], &ps, true)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_INFO_RL(&rl, "invalid syntax '%s' in port security. No MAC" + " address found", op->nbs->port_security[i]); + continue; + } + + /* Allow all traffic with priority 50 */ + ds_init(&match); + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT, + port_direction, op->json_key, eth_addr_field, + ETH_ADDR_ARGS(ps.ea)); + ovn_lflow_add(lflows, op->od, stage, 50, ds_cstr(&match), action); + ds_destroy(&match); + + if (!ps.n_ipv4_addrs && !ps.n_ipv6_addrs) { + /* port security option has only l2 address. + * Add port security constraints only on eth address which the above + * priority 50 flow takes care of*/ + continue; + } + + if (ps.n_ipv4_addrs) { + if (pipeline == P_IN) { + ds_init(&match); + /* Allow arp traffic for known ipv4 addresses */ + build_port_security_arp_flow(&match, op->json_key, ps.ea, + ps.ipv4_addrs, ps.n_ipv4_addrs); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + } + + /* Allow traffic for known ipv4 addresses */ + ds_init(&match); + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT, + port_direction, op->json_key, eth_addr_field, + ETH_ADDR_ARGS(ps.ea)); + build_port_security_ipv4_flow(pipeline, &match, ps.ipv4_addrs, + ps.n_ipv4_addrs); + ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), + action); + ds_destroy(&match); + free(ps.ipv4_addrs); + } + + /* Drop all ip and arp packets (for ingress pipline) with + * priority 80. */ + ds_init(&match); + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT, + port_direction, op->json_key, eth_addr_field, + ETH_ADDR_ARGS(ps.ea)); + if (pipeline == P_IN) { + ds_put_cstr(&match, " && (arp || ip4)"); + } + else { + ds_put_cstr(&match, " && ip4"); + } + ovn_lflow_add(lflows, op->od, stage, 80, ds_cstr(&match), "drop;"); + ds_destroy(&match); + + if (ps.n_ipv6_addrs) { + /* XXX Need to add port security flows for ipv6 ND */ + ds_init(&match); + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT, + port_direction, op->json_key, eth_addr_field, + ETH_ADDR_ARGS(ps.ea)); + build_port_security_ipv6_flow(pipeline, &match, ps.ea, + ps.ipv6_addrs, ps.n_ipv6_addrs); + ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), action); + ds_destroy(&match); + free(ps.ipv6_addrs); + } + + /* Drop all ipv6 traffic with priority 80. */ + ds_init(&match); + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT" && ip6", + port_direction, op->json_key, eth_addr_field, + ETH_ADDR_ARGS(ps.ea)); + ovn_lflow_add(lflows, op->od, stage, 80, ds_cstr(&match), "drop;"); + ds_destroy(&match); } } @@ -1273,14 +1456,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, continue; } - struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "inport == %s", op->json_key); - build_port_security("eth.src", - op->nbs->port_security, op->nbs->n_port_security, - &match); - ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC, 50, - ds_cstr(&match), "next;"); - ds_destroy(&match); + build_port_security(P_IN, op, lflows); } /* Ingress table 3: Destination lookup, ARP reply for known IPs. @@ -1424,19 +1600,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, continue; } - struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "outport == %s", op->json_key); if (lport_is_enabled(op->nbs)) { - build_port_security("eth.dst", op->nbs->port_security, - op->nbs->n_port_security, &match); - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 50, - ds_cstr(&match), "output;"); + build_port_security(P_OUT, op, lflows); } else { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format(&match, "outport == %s", op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 150, ds_cstr(&match), "drop;"); + ds_destroy(&match); } - - ds_destroy(&match); } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index dbd7bbb..cdddda5 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -348,23 +348,134 @@ -

    - A set of L2 (Ethernet) addresses from which the logical port is - allowed to send packets and to which it is allowed to receive - packets. If this column is empty, all addresses are permitted. - Logical ports are always allowed to receive packets addressed to - multicast and broadcast addresses. -

    +

    + This column controls the addresses from which the host attached to the + logical port (``the host'') is allowed to send packets and to which it + is allowed to receive packets. If this column is empty, all addresses + are permitted. +

    -

    - Each member of the set is an Ethernet address in the form - xx:xx:xx:xx:xx:xx. -

    +

    + Each element in the set must contain one Ethernet address optionally + masked followed by zero or more IPv4 or IPv6 addresses (or both). + It would restrict the host to sending packets from and receiving + packets to the ethernet addresses defined in the logical port's + column. It also restricts the inner + source MAC addresses that the host may send in ARP and IPv6 + Neighbor Discovery packets. The host is always allowed to receive packets + to multicast and broadcast Ethernet addresses. +

    -

    - This specification will be extended to support L3 port security. -

    -
    +

    + Each element in the set may additionally contain one or more IPv4 or + IPv6 addresses (or both), with optional masks. If a mask is given, it + must be a CIDR mask. In addition to the restrictions described for + Ethernet addresses above, such an element restricts the IPv4 or IPv6 + addresses from the host may send and to which it may receive to packets + to the specified addresses. A masked address, if the host part is + zero, indicates that the host is allowed to use any addresses in the + subnet; if the host part is nonzero, the mask simply indicates the size + of the subnet. In addition: +

    + +
      +
    • +

      + If any IPv4 address is given, the host is also allowed to receive + packets to the IPv4 local broadcast address 255.255.255.255 and to + IPv4 multicast addresses (224.0.0.0/4). If an IPv4 address with a + mask is given, the host is also allowed to receive packets to the + broadcast address in that specified subnet. +

      + +

      + If any IPv4 address is given, the host is additionally restricted + to sending ARP packets with the specified source IPv4 address. + (RARP is not restricted.) +

      +
    • + +
    • +

      + If any IPv6 address is given, the host is also allowed to receive + packets to IPv6 multicast addresses (ff00::/8). +

      + +

      + If any IPv6 address is given, the host is additionally restricted + to sending IPv6 Neighbor Discovery Solicitation or Advertisement + packets with the specified source address or, for solicitations, + the unspecified address. +

      +
    • +
    + +

    + If an element includes an IPv4 address, but no IPv6 addresses, then + IPv6 traffic is not allowed. If an element includes an IPv6 address, + but no IPv4 address, then IPv4 and ARP traffic is not allowed. +

    + +

    + This column uses the same lexical syntax as the column in the OVN Southbound + database's table. Multiple + addresses within an element may be space or comma separated. +

    + +

    + This column is provided as a convenience to cloud management systems, + but all of the features that it implements can be implemented as ACLs + using the table. +

    + +

    + Examples: +

    + +
    +
    80:fa:5b:06:72:b7
    +
    + The host may send traffic from and receive traffic to the specified + MAC address, and to receive traffic to Ethernet multicast and + broadcast addresses, but not otherwise. The host may not send ARP or + IPv6 Neighbor Discovery packets with inner source Ethernet addresses + other than the one specified. +
    + +
    00:23:20:00:00:00/ff:ff:ff:00:00:00
    +
    + Similar to the first example, except that any Ethernet address in the + Nicira OUI is allowed. +
    + +
    80:fa:5b:06:72:b7 192.168.1.10/24
    +
    + This adds further restrictions to the first example. The host may + send IPv4 packets from or receive IPv4 packets to only 192.168.1.10, + except that it may also receive IPv4 packets to 192.168.1.255 (based + on the subnet mask), 255.255.255.255, and any address n 224.0.0.0/4. + The host may not send ARPs with a source Ethernet address other than + 80:fa:5b:06:72:b7 or source IPv4 address other than 192.168.1.10. + The host may not send or receive any IPv6 (including IPv6 Neighbor + Discovery) traffic. +
    + +
    "80:fa:5b:12:42:ba", "80:fa:5b:06:72:b7 192.168.1.10/24"
    +
    + In this case, the host may send traffic from and receive traffic to the + specified MAC addresses - "80:fa:5b:12:42:ba" and "80:fa:5b:06:72:b7", and + to receive traffic to Ethernet multicast and broadcast addresses, + but not otherwise. With mac "80:fa:5b:12:42:ba", host may + send traffic from and receive traffic to any l3 address. + With mac "80:fa:5b:06:72:b7" the host may send IPv4 packets from or + receive IPv4 packets to only 192.168.1.10, except that it may also + receive IPv4 packets to 192.168.1.255 (based on the subnet mask), + 255.255.255.255, and any address n 224.0.0.0/4 and the host may not + send or receive any IPv6 (including IPv6 Neighbor Discovery) traffic. +
    +
    + diff --git a/tests/ovn.at b/tests/ovn.at index a510f8e..9c68329 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1345,3 +1345,251 @@ for i in 1 2 3; do done done AT_CLEANUP + +# 3 hypervisors, one logical switch, 3 logical ports per hypervisor +AT_SETUP([ovn -- 3 HVs, 1 LS, 3 lports/HV]) +AT_KEYWORDS([portsecurity]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# Create hypervisors hv[123]. +# Add vif1[123] to hv1, vif2[123] to hv2, vif3[123] to hv3. +# Add all of the vifs to a single logical switch lsw0. +# Turn off port security on vifs vif[123]1 +# Turn on l2 port security on vifs vif[123]2 +# Turn of l2 and l3 port security on vifs vif[123]3 +# Make vif13, vif2[23], vif3[123] destinations for unknown MACs. +ovn-nbctl lswitch-add lsw0 +net_add n1 +for i in 1 2 3; do + sim_add hv$i + as hv$i + ovs-vsctl add-br br-phys + ovn_attach n1 br-phys 192.168.0.$i + + for j in 1 2 3; do + ovs-vsctl add-port br-int vif$i$j -- set Interface vif$i$j external-ids:iface-id=lp$i$j options:tx_pcap=hv$i/vif$i$j-tx.pcap options:rxq_pcap=hv$i/vif$i$j-rx.pcap ofport-request=$i$j + ovn-nbctl lport-add lsw0 lp$i$j + if test $j = 1; then + ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" unknown + elif test $j = 2; then + ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" + ovn-nbctl lport-set-port-security lp$i$j f0:00:00:00:00:$i$j + else + ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" + ovn-nbctl lport-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" + fi + done +done + + +# Pre-populate the hypervisors' ARP tables so that we don't lose any +# packets for ARP resolution (native tunneling doesn't queue packets +# for ARP resolution). +ovn_populate_arp + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 +ovn-sbctl dump-flows -- list multicast_group + +# Given the name of a logical port, prints the name of the hypervisor +# on which it is located. +vif_to_hv() { + echo hv${1%?} +} + + +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} +for i in 1 2 3; do + for j in 1 2 3; do + : > $i$j.expected + done +done + +# test_ip INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... +# +# This shell function causes an ip packet to be received on INPORT. +# The packet's content has Ethernet destination DST and source SRC +# (each exactly 12 hex digits) and Ethernet type ETHTYPE (4 hex digits). +# The OUTPORTs (zero or more) list the VIFs on which the packet should +# be received. INPORT and the OUTPORTs are specified as lport numbers, +# e.g. 11 for vif11. +test_ip() { + # This packet has bad checksums but logical L3 routing doesn't check. + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 + local packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}003511110008 + shift; shift; shift; shift; shift + hv=`vif_to_hv $inport` + as $hv ovs-appctl netdev-dummy/receive vif$inport $packet + #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet + for outport; do + echo $packet | trim_zeros >> $outport.expected + done +} + +# test_arp INPORT SHA SPA TPA DROP [REPLY_HA] +# +# Causes a packet to be received on INPORT. The packet is an ARP +# request with SHA, SPA, and TPA as specified. If REPLY_HA is provided, then +# it should be the hardware address of the target to expect to receive in an +# ARP reply; otherwise no reply is expected. +# +# INPORT is an lport number, e.g. 11 for vif11. +# SHA and REPLY_HA are each 12 hex digits. +# SPA and TPA are each 8 hex digits. +test_arp() { + local inport=$1 sha=$2 spa=$3 tpa=$4 drop=$5 reply_ha=$6 + local request=ffffffffffff${sha}08060001080006040001${sha}${spa}ffffffffffff${tpa} + hv=`vif_to_hv $inport` + as $hv ovs-appctl netdev-dummy/receive vif$inport $request + #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $request + if test $drop != 1; then + if test X$reply_ha == X; then + # Expect to receive the broadcast ARP on the other logical switch ports + # if no reply is expected. + local i j + for i in 1 2 3; do + for j in 1 2 3; do + if test $i$j != $inport; then + echo $request >> $i$j.expected + fi + done + done + else + # Expect to receive the reply, if any. + local reply=${sha}${reply_ha}08060001080006040002${reply_ha}${tpa}${sha}${spa} + echo $reply >> $inport.expected + fi + fi +} + +# test_ipv6 INPORT SRC_MAC DST_MAC SRC_IP DST_IP OUTPORT... +# This function is similar to test_ip() except that it sends +# ipv6 packet +test_ipv6() { + local inport=$1 src_mac=$2 dst_mac=$3 src_ip=$4 dst_ip=$5 + local packet=${dst_mac}${src_mac}86dd6000000000083aff${src_ip}${dst_ip}0000000000000000 + shift; shift; shift; shift; shift + hv=`vif_to_hv $inport` + as $hv ovs-appctl netdev-dummy/receive vif$inport $packet + #as $hv ovs-appctl ofproto/trace br-int in_port=$inport $packet + for outport; do + echo $packet | trim_zeros >> $outport.expected + done +} + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} + +# no port security +sip=`ip_to_hex 192 168 0 12` +tip=`ip_to_hex 192 168 0 13` +# the arp packet should be allowed even if lp[123]1 is +# not configured with mac f00000000023 and ip 192.168.0.12 +for i in 1 2 3; do + test_arp ${i}1 f00000000023 $sip $tip 0 f00000000013 + for j in 1 2 3; do + if test $i != $j; then + test_ip ${i}1 f000000000${i}1 f000000000${j}1 $sip $tip ${j}1 + fi + done +done + +# l2 port security +sip=`ip_to_hex 192 168 0 12` +tip=`ip_to_hex 192 168 0 13` + +# arp packet should be allowed since lp22 is configured with +# mac f00000000022 +test_arp 22 f00000000022 $sip $tip 0 f00000000013 + +# arp packet should not be allowed since lp32 is not configured with +# mac f00000000021 +test_arp 32 f00000000021 $sip $tip 1 + +# ip packets should be allowed and received since lp[123]2 do not +# have l3 port security +sip=`ip_to_hex 192 168 0 55` +tip=`ip_to_hex 192 168 0 66` +for i in 1 2 3; do + for j in 1 2 3; do + if test $i != $j; then + test_ip ${i}2 f000000000${i}2 f000000000${j}2 $sip $tip ${j}2 + fi + done +done + +# ipv6 packets should be received by lp[123]2 +# lp[123]1 can send ipv6 traffic as there is no port security +sip=fe800000000000000000000000000000 +tip=ff020000000000000000000000000000 + +for i in 1 2 3; do + test_ipv6 ${i}1 f000000000${i}2 f000000000{i}2 $sip $tip {i}2 +done + + +# l2 and l3 port security +sip=`ip_to_hex 192 168 0 13` +tip=`ip_to_hex 192 168 0 22` +# arp packet should be allowed since lp13 is configured with +# f00000000013 and 192.168.0.13 +test_arp 13 f00000000013 $sip $tip 0 f00000000022 + +# the arp packet should be dropped because lp23 is not configured +# with mac f00000000022 +sip=`ip_to_hex 192 168 0 13` +tip=`ip_to_hex 192 168 0 22` +test_arp 23 f00000000022 $sip $tip 1 + +# the arp packet should be dropped because lp33 is not configured +# with ip 192.168.0.55 +spa=`ip_to_hex 192 168 0 55` +tpa=`ip_to_hex 192 168 0 22` +test_arp 33 f00000000031 $spa $tpa 1 + +# ip packets should not be received by lp[123]3 since +# l3 port security is enabled +sip=`ip_to_hex 192 168 0 55` +tip=`ip_to_hex 192 168 0 66` +for i in 1 2 3; do + for j in 1 2 3; do + test_ip ${i}2 f000000000${i}2 f000000000${j}3 $sip $tip + done +done + +# ipv6 packets should be dropped for lp[123]3 since +# it is configured with only ipv4 address +sip=fe800000000000000000000000000000 +tip=ff020000000000000000000000000000 + +for i in 1 2 3; do + test_ipv6 ${i}3 f000000000${i}3 f00000000022 $sip $tip +done + +# ipv6 packets should not be received by lp[123]3 +# lp[123]1 can send ipv6 traffic as there is no port security +for i in 1 2 3; do + test_ipv6 ${i}1 f000000000${i}1 f000000000${i}3 $sip $tip +done + +# Allow some time for packet forwarding. +# XXX This can be improved. +sleep 1 + +# Now check the packets actually received against the ones expected. +for i in 1 2 3; do + for j in 1 2 3; do + file=hv$i/vif$i$j-tx.pcap + echo $file + $PYTHON "$top_srcdir/utilities/ovs-pcap.in" $file | trim_zeros > $i$j.packets + sort $i$j.expected > expout + AT_CHECK([sort $i$j.packets], [0], [expout]) + echo + done +done +AT_CLEANUP