ovn-northd: Allow lport 'addresses' to store multiple ips in each set
[cascardo/ovs.git] / ovn / northd / ovn-northd.c
index 270b116..b2b1a45 100644 (file)
@@ -914,6 +914,112 @@ ovn_lflow_destroy(struct hmap *lflows, struct ovn_lflow *lflow)
     }
 }
 
+struct ipv4_netaddr {
+    ovs_be32 addr;
+    unsigned int plen;
+};
+
+struct ipv6_netaddr {
+    struct in6_addr addr;
+    unsigned int plen;
+};
+
+struct lport_addresses {
+    struct eth_addr ea;
+    size_t n_ipv4_addrs;
+    struct ipv4_netaddr *ipv4_addrs;
+    size_t n_ipv6_addrs;
+    struct ipv6_netaddr *ipv6_addrs;
+};
+
+/*
+ * Extracts the mac, ipv4 and ipv6 addresses from the input param 'address'
+ * which should be of the format 'MAC [IP1 IP2 ..]" where IPn should be
+ * a valid IPv4 or IPv6 address and stores them in the 'ipv4_addrs' and
+ * 'ipv6_addrs' fields of input param 'laddrs'.
+ * The caller has to free the 'ipv4_addrs' and 'ipv6_addrs' fields.
+ * If input param 'store_ipv6' is true only then extracted ipv6 addresses
+ * are stored in 'ipv6_addrs' fields.
+ * Return true if at least 'MAC' is found in 'address', false otherwise.
+ * Eg 1.
+ * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64
+ *                 30.0.0.3/23' and 'store_ipv6' = true
+ * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 1.
+ *
+ * Eg. 2
+ * If 'address' = '00:00:00:00:00:01 10.0.0.4 fe80::ea2a:eaff:fe28:3390/64
+ *                 30.0.0.3/23' and 'store_ipv6' = false
+ * then returns true with laddrs->n_ipv4_addrs = 2, naddrs->n_ipv6_addrs = 0.
+ *
+ * Eg 3. If 'address' = '00:00:00:00:00:01 10.0.0.4 addr 30.0.0.4', then
+ * returns true with laddrs->n_ipv4_addrs = 1 and laddrs->n_ipv6_addrs = 0.
+ */
+static bool
+extract_lport_addresses(char *address, struct lport_addresses *laddrs,
+                        bool store_ipv6)
+{
+    char *buf = address;
+    int buf_index = 0;
+    char *buf_end = buf + strlen(address);
+    if (!ovs_scan_len(buf, &buf_index, ETH_ADDR_SCAN_FMT,
+                      ETH_ADDR_SCAN_ARGS(laddrs->ea))) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        VLOG_INFO_RL(&rl, "invalid syntax '%s' in address. No MAC address"
+                     " found", address);
+        return false;
+    }
+
+    ovs_be32 ip4;
+    struct in6_addr ip6;
+    unsigned int plen;
+    char *error;
+
+    laddrs->n_ipv4_addrs = 0;
+    laddrs->n_ipv6_addrs = 0;
+    laddrs->ipv4_addrs = NULL;
+    laddrs->ipv6_addrs = NULL;
+
+    /* Loop through the buffer and extract the IPv4/IPv6 addresses
+     * and store in the 'laddrs'. Break the loop if invalid data is found.
+     */
+    buf += buf_index;
+    while (buf < buf_end) {
+        buf_index = 0;
+        error = ip_parse_cidr_len(buf, &buf_index, &ip4, &plen);
+        if (!error) {
+            laddrs->n_ipv4_addrs++;
+            laddrs->ipv4_addrs = xrealloc(
+                laddrs->ipv4_addrs,
+                sizeof (struct ipv4_netaddr) * laddrs->n_ipv4_addrs);
+            laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].addr = ip4;
+            laddrs->ipv4_addrs[laddrs->n_ipv4_addrs - 1].plen = plen;
+            buf += buf_index;
+            continue;
+        }
+        free(error);
+        error = ipv6_parse_cidr_len(buf, &buf_index, &ip6, &plen);
+        if (!error && store_ipv6) {
+            laddrs->n_ipv6_addrs++;
+            laddrs->ipv6_addrs = xrealloc(
+                laddrs->ipv6_addrs,
+                sizeof(struct ipv6_netaddr) * laddrs->n_ipv6_addrs);
+            memcpy(&laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].addr, &ip6,
+                   sizeof(struct in6_addr));
+            laddrs->ipv6_addrs[laddrs->n_ipv6_addrs - 1].plen = plen;
+        }
+
+        if (error) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+            VLOG_INFO_RL(&rl, "invalid syntax '%s' in address", address);
+            free(error);
+            break;
+        }
+        buf += buf_index;
+    }
+
+    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
@@ -950,6 +1056,12 @@ lport_is_enabled(const struct nbrec_logical_port *lport)
     return !lport->enabled || *lport->enabled;
 }
 
+static bool
+lport_is_up(const struct nbrec_logical_port *lport)
+{
+    return !lport->up || *lport->up;
+}
+
 static bool
 has_stateful_acl(struct ovn_datapath *od)
 {
@@ -964,9 +1076,11 @@ has_stateful_acl(struct ovn_datapath *od)
 }
 
 static void
-build_acls(struct ovn_datapath *od, struct hmap *lflows)
+build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
 {
     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. */
@@ -983,6 +1097,30 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
      * 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",
@@ -1100,7 +1238,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
-        build_acls(od, lflows);
+        build_acls(od, lflows, ports);
     }
 
     /* Logical switch ingress table 0: Admission control framework (priority
@@ -1152,15 +1290,25 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
             continue;
         }
 
-        for (size_t i = 0; i < op->nbs->n_addresses; i++) {
-            struct eth_addr ea;
-            ovs_be32 ip;
+        /*
+         * Add ARP reply flows if either the
+         *  - port is up or
+         *  - port type is router
+         */
+        if (!lport_is_up(op->nbs) && strcmp(op->nbs->type, "router")) {
+            continue;
+        }
 
-            if (ovs_scan(op->nbs->addresses[i],
-                         ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT,
-                         ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) {
+        for (size_t i = 0; i < op->nbs->n_addresses; i++) {
+            struct lport_addresses laddrs;
+            if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                         false)) {
+                continue;
+            }
+            for (size_t j = 0; j < laddrs.n_ipv4_addrs; j++) {
                 char *match = xasprintf(
-                    "arp.tpa == "IP_FMT" && arp.op == 1", IP_ARGS(ip));
+                    "arp.tpa == "IP_FMT" && arp.op == 1",
+                    IP_ARGS(laddrs.ipv4_addrs[j].addr));
                 char *actions = xasprintf(
                     "eth.dst = eth.src; "
                     "eth.src = "ETH_ADDR_FMT"; "
@@ -1172,14 +1320,16 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
                     "outport = inport; "
                     "inport = \"\"; /* Allow sending out inport. */ "
                     "output;",
-                    ETH_ADDR_ARGS(ea),
-                    ETH_ADDR_ARGS(ea),
-                    IP_ARGS(ip));
+                    ETH_ADDR_ARGS(laddrs.ea),
+                    ETH_ADDR_ARGS(laddrs.ea),
+                    IP_ARGS(laddrs.ipv4_addrs[j].addr));
                 ovn_lflow_add(lflows, op->od, S_SWITCH_IN_L2_LKUP, 150,
                               match, actions);
                 free(match);
                 free(actions);
             }
+
+            free(laddrs.ipv4_addrs);
         }
     }
 
@@ -1500,12 +1650,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
             /* XXX ARP for neighboring router */
         } else if (op->od->n_router_ports) {
             for (size_t i = 0; i < op->nbs->n_addresses; i++) {
-                struct eth_addr ea;
-                ovs_be32 ip;
+                struct lport_addresses laddrs;
+                if (!extract_lport_addresses(op->nbs->addresses[i], &laddrs,
+                                             false)) {
+                    continue;
+                }
 
-                if (ovs_scan(op->nbs->addresses[i],
-                             ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT,
-                             ETH_ADDR_SCAN_ARGS(ea), IP_SCAN_ARGS(&ip))) {
+                for (size_t k = 0; k < laddrs.n_ipv4_addrs; k++) {
+                    ovs_be32 ip = laddrs.ipv4_addrs[k].addr;
                     for (size_t j = 0; j < op->od->n_router_ports; j++) {
                         /* Get the Logical_Router_Port that the Logical_Port is
                          * connected to, as 'peer'. */
@@ -1533,7 +1685,7 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                                                   "outport = %s; "
                                                   "output;",
                                                   ETH_ADDR_ARGS(peer->mac),
-                                                  ETH_ADDR_ARGS(ea),
+                                                  ETH_ADDR_ARGS(laddrs.ea),
                                                   peer->json_key);
                         ovn_lflow_add(lflows, peer->od,
                                       S_ROUTER_IN_ARP, 200, match, actions);
@@ -1542,6 +1694,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
                         break;
                     }
                 }
+
+                free(laddrs.ipv4_addrs);
             }
         }
     }
@@ -1836,8 +1990,6 @@ add_column_noalert(struct ovsdb_idl *idl,
 int
 main(int argc, char *argv[])
 {
-    extern struct vlog_module VLM_reconnect;
-    unsigned int ovnnb_seqno, ovnsb_seqno;
     int res = EXIT_SUCCESS;
     struct unixctl_server *unixctl;
     int retval;
@@ -1846,8 +1998,6 @@ main(int argc, char *argv[])
     fatal_ignore_sigpipe();
     set_program_name(argv[0]);
     service_start(&argc, &argv);
-    vlog_set_levels(NULL, VLF_CONSOLE, VLL_WARN);
-    vlog_set_levels(&VLM_reconnect, VLF_ANY_DESTINATION, VLL_WARN);
     parse_options(argc, argv);
 
     daemonize_start(false);
@@ -1907,9 +2057,6 @@ main(int argc, char *argv[])
     add_column_noalert(ovnsb_idl_loop.idl, &sbrec_port_binding_col_mac);
     ovsdb_idl_add_column(ovnsb_idl_loop.idl, &sbrec_port_binding_col_chassis);
 
-    ovnnb_seqno = ovsdb_idl_get_seqno(ovnnb_idl_loop.idl);
-    ovnsb_seqno = ovsdb_idl_get_seqno(ovnsb_idl_loop.idl);
-
     /* Main loop. */
     exiting = false;
     while (!exiting) {
@@ -1920,14 +2067,8 @@ main(int argc, char *argv[])
             .ovnsb_txn = ovsdb_idl_loop_run(&ovnsb_idl_loop),
         };
 
-        if (ovnnb_seqno != ovsdb_idl_get_seqno(ctx.ovnnb_idl)) {
-            ovnnb_seqno = ovsdb_idl_get_seqno(ctx.ovnnb_idl);
-            ovnnb_db_run(&ctx);
-        }
-        if (ovnsb_seqno != ovsdb_idl_get_seqno(ctx.ovnsb_idl)) {
-            ovnsb_seqno = ovsdb_idl_get_seqno(ctx.ovnsb_idl);
-            ovnsb_db_run(&ctx);
-        }
+        ovnnb_db_run(&ctx);
+        ovnsb_db_run(&ctx);
 
         unixctl_server_run(unixctl);
         unixctl_server_wait(unixctl);