+ bool has_stateful = has_stateful_acl(od);
+ struct ovn_port *op;
+ struct ds match_in, match_out;
+
+ /* Ingress and Egress Pre-ACL Table (Priority 0): Packets are
+ * allowed by default. */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 0, "1", "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 0, "1", "next;");
+
+ /* 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;");
+
+ /* If there are any stateful ACL rules in this dapapath, we must
+ * send all IP packets through the conntrack action, which handles
+ * defragmentation, in order to match L4 headers. */
+ if (has_stateful) {
+ HMAP_FOR_EACH (op, key_node, ports) {
+ if (op->od == od && !strcmp(op->nbs->type, "router")) {
+ /* Can't use ct() for router ports. Consider the following configuration:
+ lp1(10.0.0.2) on hostA--ls1--lr0--ls2--lp2(10.0.1.2) on hostB,
+ For a ping from lp1 to lp2, First, the response will go through ct()
+ with a zone for lp2 in the ls2 ingress pipeline on hostB.
+ That ct zone knows about this connection. Next, it goes through ct()
+ with the zone for the router port in the egress pipeline of ls2 on hostB.
+ This zone does not know about the connection, as the icmp request
+ went through the logical router on hostA, not hostB. This would only work
+ with distributed conntrack state across all chassis. */
+
+ ds_init(&match_in);
+ ds_init(&match_out);
+ ds_put_format(&match_in, "ip && inport == %s", op->json_key);
+ ds_put_format(&match_out, "ip && outport == %s", op->json_key);
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 110, ds_cstr(&match_in), "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 110, ds_cstr(&match_out), "next;");
+
+ ds_destroy(&match_in);
+ ds_destroy(&match_out);
+ }
+ }
+
+ /* Ingress and Egress Pre-ACL Table (Priority 100).
+ *
+ * Regardless of whether the ACL is "from-lport" or "to-lport",
+ * we need rules in both the ingress and egress table, because
+ * the return traffic needs to be followed. */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_ACL, 100, "ip", "ct_next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_PRE_ACL, 100, "ip", "ct_next;");
+
+ /* 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". */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_ACL, 1, "ip",
+ "ct_commit; next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, 1, "ip",
+ "ct_commit; next;");
+
+ /* Ingress and Egress ACL Table (Priority 65535).
+ *
+ * Always drop traffic that's in an invalid state. 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", "drop;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
+ "ct.inv", "drop;");
+
+ /* Ingress and Egress ACL Table (Priority 65535).
+ *
+ * Always allow traffic that is established to a committed
+ * conntrack entry. 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",
+ "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
+ "ct.est && !ct.rel && !ct.new && !ct.inv",
+ "next;");
+
+ /* Ingress and Egress ACL Table (Priority 65535).
+ *
+ * Always allow traffic that is related to an existing conntrack
+ * entry. 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",
+ "next;");
+ ovn_lflow_add(lflows, od, S_SWITCH_OUT_ACL, UINT16_MAX,
+ "!ct.est && ct.rel && !ct.new && !ct.inv",
+ "next;");
+ }
+
+ /* 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;
+
+ if (!strcmp(acl->action, "allow")) {
+ /* If there are any stateful flows, we must even commit "allow"
+ * actions. 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". */
+ const char *actions = has_stateful ? "ct_commit; next;" : "next;";
+ ovn_lflow_add(lflows, od, stage,
+ acl->priority + OVN_ACL_PRI_OFFSET,
+ acl->match, actions);
+ } else if (!strcmp(acl->action, "allow-related")) {
+ struct ds match = DS_EMPTY_INITIALIZER;
+
+ /* Commit the connection tracking entry, which allows all
+ * other traffic related to this entry to flow due to the
+ * 65535 priority flow defined earlier. */
+ ds_put_format(&match, "ct.new && (%s)", acl->match);
+ ovn_lflow_add(lflows, od, stage,
+ acl->priority + OVN_ACL_PRI_OFFSET,
+ ds_cstr(&match), "ct_commit; next;");
+
+ ds_destroy(&match);
+ } else if (!strcmp(acl->action, "drop")) {
+ ovn_lflow_add(lflows, od, stage,
+ acl->priority + OVN_ACL_PRI_OFFSET,
+ acl->match, "drop;");
+ } else if (!strcmp(acl->action, "reject")) {
+ /* xxx Need to support "reject". */
+ VLOG_INFO("reject is not a supported action");
+ ovn_lflow_add(lflows, od, stage,
+ acl->priority + OVN_ACL_PRI_OFFSET,
+ acl->match, "drop;");
+ }
+ }
+}
+
+static void
+build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
+ struct hmap *lflows, struct hmap *mcgroups)
+{
+ /* This flow table structure is documented in ovn-northd(8), so please
+ * update ovn-northd.8.xml if you change anything. */