From patchwork Wed Mar 2 19:08:42 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Numan Siddique X-Patchwork-Id: 591094 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 4AFAC1402DE for ; Thu, 3 Mar 2016 06:08:50 +1100 (AEDT) Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id 8DECB1061E; Wed, 2 Mar 2016 11:08:49 -0800 (PST) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx1e4.cudamail.com (mx1.cudamail.com [69.90.118.67]) by archives.nicira.com (Postfix) with ESMTPS id 51B6C1054B for ; Wed, 2 Mar 2016 11:08:48 -0800 (PST) Received: from bar5.cudamail.com (unknown [192.168.21.12]) by mx1e4.cudamail.com (Postfix) with ESMTPS id D0E411E0415 for ; Wed, 2 Mar 2016 12:08:47 -0700 (MST) X-ASG-Debug-ID: 1456945726-09eadd2b1331ac00001-byXFYA Received: from mx3-pf2.cudamail.com ([192.168.14.1]) by bar5.cudamail.com with ESMTP id dh3irCkH1kYGwgKx (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Wed, 02 Mar 2016 12:08:46 -0700 (MST) X-Barracuda-Envelope-From: nusiddiq@redhat.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.1 Received: from unknown (HELO mx1.redhat.com) (209.132.183.28) by mx3-pf2.cudamail.com with ESMTPS (DHE-RSA-AES256-SHA encrypted); 2 Mar 2016 19:08:46 -0000 Received-SPF: pass (mx3-pf2.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-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (Postfix) with ESMTPS id B1FA07F081; Wed, 2 Mar 2016 19:08:45 +0000 (UTC) Received: from nusiddiq.blr.redhat.com (ovpn-113-156.phx2.redhat.com [10.3.113.156]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u22J8gP3011230; Wed, 2 Mar 2016 14:08:43 -0500 X-CudaMail-Envelope-Sender: nusiddiq@redhat.com From: Numan Siddique X-CudaMail-Whitelist-To: dev@openvswitch.org X-CudaMail-MID: CM-V2-301042171 X-CudaMail-DTE: 030216 X-CudaMail-Originating-IP: 209.132.183.28 To: dev@openvswitch.org X-ASG-Orig-Subj: [##CM-V2-301042171##][PATCHv3 1/1] ovn: Add l3 port security for IPv4 and IPv6 Organization: Red Hat Message-ID: <56D73A3A.4030902@redhat.com> Date: Thu, 3 Mar 2016 00:38:42 +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.22 X-Barracuda-Connect: UNKNOWN[192.168.14.1] X-Barracuda-Start-Time: 1456945726 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] [PATCHv3 1/1] 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" This patch extends the port security to support L3. The ingress stage 'ls_in_port_sec' is renamed to 'ls_in_port_sec_l2' and 2 new stages 'ls_in_port_sec_ip' (table 1) and 'ls_in_port_sec_nd' (table 2) are added. 'ls_in_port_sec_ip' adds flows to restrict the IPv4 and IPv6 traffic to valid IPv4 and IPv6 addresses of the port. 'ls_in_port_sec_nd' adds flows to restricts the ARP and IPv6 ND packets. For egress pipeline, 'ls_out_port_sec' is renamed to 'ls_out_port_sec_l2' and a new stage 'ls_out_port_sec_ip' is added before 'ls_out_port_sec_l2' to restrict the IPv4 and IPv6 traffic for valid IPs. Co-Authored-by: Ben Pfaff Signed-Off-by: Numan Siddique --- lib/packets.h | 16 +++ ovn/northd/ovn-northd.8.xml | 123 ++++++++++++++--- ovn/northd/ovn-northd.c | 313 ++++++++++++++++++++++++++++++++++++++----- ovn/ovn-nb.xml | 120 +++++++++++++++-- tests/ovn.at | 318 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 837 insertions(+), 53 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 cacd760..53c0aff 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -111,7 +111,7 @@

Logical Switch Datapaths

-

Ingress Table 0: Admission Control and Ingress Port Security

+

Ingress Table 0: Admission Control and Ingress Port Security - L2

Ingress table 0 contains these logical flows: @@ -139,17 +139,101 @@ be dropped.

-

Ingress Table 1: from-lport Pre-ACLs

+

Ingress Table 1: Ingress Port Security - IP

- Ingress table 1 prepares flows for possible stateful ACL processing - in table 2. It contains a priority-0 flow that simply moves - traffic to table 2. If stateful ACLs are used in the logical + Ingress table 1 contains these logical flows: +

+ +
    +
  • +

    + For each element in the port security set having one or more IPv4 or + IPv6 addresses (or both), +

    + +
      +
    • + Priority 90 flow 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 to allow IPv6 traffic if it has IPv6 addresses + which match the inport, valid eth.src and + valid ip6.src address(es). +
    • + +
    • + Priority 80 flow to drop IP (both IPv4 and IPv6) traffic which + match the inport and valid eth.src. +
    • +
    +
  • + +
  • + One priority-0 fallback flow that matches all packets and advances to + table 2. +
  • +
+ +

Ingress Table 2: Ingress Port Security - Neighbor discovery

+ +

+ Ingress table 2 contains these logical flows: +

+ +
    +
  • +

    + For each element in the port security set, +

    + +
      +
    • + Priority 90 flow to allow ARP traffic which match the + inport and valid eth.src and + arp.sha. If the element has one or more + IPv4 addresses, then it also matches the valid + arp.spa. +
    • + +
    • + Priority 90 flow to allow IPv6 Neighbor Solicitation and + Advertisement traffic which match the inport, + valid eth.src and + nd.sll/nd.tll. + If the element has one or more IPv6 addresses, then it also + matches the valid nd.target address(es) for Neighbor + Advertisement traffic. +
    • + +
    • + Priority 80 flow to drop ARP and IPv6 Neighbor Solicitation and + Advertisement traffic which match the inport and + valid eth.src. +
    • +
    +
  • + +
  • + One priority-0 fallback flow that matches all packets and advances to + table 3. +
  • +
+ +

Ingress Table 3: from-lport Pre-ACLs

+ +

+ Ingress table 3 prepares flows for possible stateful ACL processing + in table 4. It contains a priority-0 flow that simply moves + traffic to table 4. If stateful ACLs are used in the logical datapath, a priority-100 flow is added that sends IP packets to - the connection tracker before advancing to table 2. + the connection tracker before advancing to table 4.

-

Ingress table 2: from-lport ACLs

+

Ingress table 4: from-lport ACLs

Logical flows in this table closely reproduce those in the @@ -163,7 +247,7 @@

- Ingress table 2 also contains a priority 0 flow with action + Ingress table 4 also contains a priority 0 flow with action next;, so that ACLs allow packets by default. If the logical datapath has a statetful ACL, the following flows will also be added: @@ -195,7 +279,7 @@ -

Ingress Table 3: ARP responder

+

Ingress Table 5: ARP responder

This table implements ARP responder for known IPs. It contains these @@ -205,7 +289,7 @@

  • Priority-100 flows to skip ARP responder if inport is of type - localnet, and advances directly to table 3. + localnet, and advances directly to table 6.
  • @@ -236,11 +320,11 @@ output;
  • One priority-0 fallback flow that matches all packets and advances to - table 4. + table 6.
-

Ingress Table 4: Destination Lookup

+

Ingress Table 6: Destination Lookup

This table implements switching behavior. It contains these logical @@ -274,17 +358,26 @@ output;

Egress Table 0: to-lport Pre-ACLs

- This is similar to ingress table 1 except for to-lport + This is similar to ingress table 3 except for to-lport traffic.

Egress Table 1: to-lport ACLs

- This is similar to ingress table 2 except for to-lport ACLs. + This is similar to ingress table 4 except for to-lport ACLs. +

+ +

Egress Table 2: Egress Port Security - IP

+ +

+ This is similar to the ingress port security logic in table 1 except + that outport, eth.dst, ip4.dst + and ip6.dst are checked instead of inport, + eth.src, ip4.src and ip6.src

-

Egress Table 2: Egress Port Security

+

Egress Table 3: Egress Port Security - L2

This is similar to the ingress port security logic in ingress table 0, diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 35ec267..59bb725 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -80,21 +80,24 @@ enum ovn_datapath_type { * An "enum ovn_stage" indicates whether the stage is part of a logical switch * or router, whether the stage is part of the ingress or egress pipeline, and * the table within that pipeline. The first three components are combined to - * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC, + * form the stage's full name, e.g. S_SWITCH_IN_PORT_SEC_L2, * S_ROUTER_OUT_DELIVERY. */ enum ovn_stage { #define PIPELINE_STAGES \ /* Logical switch ingress stages. */ \ - PIPELINE_STAGE(SWITCH, IN, PORT_SEC, 0, "ls_in_port_sec") \ - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 1, "ls_in_pre_acl") \ - PIPELINE_STAGE(SWITCH, IN, ACL, 2, "ls_in_acl") \ - PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 3, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 4, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_L2, 0, "ls_in_port_sec_l2") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_IP, 1, "ls_in_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, IN, PORT_SEC_ND, 2, "ls_in_port_sec_nd") \ + PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 3, "ls_in_pre_acl") \ + PIPELINE_STAGE(SWITCH, IN, ACL, 4, "ls_in_acl") \ + PIPELINE_STAGE(SWITCH, IN, ARP_RSP, 5, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 6, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") \ PIPELINE_STAGE(SWITCH, OUT, ACL, 1, "ls_out_acl") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC, 2, "ls_out_port_sec") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 2, "ls_out_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 3, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ @@ -1023,9 +1026,9 @@ extract_lport_addresses(char *address, struct lport_addresses *laddrs, * '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_l2(const char *eth_addr_field, + char **port_security, size_t n_port_security, + struct ds *match) { size_t base_len = match->length; ds_put_format(match, " && %s == {", eth_addr_field); @@ -1048,6 +1051,227 @@ build_port_security(const char *eth_addr_field, } } +static void +build_port_security_ipv6_nd_flow( + struct ds *match, struct eth_addr ea, struct ipv6_netaddr *ipv6_addrs, + int n_ipv6_addrs) +{ + ds_put_format(match, " && ip6 && nd && ((nd.sll == "ETH_ADDR_FMT" || " + "nd.sll == "ETH_ADDR_FMT") || ((nd.tll == "ETH_ADDR_FMT" || " + "nd.tll == "ETH_ADDR_FMT")", ETH_ADDR_ARGS(eth_addr_zero), + ETH_ADDR_ARGS(ea), ETH_ADDR_ARGS(eth_addr_zero), + ETH_ADDR_ARGS(ea)); + if (!n_ipv6_addrs) { + ds_put_cstr(match, "))"); + return; + } + + char ip6_str[INET6_ADDRSTRLEN + 1]; + 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, " && (nd.target == %s", ip6_str); + + 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, " || nd.target == %s", ip6_str); + } + + ds_put_format(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_str[INET6_ADDRSTRLEN + 1]; + + ds_put_format(match, " && %s == {", + pipeline == P_IN ? "ip6.src" : "ip6.dst"); + + /* Allow link-local address. */ + struct in6_addr lla; + in6_generate_lla(ea, &lla); + ipv6_string_mapped(ip6_str, &lla); + ds_put_format(match, "%s, ", ip6_str); + + /* Allow ip6.src=:: and ip6.dst=ff00::/8 for ND packets */ + ds_put_cstr(match, pipeline == P_IN ? "::" : "ff00::/8"); + for(int i = 0; i < n_ipv6_addrs; i++) { + ipv6_string_mapped(ip6_str, &ipv6_addrs[i].addr); + ds_put_format(match, ", %s", ip6_str); + } + ds_put_cstr(match, "}"); +} + +/** + * Build port security constraints on ARP and IPv6 ND fields + * and add logical flows to S_SWITCH_IN_PORT_SEC_ND stage. + * + * For each port security of the logical port, following + * logical flows are added + * - If the port security has no IP (both IPv4 and IPv6) or + * if it has IPv4 address(es) + * - Priority 90 flow to allow ARP packets for known MAC addresses + * in the eth.src and arp.spa fields. If the port security + * has IPv4 addresses, allow known IPv4 addresses in the arp.tpa field. + * + * - If the port security has no IP (both IPv4 and IPv6) or + * if it has IPv6 address(es) + * - Priority 90 flow to allow IPv6 ND packets for known MAC addresses + * in the eth.src and nd.sll/nd.tll fields. If the port security + * has IPv6 addresses, allow known IPv6 addresses in the nd.target field + * for IPv6 Neighbor Advertisement packet. + * + * - Priority 80 flow to drop ARP and IPv6 ND packets. + */ +static void +build_port_security_nd(struct ovn_port *op, struct hmap *lflows) +{ + 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; + } + + bool no_ip = !(ps.n_ipv4_addrs || ps.n_ipv6_addrs); + struct ds match = DS_EMPTY_INITIALIZER; + + if (ps.n_ipv4_addrs || no_ip) { + ds_put_format( + &match, "inport == %s && eth.src == "ETH_ADDR_FMT" && arp.sha == " + ETH_ADDR_FMT, op->json_key, ETH_ADDR_ARGS(ps.ea), + ETH_ADDR_ARGS(ps.ea)); + + if (ps.n_ipv4_addrs) { + ds_put_cstr(&match, " && ("); + for (size_t i = 0; i < ps.n_ipv4_addrs; i++) { + ds_put_format(&match, "arp.spa == "IP_FMT" || ", + IP_ARGS(ps.ipv4_addrs[i].addr)); + } + ds_chomp(&match, ' '); + ds_chomp(&match, '|'); + ds_chomp(&match, '|'); + ds_put_cstr(&match, ")"); + } + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + } + + if (ps.n_ipv6_addrs || no_ip) { + ds_init(&match); + ds_put_format(&match, "inport == %s && eth.src == "ETH_ADDR_FMT, + op->json_key, ETH_ADDR_ARGS(ps.ea)); + build_port_security_ipv6_nd_flow(&match, ps.ea, ps.ipv6_addrs, + ps.n_ipv6_addrs); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 90, + ds_cstr(&match), "next;"); + ds_destroy(&match); + } + free(ps.ipv4_addrs); + free(ps.ipv6_addrs); + } + + char *match = xasprintf("inport == %s && (arp || nd)", op->json_key); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_ND, 80, + match, "drop;"); + free(match); +} + +/** + * Build port security constraints on IPv4 and IPv6 src and dst fields + * and add logical flows to S_SWITCH_(IN/OUT)_PORT_SEC_IP stage. + * + * For each port security of the logical port, following + * logical flows are added + * - If the port security has IPv4 addresses, + * - Priority 90 flow to allow IPv4 packets for known IPv4 addresses + * + * - If the port security has IPv6 addresses, + * - Priority 90 flow to allow IPv6 packets for known IPv6 addresses + * + * - If the port security has IPv4 addresses or IPv6 addresses or both + * - Priority 80 flow to drop all IPv4 and IPv6 traffic + */ +static void +build_port_security_ip(enum ovn_pipeline pipeline, struct ovn_port *op, + struct hmap *lflows) +{ + char *port_direction; + enum ovn_stage stage; + if (pipeline == P_IN) { + port_direction = "inport"; + stage = S_SWITCH_IN_PORT_SEC_IP; + } else { + port_direction = "outport"; + stage = S_SWITCH_OUT_PORT_SEC_IP; + } + + 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)) { + continue; + } + + if (!(ps.n_ipv4_addrs || ps.n_ipv6_addrs)) { + continue; + } + + if (ps.n_ipv4_addrs) { + struct ds match = DS_EMPTY_INITIALIZER; + if (pipeline == P_IN) { + ds_put_format(&match, "inport == %s && eth.src == "ETH_ADDR_FMT + " && ip4.src == {0.0.0.0, ", op->json_key, + ETH_ADDR_ARGS(ps.ea)); + } else { + ds_put_format(&match, "outport == %s && eth.dst == "ETH_ADDR_FMT + " && ip4.dst == {255.255.255.255, 224.0.0.0/4, ", + op->json_key, ETH_ADDR_ARGS(ps.ea)); + } + + for(int i = 0; i < ps.n_ipv4_addrs; i++) { + ds_put_format(&match, IP_FMT", ", IP_ARGS(ps.ipv4_addrs[i].addr)); + } + + /* Replace ", " by "}". */ + ds_chomp(&match, ' '); + ds_chomp(&match, ','); + ds_put_cstr(&match, "}"); + ovn_lflow_add(lflows, op->od, stage, 90, ds_cstr(&match), "next;"); + ds_destroy(&match); + free(ps.ipv4_addrs); + } + + if (ps.n_ipv6_addrs) { + struct ds match = DS_EMPTY_INITIALIZER; + ds_put_format(&match, "%s == %s && %s == "ETH_ADDR_FMT"", + port_direction, op->json_key, + pipeline == P_IN ? "eth.src" : "eth.dst", + 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), "next;"); + ds_destroy(&match); + free(ps.ipv6_addrs); + } + + char *match = xasprintf( + "%s == %s && %s == "ETH_ADDR_FMT" && ip", port_direction, + op->json_key, pipeline == P_IN ? "eth.src" : "eth.dst", + ETH_ADDR_ARGS(ps.ea)); + ovn_lflow_add(lflows, op->od, stage, 80, match, "drop;"); + free(match); + } +} + static bool lport_is_enabled(const struct nbrec_logical_port *lport) { @@ -1229,7 +1453,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, * update ovn-northd.8.xml if you change anything. */ /* Build pre-ACL and ACL tables for both ingress and egress. - * Ingress tables 1 and 2. Egress tables 0 and 1. */ + * Ingress tables 3 and 4. Egress tables 0 and 1. */ struct ovn_datapath *od; HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1247,18 +1471,22 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } /* Logical VLANs not supported. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC, 100, "vlan.present", + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "vlan.present", "drop;"); /* Broadcast/multicast source address is invalid. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC, 100, "eth.src[40]", + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_L2, 100, "eth.src[40]", "drop;"); /* Port security flows have priority 50 (see below) and will continue * to the next table if packet source is acceptable. */ } - /* Logical switch ingress table 0: Ingress port security (priority 50). */ + /* Logical switch ingress table 0: Ingress port security - L2 + * (priority 50). + * Ingress table 1: Ingress port security - IP (priority 90 and 80) + * Ingress table 2: Ingress port security - ND (priority 90 and 80) + */ struct ovn_port *op; HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1273,12 +1501,28 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, 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, + build_port_security_l2( + "eth.src", op->nbs->port_security, op->nbs->n_port_security, + &match); + ovn_lflow_add(lflows, op->od, S_SWITCH_IN_PORT_SEC_L2, 50, ds_cstr(&match), "next;"); ds_destroy(&match); + + if (op->nbs->n_port_security) { + build_port_security_ip(P_IN, op, lflows); + build_port_security_nd(op, lflows); + } + } + + /* Ingress table 1 and 2: Port security - IP and ND, by default goto next. + * (priority 0)*/ + HMAP_FOR_EACH (od, key_node, datapaths) { + if (!od->nbs) { + continue; + } + + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_ND, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;"); } /* Ingress table 3: ARP responder, skip requests coming from localnet ports. @@ -1296,7 +1540,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 3: ARP responder, reply for known IPs. + /* Ingress table 5: ARP responder, reply for known IPs. * (priority 50). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1346,7 +1590,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 3: ARP responder, by default goto next. + /* Ingress table 5: ARP responder, by default goto next. * (priority 0)*/ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { @@ -1356,7 +1600,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_RSP, 0, "1", "next;"); } - /* Ingress table 4: Destination lookup, broadcast and multicast handling + /* Ingress table 6: Destination lookup, broadcast and multicast handling * (priority 100). */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { @@ -1376,7 +1620,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, "outport = \""MC_FLOOD"\"; output;"); } - /* Ingress table 4: Destination lookup, unicast handling (priority 50), */ + /* Ingress table 6: Destination lookup, unicast handling (priority 50), */ HMAP_FOR_EACH (op, key_node, ports) { if (!op->nbs) { continue; @@ -1413,7 +1657,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Ingress table 4: Destination lookup for unknown MACs (priority 0). */ + /* Ingress table 6: Destination lookup for unknown MACs (priority 0). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; @@ -1425,18 +1669,23 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, } } - /* Egress table 2: Egress port security multicast/broadcast (priority + /* Egress table 2: Egress port security - IP (priority 0) + * port security L2 - multicast/broadcast (priority * 100). */ HMAP_FOR_EACH (od, key_node, datapaths) { if (!od->nbs) { continue; } - ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC, 100, "eth.mcast", + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_IP, 0, "1", "next;"); + ovn_lflow_add(lflows, od, S_SWITCH_OUT_PORT_SEC_L2, 100, "eth.mcast", "output;"); } - /* Egress table 2: Egress port security (priorities 50 and 150). + /* Egress table 2: Egress port security - IP (priorities 90 and 80) + * if port security enabled. + * + * Egress table 3: Egress port security - L2 (priorities 50 and 150). * * Priority 50 rules implement port security for enabled logical port. * @@ -1450,16 +1699,20 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports, 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, + build_port_security_l2("eth.dst", op->nbs->port_security, + op->nbs->n_port_security, &match); + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 50, ds_cstr(&match), "output;"); } else { - ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC, 150, + ovn_lflow_add(lflows, op->od, S_SWITCH_OUT_PORT_SEC_L2, 150, ds_cstr(&match), "drop;"); } ds_destroy(&match); + + if (op->nbs->n_port_security) { + build_port_security_ip(P_OUT, op, lflows); + } } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 5c8e942..e65bc3a 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -362,21 +362,125 @@

- 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 begin with one Ethernet address. + This 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 which the host may send and to which it may receive + packets to the specified addresses. A masked address, if the host part + is zero, indicates that the host is allowed to use any address 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. +
+ +
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 in 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"
+
+ The host may send traffic from and receive traffic to the + specified MAC addresses, and + to receive traffic to Ethernet multicast and broadcast addresses, + but not otherwise. With MAC 80:fa:5b:12:42:ba, the 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 in 224.0.0.0/4. 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 5cb7d8b..9d69526 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1376,3 +1376,321 @@ for daemon in ovn-controller ovn-northd ovsdb-server; do ovs-appctl -t $daemon exit done AT_CLEANUP + +# 3 hypervisors, one logical switch, 3 logical ports per hypervisor +AT_SETUP([ovn -- portsecurity : 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 + extra_addr="f0:00:00:00:0$i:$i$j fe80::ea2a:eaff:fe28:$i$j" + ovn-nbctl lport-set-addresses lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" + ovn-nbctl lport-set-port-security lp$i$j "f0:00:00:00:00:$i$j 192.168.0.$i$j" "$extra_addr" + fi + done +done + +ovn-nbctl show + +# 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 + +echo "------ hv1 dump ------" +as hv1 ovs-vsctl show +as hv1 ovs-ofctl -O OpenFlow13 dump-flows br-int + +echo "------ hv2 dump ------" +as hv2 ovs-vsctl show +as hv2 ovs-ofctl -O OpenFlow13 dump-flows br-int + +echo "------ hv3 dump ------" +as hv3 ovs-vsctl show +as hv3 ovs-ofctl -O OpenFlow13 dump-flows br-int + +# 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 smac=$2 sha=$3 spa=$4 tpa=$5 drop=$6 reply_ha=$7 + local request=ffffffffffff${smac}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=${smac}${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 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 f00000000022 $sip $tip 0 f00000000013 + +# arp packet should not be allowed since lp32 is not configured with +# mac f00000000021 +test_arp 32 f00000000021 f00000000021 $sip $tip 1 + +# arp packet with sha set to f00000000021 should not be allowed +# for lp12 +test_arp 12 f00000000012 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}1 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 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 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 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 with mac f000000000$[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 + +# lp13 has extra port security with mac f0000000113 and ipv6 addr +# fe80::ea2a:eaff:fe28:0012 + +# ipv4 packet should be dropped for lp13 with mac f0000000113 +sip=`ip_to_hex 192 168 0 13` +tip=`ip_to_hex 192 168 0 23` +test_ip 13 f00000000113 f00000000023 $sip $tip + +# ipv6 packet should be received by lp[123]3 with mac f0000000{i}{i}3 +# and ip6.dst as fe80::ea2a:eaff:fe28:0{i}{i}3. +# lp11 can send ipv6 traffic as there is no port security +sip=ee800000000000000000000000000000 +for i in 1 2 3; do + tip=fe80000000000000ea2aeafffe2800{i}3 + test_ipv6 11 f00000000011 f000000000{i}${i}3 $sip $tip {i}3 +done + + +# ipv6 packet should not be received by lp33 with mac f0000000333 +# and ip6.dst as fe80::ea2a:eaff:fe28:0023 as it is +# configured with fe80::ea2a:eaff:fe28:0033 +# lp11 can send ipv6 traffic as there is no port security + +sip=ee800000000000000000000000000000 +tip=fe80000000000000ea2aeafffe280023 +test_ipv6 11 f00000000011 f00000000333 $sip $tip + +# ipv6 packet should be allowed for lp[123]3 with mac f0000000{i}{i}3 +# and ip6.src fe80::ea2a:eaff:fe28:0{i}{i}3 and ip6.src ::. +# and should be dropped for any other ip6.src +# lp21 can receive ipv6 traffic as there is no port security + +tip=ee800000000000000000000000000000 +for i in 1 2 3; do + sip=fe80000000000000ea2aeafffe2800${i}3 + test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21 + + sip=00000000000000000000000000000000 + test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $sip $tip 21 + + # should be dropped + sip=ae80000000000000ea2aeafffe2800aa + test_ipv6 ${i}3 f00000000${i}${i}3 f00000000021 $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 + +# Gracefully terminate daemons +for daemon in ovn-controller ovn-northd ovsdb-server; do + ovs-appctl -t $daemon exit +done +AT_CLEANUP