From patchwork Fri Jul 29 04:47:04 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mickey Spiegel X-Patchwork-Id: 653983 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 3s0x4k51tLz9ssM for ; Fri, 29 Jul 2016 14:45:58 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=eC7NR9RA; dkim-atps=neutral Received: from archives.nicira.com (localhost [127.0.0.1]) by archives.nicira.com (Postfix) with ESMTP id B287211433; Thu, 28 Jul 2016 21:45:57 -0700 (PDT) X-Original-To: dev@openvswitch.org Delivered-To: dev@openvswitch.org Received: from mx3v3.cudamail.com (mx3.cudamail.com [64.34.241.5]) by archives.nicira.com (Postfix) with ESMTPS id 480DF11431 for ; Thu, 28 Jul 2016 21:45:57 -0700 (PDT) Received: from bar6.cudamail.com (localhost [127.0.0.1]) by mx3v3.cudamail.com (Postfix) with ESMTPS id D44B0162587 for ; Thu, 28 Jul 2016 22:45:56 -0600 (MDT) X-ASG-Debug-ID: 1469767555-0b323747753707d0001-byXFYA Received: from mx3-pf2.cudamail.com ([192.168.14.1]) by bar6.cudamail.com with ESMTP id fybiPnuXz0xfj3cs (version=TLSv1 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Thu, 28 Jul 2016 22:45:55 -0600 (MDT) X-Barracuda-Envelope-From: mickeys.dev@gmail.com X-Barracuda-RBL-Trusted-Forwarder: 192.168.14.1 Received: from unknown (HELO mail-pa0-f67.google.com) (209.85.220.67) by mx3-pf2.cudamail.com with ESMTPS (AES128-SHA encrypted); 29 Jul 2016 04:45:54 -0000 Received-SPF: pass (mx3-pf2.cudamail.com: SPF record at _netblocks.google.com designates 209.85.220.67 as permitted sender) X-Barracuda-Apparent-Source-IP: 209.85.220.67 X-Barracuda-RBL-IP: 209.85.220.67 Received: by mail-pa0-f67.google.com with SMTP id cf3so4556958pad.2 for ; Thu, 28 Jul 2016 21:45:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id; bh=2BL/1/7h9rfZGayXiCO8OFBlu20YfMA3K6Nig+PTxDM=; b=eC7NR9RA0LG80fK6NDoBpW4/02h4KosUQ89quJo4UhkIOMHXfbQwenJqu6CxvMbdP8 9/CLmhOUrH4OLZcsXMO7GjPTCpCtG+f2kC+bQrDiL2Cx24IZOQCIGZ9mxALhL/4IPCmf 9Gbfezf1DDNPQ6dg2pZymIY8UXTEauqC60othTUVjER3eQE+h+6GDe3SsUg3z8G/u8I+ +VpM8cvL1jm/POV+wxSfk85L8jX5llDXqwYC/hZHpXkLaB5uzTnQdrS/qD2j5qE/O5D0 JbdRwbpyu9vOLkDBHNrV89tyYqz3mo+4vUv6TSw+LsBAvf4rH2YwXe17Yx/WzDTS3cec 0Eiw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=2BL/1/7h9rfZGayXiCO8OFBlu20YfMA3K6Nig+PTxDM=; b=DVZM1Pt0yGG3I7d3Y3BAuxCgkFONgcRb66Df/3KHRgUPOBGXiZctpeeJgk5aPsFGvN 8t0D/0Gshi/P6cHzW80H2r3WH+U6aqgRCkM226eJXRpihXbysZPXeLbD5gzutB7jVUZb GwhUbzT5pBekixQUfNp9F3UmitFXZ5sHPC9T1DxYSjB5dUh8kEyuP7hiU2uVm5KLvo3L oqLMd7Tw5Z66trtfr7EHBRCH7OGB+bSmf+M+xj8GwN7lAFqOPhBEQZBJQc0MHJCNgRCM COO+u8fhnPqxUuPBnAgR7w8W/Xmd5d3eeicRgngRsQZZx2X1iektcgTPc3W6zeSSHbjF M0xw== X-Gm-Message-State: AEkoous1/4UE6/3BD724CJCasAVgO37HkFF1izQgKKnBkEtvGxQgpVm3SneVrg9XweK9gA== X-Received: by 10.66.182.232 with SMTP id eh8mr65468194pac.146.1469767553475; Thu, 28 Jul 2016 21:45:53 -0700 (PDT) Received: from localhost.localdomain (c-73-202-53-195.hsd1.ca.comcast.net. [73.202.53.195]) by smtp.gmail.com with ESMTPSA id o5sm20783469pfb.9.2016.07.28.21.45.52 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Thu, 28 Jul 2016 21:45:52 -0700 (PDT) X-CudaMail-Envelope-Sender: mickeys.dev@gmail.com From: Mickey Spiegel To: dev@openvswitch.org X-CudaMail-MID: CM-V2-727060962 X-CudaMail-DTE: 072816 X-CudaMail-Originating-IP: 209.85.220.67 Date: Thu, 28 Jul 2016 21:47:04 -0700 X-ASG-Orig-Subj: [##CM-V2-727060962##][PATCH] ovn: Add second ACL stage Message-Id: <1469767624-20966-1-git-send-email-mickeys.dev@gmail.com> X-Mailer: git-send-email 1.9.1 X-GBUdb-Analysis: 0, 209.85.220.67, Ugly c=0.313739 p=-0.142857 Source Normal X-MessageSniffer-Rules: 0-0-0-32767-c X-Barracuda-Connect: UNKNOWN[192.168.14.1] X-Barracuda-Start-Time: 1469767555 X-Barracuda-Encrypted: DHE-RSA-AES256-SHA X-Barracuda-URL: https://web.cudamail.com:443/cgi-mod/mark.cgi X-Virus-Scanned: by bsmtpd at cudamail.com X-Barracuda-BRTS-Status: 1 X-Barracuda-Spam-Score: 0.60 X-Barracuda-Spam-Status: No, SCORE=0.60 using global scores of TAG_LEVEL=3.5 QUARANTINE_LEVEL=1000.0 KILL_LEVEL=4.0 tests=BSF_SC5_MJ1963, DKIM_SIGNED, RDNS_NONE X-Barracuda-Spam-Report: Code version 3.2, rules version 3.2.3.31597 Rule breakdown below pts rule name description ---- ---------------------- -------------------------------------------------- 0.00 DKIM_SIGNED Domain Keys Identified Mail: message has a signature 0.10 RDNS_NONE Delivered to trusted network by a host with no rDNS 0.50 BSF_SC5_MJ1963 Custom Rule MJ1963 Subject: [ovs-dev] [PATCH] ovn: Add second ACL stage 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: , MIME-Version: 1.0 Errors-To: dev-bounces@openvswitch.org Sender: "dev" From: Mickey Spiegel This patch adds a second logical switch ingress ACL stage, and correspondingly a second logical switch egress ACL stage. This allows for more than one ACL-based feature to be applied in the ingress and egress logical switch pipelines. The features driving the different ACL stages may be configured by different users, for example an application deployer managing security groups and a network or security admin configuring network ACLs or firewall rules. Each ACL stage is self contained. The "action" for the highest-"priority" matching row in an ACL stage determines a packet's treatment. A separate "action" will be determined in each ACL stage, according to the ACL rules configured for that ACL stage. The "priority" values are only relevant within the context of an ACL stage. ACL rules that do not specify an ACL stage are applied to the default "acl" stage. Signed-off-by: Mickey Spiegel --- ovn/northd/ovn-northd.c | 319 +++++++++++++++++++++++++++------------------- ovn/ovn-nb.ovsschema | 7 +- ovn/ovn-nb.xml | 25 ++++ ovn/utilities/ovn-nbctl.c | 35 +++-- tests/ovn-nbctl.at | 30 +++-- 5 files changed, 264 insertions(+), 152 deletions(-) diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 3047b76..2e1d314 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -97,12 +97,13 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \ PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \ PIPELINE_STAGE(SWITCH, IN, ACL, 6, "ls_in_acl") \ - PIPELINE_STAGE(SWITCH, IN, LB, 7, "ls_in_lb") \ - PIPELINE_STAGE(SWITCH, IN, STATEFUL, 8, "ls_in_stateful") \ - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 9, "ls_in_arp_rsp") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 10, "ls_in_dhcp_options") \ - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 11, "ls_in_dhcp_response") \ - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 12, "ls_in_l2_lkup") \ + PIPELINE_STAGE(SWITCH, IN, ACL2, 7, "ls_in_acl2") \ + PIPELINE_STAGE(SWITCH, IN, LB, 8, "ls_in_lb") \ + PIPELINE_STAGE(SWITCH, IN, STATEFUL, 9, "ls_in_stateful") \ + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 10, "ls_in_arp_rsp") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 11, "ls_in_dhcp_options") \ + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 12, "ls_in_dhcp_response") \ + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 13, "ls_in_l2_lkup") \ \ /* Logical switch egress stages. */ \ PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \ @@ -110,9 +111,10 @@ enum ovn_stage { PIPELINE_STAGE(SWITCH, OUT, PRE_STATEFUL, 2, "ls_out_pre_stateful") \ PIPELINE_STAGE(SWITCH, OUT, LB, 3, "ls_out_lb") \ PIPELINE_STAGE(SWITCH, OUT, ACL, 4, "ls_out_acl") \ - PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 5, "ls_out_stateful") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 6, "ls_out_port_sec_ip") \ - PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 7, "ls_out_port_sec_l2") \ + PIPELINE_STAGE(SWITCH, OUT, ACL2, 5, "ls_out_acl2") \ + PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 6, "ls_out_stateful") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_IP, 7, "ls_out_port_sec_ip") \ + PIPELINE_STAGE(SWITCH, OUT, PORT_SEC_L2, 8, "ls_out_port_sec_l2") \ \ /* Logical router ingress stages. */ \ PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") \ @@ -193,6 +195,48 @@ ovn_stage_to_datapath_type(enum ovn_stage stage) default: OVS_NOT_REACHED(); } } + +static enum ovn_stage +ovn_stage_from_acl_stage(const char *acl_stage, enum ovn_pipeline pipeline) { + if ((pipeline == P_IN) && !acl_stage) { + return S_SWITCH_IN_ACL; + } else if ((pipeline == P_OUT) && !acl_stage) { + return S_SWITCH_OUT_ACL; + } else if ((pipeline == P_IN) && !strcmp(acl_stage, "acl")) { + return S_SWITCH_IN_ACL; + } else if ((pipeline == P_OUT) && !strcmp(acl_stage, "acl")) { + return S_SWITCH_OUT_ACL; + } else if ((pipeline == P_IN) && !strcmp(acl_stage, "acl2")) { + return S_SWITCH_IN_ACL2; + } else if ((pipeline == P_OUT) && !strcmp(acl_stage, "acl2")) { + return S_SWITCH_OUT_ACL2; + } else if (pipeline == P_IN) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Bad configuration: invalid ACL stage %s", + acl_stage); + return S_SWITCH_IN_ACL; + } else { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1); + VLOG_WARN_RL(&rl, "Bad configuration: invalid ACL stage %s", + acl_stage); + return S_SWITCH_OUT_ACL; + } +} + +enum acl_stage_enum { + acl, + acl2 +}; + +static const char * +acl_stage_str_from_enum(enum acl_stage_enum stage) +{ + switch (stage) { + case acl: return "acl"; + case acl2: return "acl2"; + default: return ""; + } +} static void usage(void) @@ -1645,105 +1689,149 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows) { bool has_stateful = has_stateful_acl(od); - /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by - * default. A related rule at priority 1 is added below if there - * are any stateful ACLs in this datapath. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 0, "1", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 0, "1", "next;"); + for (int i = acl; i <= acl2; i++) { + const char *acl_stage = acl_stage_str_from_enum(i); + enum ovn_stage in_stage = ovn_stage_from_acl_stage(acl_stage, P_IN); + enum ovn_stage out_stage = ovn_stage_from_acl_stage(acl_stage, P_OUT); - if (has_stateful) { - /* Ingress and Egress ACL Table (Priority 1). - * - * By default, traffic is allowed. This is partially handled by - * the Priority 0 ACL flows added earlier, but we also need to - * commit IP flows. This is because, while the initiater's - * direction may not have any stateful rules, the server's may - * and then its return traffic would not have an associated - * conntrack entry and would return "+invalid". - * - * We use "ct_commit" for a connection that is not already known - * by the connection tracker. Once a connection is committed, - * subsequent packets will hit the flow at priority 0 that just - * uses "next;" - * - * We also check for established connections that have ct_label[0] - * set on them. That's a connection that was disallowed, but is - * now allowed by policy again since it hit this default-allow flow. - * We need to set ct_label[0]=0 to let the connection continue, - * which will be done by ct_commit() in the "stateful" stage. - * Subsequent packets will hit the flow at priority 0 that just - * uses "next;". */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 1, - "ip && (!ct.est || (ct.est && ct_label[0] == 1))", - REGBIT_CONNTRACK_COMMIT" = 1; next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 1, - "ip && (!ct.est || (ct.est && ct_label[0] == 1))", - REGBIT_CONNTRACK_COMMIT" = 1; next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Always drop traffic that's in an invalid state. Also drop - * reply direction packets for connections that have been marked - * for deletion (bit 0 of ct_label is set). - * - * This is enforced at a higher priority than ACLs can be defined. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "ct.inv || (ct.est && ct.rpl && ct_label[0] == 1)", - "drop;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "ct.inv || (ct.est && ct.rpl && ct_label[0] == 1)", - "drop;"); + /* Ingress and Egress ACL Table (Priority 0): Packets are allowed by + * default. A related rule at priority 1 is added below if there + * are any stateful ACLs in this datapath. */ + ovn_lflow_add(lflows, od, in_stage, 0, "1", "next;"); + ovn_lflow_add(lflows, od, out_stage, 0, "1", "next;"); - /* Ingress and Egress ACL Table (Priority 65535). - * - * Allow reply traffic that is part of an established - * conntrack entry that has not been marked for deletion - * (bit 0 of ct_label). We only match traffic in the - * reply direction because we want traffic in the request - * direction to hit the currently defined policy from ACLs. - * - * This is enforced at a higher priority than ACLs can be defined. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv " - "&& ct.rpl && ct_label[0] == 0", - "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "ct.est && !ct.rel && !ct.new && !ct.inv " - "&& ct.rpl && ct_label[0] == 0", - "next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Allow traffic that is related to an existing conntrack entry that - * has not been marked for deletion (bit 0 of ct_label). - * - * This is enforced at a higher priority than ACLs can be defined. - * - * NOTE: This does not support related data sessions (eg, - * a dynamically negotiated FTP data channel), but will allow - * related traffic such as an ICMP Port Unreachable through - * that's generated from a non-listening UDP port. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, - "!ct.est && ct.rel && !ct.new && !ct.inv " - "&& ct_label[0] == 0", - "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, - "!ct.est && ct.rel && !ct.new && !ct.inv " - "&& ct_label[0] == 0", - "next;"); - - /* Ingress and Egress ACL Table (Priority 65535). - * - * Not to do conntrack on ND packets. */ - ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, UINT16_MAX, "nd", "next;"); - ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX, "nd", "next;"); + if (has_stateful) { + /* Ingress and Egress ACL Table (Priority 1). + * + * By default, traffic is allowed. This is partially handled by + * the Priority 0 ACL flows added earlier, but we also need to + * commit IP flows. This is because, while the initiater's + * direction may not have any stateful rules, the server's may + * and then its return traffic would not have an associated + * conntrack entry and would return "+invalid". + * + * We use "ct_commit" for a connection that is not already known + * by the connection tracker. Once a connection is committed, + * subsequent packets will hit the flow at priority 0 that just + * uses "next;" + * + * We also check for established connections that have ct_label[0] + * set on them. That's a connection that was disallowed, but is + * now allowed by policy again since it hit this default-allow + * flow. We need to set ct_label[0]=0 to let the connection + * continue, which will be done by ct_commit() in the "stateful" + * stage. Subsequent packets will hit the flow at priority 0 that + * just uses "next;". */ + ovn_lflow_add(lflows, od, in_stage, 1, + "ip && (!ct.est || (ct.est && ct_label[0] == 1))", + REGBIT_CONNTRACK_COMMIT" = 1; next;"); + ovn_lflow_add(lflows, od, out_stage, 1, + "ip && (!ct.est || (ct.est && ct_label[0] == 1))", + REGBIT_CONNTRACK_COMMIT" = 1; next;"); + + /* Ingress and Egress ACL Table (Priority 65535). + * + * Always drop traffic that's in an invalid state. Also drop + * reply direction packets for connections that have been marked + * for deletion (bit 0 of ct_label is set). + * + * This is enforced at a higher priority than ACLs can be + * defined. */ + ovn_lflow_add(lflows, od, in_stage, UINT16_MAX, + "ct.inv || (ct.est && ct.rpl && ct_label[0] == 1)", + "drop;"); + ovn_lflow_add(lflows, od, out_stage, UINT16_MAX, + "ct.inv || (ct.est && ct.rpl && ct_label[0] == 1)", + "drop;"); + + /* Ingress and Egress ACL Table (Priority 65535). + * + * Allow reply traffic that is part of an established + * conntrack entry that has not been marked for deletion + * (bit 0 of ct_label). We only match traffic in the + * reply direction because we want traffic in the request + * direction to hit the currently defined policy from ACLs. + * + * This is enforced at a higher priority than ACLs can be + * defined. */ + ovn_lflow_add(lflows, od, in_stage, UINT16_MAX, + "ct.est && !ct.rel && !ct.new && !ct.inv " + "&& ct.rpl && ct_label[0] == 0", + "next;"); + ovn_lflow_add(lflows, od, out_stage, UINT16_MAX, + "ct.est && !ct.rel && !ct.new && !ct.inv " + "&& ct.rpl && ct_label[0] == 0", + "next;"); + + /* Ingress and Egress ACL Table (Priority 65535). + * + * Allow traffic that is related to an existing conntrack entry + * that has not been marked for deletion (bit 0 of ct_label). + * + * This is enforced at a higher priority than ACLs can be defined. + * + * NOTE: This does not support related data sessions (eg, + * a dynamically negotiated FTP data channel), but will allow + * related traffic such as an ICMP Port Unreachable through + * that's generated from a non-listening UDP port. */ + ovn_lflow_add(lflows, od, in_stage, UINT16_MAX, + "!ct.est && ct.rel && !ct.new && !ct.inv " + "&& ct_label[0] == 0", + "next;"); + ovn_lflow_add(lflows, od, out_stage, UINT16_MAX, + "!ct.est && ct.rel && !ct.new && !ct.inv " + "&& ct_label[0] == 0", + "next;"); + + /* Ingress and Egress ACL Table (Priority 65535). + * + * Not to do conntrack on ND packets. */ + ovn_lflow_add(lflows, od, in_stage, UINT16_MAX, "nd", "next;"); + ovn_lflow_add(lflows, od, out_stage, UINT16_MAX, "nd", "next;"); + } + + /* Add 34000 priority flow to allow DHCP reply from ovn-controller to + * all logical ports of the datapath if the CMS has configured DHCPv4 + * options*/ + if (od->nbs && od->nbs->n_ports) { + for (size_t i = 0; i < od->nbs->n_ports; i++) { + if (od->nbs->ports[i]->dhcpv4_options) { + const char *server_id = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, + "server_id"); + const char *server_mac = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, + "server_mac"); + const char *lease_time = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, + "lease_time"); + const char *router = smap_get( + &od->nbs->ports[i]->dhcpv4_options->options, "router"); + if (server_id && server_mac && lease_time && router) { + struct ds match = DS_EMPTY_INITIALIZER; + const char *actions = + has_stateful ? "ct_commit; next;" : "next;"; + ds_put_format(&match, + "outport == \"%s\" && eth.src == %s " + "&& ip4.src == %s && udp " + "&& udp.src == 67 && udp.dst == 68", + od->nbs->ports[i]->name, + server_mac, server_id); + ovn_lflow_add( + lflows, od, out_stage, 34000, ds_cstr(&match), + actions); + } + } + } + } } /* Ingress or Egress ACL Table (Various priorities). */ for (size_t i = 0; i < od->nbs->n_acls; i++) { struct nbrec_acl *acl = od->nbs->acls[i]; - bool ingress = !strcmp(acl->direction, "from-lport") ? true :false; - enum ovn_stage stage = ingress ? S_SWITCH_IN_ACL : S_SWITCH_OUT_ACL; + enum ovn_pipeline pipeline + = !strcmp(acl->direction, "from-lport") ? P_IN : P_OUT; + enum ovn_stage stage = ovn_stage_from_acl_stage(acl->stage, pipeline); if (!strcmp(acl->action, "allow") || !strcmp(acl->action, "allow-related")) { @@ -1849,35 +1937,6 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows) } } } - - /* Add 34000 priority flow to allow DHCP reply from ovn-controller to all - * logical ports of the datapath if the CMS has configured DHCPv4 options*/ - if (od->nbs && od->nbs->n_ports) { - for (size_t i = 0; i < od->nbs->n_ports; i++) { - if (od->nbs->ports[i]->dhcpv4_options) { - const char *server_id = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "server_id"); - const char *server_mac = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "server_mac"); - const char *lease_time = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "lease_time"); - const char *router = smap_get( - &od->nbs->ports[i]->dhcpv4_options->options, "router"); - if (server_id && server_mac && lease_time && router) { - struct ds match = DS_EMPTY_INITIALIZER; - const char *actions = - has_stateful ? "ct_commit; next;" : "next;"; - ds_put_format(&match, "outport == \"%s\" && eth.src == %s " - "&& ip4.src == %s && udp && udp.src == 67 " - "&& udp.dst == 68", od->nbs->ports[i]->name, - server_mac, server_id); - ovn_lflow_add( - lflows, od, S_SWITCH_OUT_ACL, 34000, ds_cstr(&match), - actions); - } - } - } - } } static void diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index a5dc669..4de531e 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.2.0", - "cksum": "650844440 8727", + "version": "5.3.0", + "cksum": "3860252638 8923", "tables": { "NB_Global": { "columns": { @@ -102,6 +102,9 @@ "match": {"type": "string"}, "action": {"type": {"key": {"type": "string", "enum": ["set", ["allow", "allow-related", "drop", "reject"]]}}}, + "stage": {"type": {"key": {"type": "string", + "enum": ["set", ["acl", "acl2"]]}, + "min": 0, "max": 1}}, "log": {"type": "boolean"}, "external_ids": { "type": {"key": "string", "value": "string", diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 4b61bbc..574b589 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -735,6 +735,31 @@ + +

+ OVN supports the use of more than one ACL stage. This allows for more + than one ACL-based feature to be applied in the ingress and egress + logical switch pipelines. The features driving the different ACL + stages may be configured by different users, for example an application + deployer managing security groups and a network or security admin + configuring network ACLs or firewall rules. +

+ +

+ Each ACL stage is self contained. The column + for the highest- matching row in an ACL stage + determines a packet's treatment. A separate + will be determined in each ACL stage, according to the ACL rules + configured for that ACL stage. The values + are only relevant within the context of an ACL stage. +

+ +

+ ACL rules that do not specify an ACL stage are applied to the default + acl stage. +

+
+

If set to true, packets that match the ACL will trigger a diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index 947e3a1..20baf06 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -334,9 +334,9 @@ Logical switch commands:\n\ ls-list print the names of all logical switches\n\ \n\ ACL commands:\n\ - acl-add SWITCH DIRECTION PRIORITY MATCH ACTION [log]\n\ + acl-add SWITCH DIRECTION PRIORITY MATCH ACTION [STAGE] [log]\n\ add an ACL to SWITCH\n\ - acl-del SWITCH [DIRECTION [PRIORITY MATCH]]\n\ + acl-del SWITCH [DIRECTION [PRIORITY MATCH [STAGE]]]\n\ remove ACLs from SWITCH\n\ acl-list SWITCH print ACLs for SWITCH\n\ \n\ @@ -1150,7 +1150,14 @@ acl_cmp(const void *acl1_, const void *acl2_) int dir1 = dir_encode(acl1->direction); int dir2 = dir_encode(acl2->direction); - if (dir1 != dir2) { + /* Normalize stage==NULL and stage=="acl". */ + const char *stage1 = (acl1->stage) ? acl1->stage : "acl"; + const char *stage2 = (acl2->stage) ? acl2->stage : "acl"; + + const int stage_cmp = strcmp(stage1, stage2); + if (stage_cmp) { + return stage_cmp; + } else if (dir1 != dir2) { return dir1 < dir2 ? -1 : 1; } else if (acl1->priority != acl2->priority) { return acl1->priority > acl2->priority ? -1 : 1; @@ -1177,7 +1184,8 @@ nbctl_acl_list(struct ctl_context *ctx) for (i = 0; i < ls->n_acls; i++) { const struct nbrec_acl *acl = acls[i]; - ds_put_format(&ctx->output, "%10s %5"PRId64" (%s) %s%s\n", + ds_put_format(&ctx->output, "%-4.4s %10s %5"PRId64" (%s) %s%s\n", + acl->stage ? acl->stage : "", acl->direction, acl->priority, acl->match, acl->action, acl->log ? " log" : ""); } @@ -1229,12 +1237,22 @@ nbctl_acl_add(struct ctl_context *ctx) return; } + /* Validate stage. */ + const char *stage = ctx->argc == 7 ? ctx->argv[6] : NULL; + if (stage && strcmp(stage, "acl") && strcmp(stage, "acl2")) { + ctl_fatal("%s: stage must be one of \"acl\" and \"acl2\"", stage); + return; + } + /* Create the acl. */ struct nbrec_acl *acl = nbrec_acl_insert(ctx->txn); nbrec_acl_set_priority(acl, priority); nbrec_acl_set_direction(acl, direction); nbrec_acl_set_match(acl, ctx->argv[4]); nbrec_acl_set_action(acl, action); + if (stage) { + nbrec_acl_set_stage(acl, stage); + } if (shash_find(&ctx->options, "--log") != NULL) { nbrec_acl_set_log(acl, true); } @@ -1254,7 +1272,7 @@ nbctl_acl_del(struct ctl_context *ctx) const struct nbrec_logical_switch *ls; ls = ls_by_name_or_uuid(ctx, ctx->argv[1], true); - if (ctx->argc != 2 && ctx->argc != 3 && ctx->argc != 5) { + if (ctx->argc != 2 && ctx->argc != 3 && ctx->argc != 5 && ctx->argc != 6) { ctl_fatal("cannot specify priority without match"); } @@ -1293,7 +1311,8 @@ nbctl_acl_del(struct ctl_context *ctx) struct nbrec_acl *acl = ls->acls[i]; if (priority == acl->priority && !strcmp(ctx->argv[4], acl->match) && - !strcmp(direction, acl->direction)) { + !strcmp(direction, acl->direction) && (ctx->argc == 5 || + !strcmp(ctx->argv[5], acl->stage))) { struct nbrec_acl **new_acls = xmemdup(ls->acls, sizeof *new_acls * ls->n_acls); new_acls[i] = ls->acls[ls->n_acls - 1]; @@ -2403,9 +2422,9 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "ls-list", 0, 0, "", NULL, nbctl_ls_list, NULL, "", RO }, /* acl commands. */ - { "acl-add", 5, 5, "SWITCH DIRECTION PRIORITY MATCH ACTION", NULL, + { "acl-add", 5, 6, "SWITCH DIRECTION PRIORITY MATCH ACTION [STAGE]", NULL, nbctl_acl_add, NULL, "--log", RW }, - { "acl-del", 1, 4, "SWITCH [DIRECTION [PRIORITY MATCH]]", NULL, + { "acl-del", 1, 5, "SWITCH [DIRECTION [PRIORITY MATCH [STAGE]]]", NULL, nbctl_acl_del, NULL, "", RW }, { "acl-list", 1, 1, "SWITCH", NULL, nbctl_acl_list, NULL, "", RO }, diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at index 5357ced..13d55e7 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -196,26 +196,32 @@ OVN_NBCTL_TEST_START AT_CHECK([ovn-nbctl ls-add ls0]) AT_CHECK([ovn-nbctl --log acl-add ls0 from-lport 600 udp drop]) AT_CHECK([ovn-nbctl --log acl-add ls0 to-lport 500 udp drop]) -AT_CHECK([ovn-nbctl acl-add ls0 from-lport 400 tcp drop]) +AT_CHECK([ovn-nbctl acl-add ls0 from-lport 400 tcp drop acl]) AT_CHECK([ovn-nbctl acl-add ls0 to-lport 300 tcp drop]) +AT_CHECK([ovn-nbctl acl-add ls0 from-lport 300 'ip4.dst == 10.0.0.0/24' allow acl2]) AT_CHECK([ovn-nbctl acl-add ls0 from-lport 200 ip drop]) AT_CHECK([ovn-nbctl acl-add ls0 to-lport 100 ip drop]) +AT_CHECK([ovn-nbctl acl-add ls0 from-lport 0 ip drop acl2]) AT_CHECK([ovn-nbctl acl-list ls0], [0], [dnl -from-lport 600 (udp) drop log -from-lport 400 (tcp) drop -from-lport 200 (ip) drop - to-lport 500 (udp) drop log - to-lport 300 (tcp) drop - to-lport 100 (ip) drop + from-lport 600 (udp) drop log +acl from-lport 400 (tcp) drop + from-lport 200 (ip) drop + to-lport 500 (udp) drop log + to-lport 300 (tcp) drop + to-lport 100 (ip) drop +acl2 from-lport 300 (ip4.dst == 10.0.0.0/24) allow +acl2 from-lport 0 (ip) drop ]) dnl Delete in one direction. AT_CHECK([ovn-nbctl acl-del ls0 to-lport]) AT_CHECK([ovn-nbctl acl-list ls0], [0], [dnl -from-lport 600 (udp) drop log -from-lport 400 (tcp) drop -from-lport 200 (ip) drop + from-lport 600 (udp) drop log +acl from-lport 400 (tcp) drop + from-lport 200 (ip) drop +acl2 from-lport 300 (ip4.dst == 10.0.0.0/24) allow +acl2 from-lport 0 (ip) drop ]) dnl Delete all ACLs. @@ -230,8 +236,8 @@ AT_CHECK([ovn-nbctl acl-add ls0 from-lport 200 ip drop]) dnl Delete a single flow. AT_CHECK([ovn-nbctl acl-del ls0 from-lport 400 tcp]) AT_CHECK([ovn-nbctl acl-list ls0], [0], [dnl -from-lport 600 (udp) drop -from-lport 200 (ip) drop + from-lport 600 (udp) drop + from-lport 200 (ip) drop ]) OVN_NBCTL_TEST_STOP