@@ -8599,6 +8599,34 @@ build_lrouter_flows_ingress_policy_od(
struct ovn_datapath *od, struct hmap *lflows,
struct hmap *ports);
+/* Local router ingress table ARP_RESOLVE: ARP Resolution.
+ *
+ * Multicast packets already have the outport set so just advance to next
+ * table (priority 500). */
+static void
+build_lrouter_flows_ingress_arp_resolution_od(
+ struct ovn_datapath *od, struct hmap *lflows);
+
+/* Local router ingress table ARP_RESOLVE: ARP Resolution.
+ *
+ * Any unicast packet that reaches this table is an IP packet whose
+ * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
+ * (ip4.dst/ipv6.dst is the final destination).
+ * This table resolves the IP address in
+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
+ * an Ethernet address in eth.dst.
+ */
+static void
+build_lrouter_flows_ingress_arp_resolution_op(
+ struct ovn_port *op, struct hmap *lflows,
+ struct hmap *ports,
+ struct ds *match, struct ds *actions);
+
+/* Local router ingress table ARP_RESOLVE: ARP Resolution. */
+static void
+build_lrouter_flows_ingress_arp_resolution2_od(
+ struct ovn_datapath *od, struct hmap *lflows);
+
/*
* Do not remove this comment - it is here on purpose
* It serves as a marker so that pulling operations out
@@ -8691,373 +8719,17 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
/* XXX destination unreachable */
- /* Local router ingress table ARP_RESOLVE: ARP Resolution.
- *
- * Multicast packets already have the outport set so just advance to next
- * table (priority 500). */
HMAP_FOR_EACH (od, key_node, datapaths) {
- if (!od->nbr) {
- continue;
- }
-
- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
- "ip4.mcast || ip6.mcast", "next;");
+ build_lrouter_flows_ingress_arp_resolution_od(od, lflows);
}
- /* Local router ingress table ARP_RESOLVE: ARP Resolution.
- *
- * Any unicast packet that reaches this table is an IP packet whose
- * next-hop IP address is in REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6
- * (ip4.dst/ipv6.dst is the final destination).
- * This table resolves the IP address in
- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 into an output port in outport and
- * an Ethernet address in eth.dst.
- */
HMAP_FOR_EACH (op, key_node, ports) {
- if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
- continue;
- }
-
- if (op->nbrp) {
- /* This is a logical router port. If next-hop IP address in
- * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
- * router port, then the packet is intended to eventually be sent
- * to this logical port. Set the destination mac address using
- * this port's mac address.
- *
- * The packet is still in peer's logical pipeline. So the match
- * should be on peer's outport. */
- if (op->peer && op->nbrp->peer) {
- if (op->lrp_networks.n_ipv4_addrs) {
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV4 "== ",
- op->peer->json_key);
- op_put_v4_networks(&match, op, false);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- op->lrp_networks.ea_s);
- ovn_lflow_add_with_hint(lflows, op->peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match), ds_cstr(&actions),
- &op->nbrp->header_);
- }
-
- if (op->lrp_networks.n_ipv6_addrs) {
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV6 " == ",
- op->peer->json_key);
- op_put_v6_networks(&match, op);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- op->lrp_networks.ea_s);
- ovn_lflow_add_with_hint(lflows, op->peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match), ds_cstr(&actions),
- &op->nbrp->header_);
- }
- }
-
- if (!op->derived && op->od->l3redirect_port) {
- const char *redirect_type = smap_get(&op->nbrp->options,
- "redirect-type");
- if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
- /* Packet is on a non gateway chassis and
- * has an unresolved ARP on a network behind gateway
- * chassis attached router port. Since, redirect type
- * is "bridged", instead of calling "get_arp"
- * on this node, we will redirect the packet to gateway
- * chassis, by setting destination mac router port mac.*/
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- "!is_chassis_resident(%s)", op->json_key,
- op->od->l3redirect_port->json_key);
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- op->lrp_networks.ea_s);
-
- ovn_lflow_add_with_hint(lflows, op->od,
- S_ROUTER_IN_ARP_RESOLVE, 50,
- ds_cstr(&match), ds_cstr(&actions),
- &op->nbrp->header_);
- }
- }
- } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
- && strcmp(op->nbsp->type, "virtual")) {
- /* This is a logical switch port that backs a VM or a container.
- * Extract its addresses. For each of the address, go through all
- * the router ports attached to the switch (to which this port
- * connects) and if the address in question is reachable from the
- * router port, add an ARP/ND entry in that router's pipeline. */
-
- for (size_t i = 0; i < op->n_lsp_addrs; i++) {
- const char *ea_s = op->lsp_addrs[i].ea_s;
- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
- const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
- for (size_t k = 0; k < op->od->n_router_ports; k++) {
- /* Get the Logical_Router_Port that the
- * Logical_Switch_Port is connected to, as
- * 'peer'. */
- const char *peer_name = smap_get(
- &op->od->router_ports[k]->nbsp->options,
- "router-port");
- if (!peer_name) {
- continue;
- }
-
- struct ovn_port *peer = ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbrp) {
- continue;
- }
-
- if (!find_lrp_member_ip(peer, ip_s)) {
- continue;
- }
-
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV4 " == %s",
- peer->json_key, ip_s);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match),
- ds_cstr(&actions),
- &op->nbsp->header_);
- }
- }
-
- for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
- const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
- for (size_t k = 0; k < op->od->n_router_ports; k++) {
- /* Get the Logical_Router_Port that the
- * Logical_Switch_Port is connected to, as
- * 'peer'. */
- const char *peer_name = smap_get(
- &op->od->router_ports[k]->nbsp->options,
- "router-port");
- if (!peer_name) {
- continue;
- }
-
- struct ovn_port *peer = ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbrp) {
- continue;
- }
-
- if (!find_lrp_member_ip(peer, ip_s)) {
- continue;
- }
-
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV6 " == %s",
- peer->json_key, ip_s);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match),
- ds_cstr(&actions),
- &op->nbsp->header_);
- }
- }
- }
- } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
- && !strcmp(op->nbsp->type, "virtual")) {
- /* This is a virtual port. Add ARP replies for the virtual ip with
- * the mac of the present active virtual parent.
- * If the logical port doesn't have virtual parent set in
- * Port_Binding table, then add the flow to set eth.dst to
- * 00:00:00:00:00:00 and advance to next table so that ARP is
- * resolved by router pipeline using the arp{} action.
- * The MAC_Binding entry for the virtual ip might be invalid. */
- ovs_be32 ip;
-
- const char *vip = smap_get(&op->nbsp->options,
- "virtual-ip");
- const char *virtual_parents = smap_get(&op->nbsp->options,
- "virtual-parents");
- if (!vip || !virtual_parents ||
- !ip_parse(vip, &ip) || !op->sb) {
- continue;
- }
-
- if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
- !op->sb->chassis) {
- /* The virtual port is not claimed yet. */
- for (size_t i = 0; i < op->od->n_router_ports; i++) {
- const char *peer_name = smap_get(
- &op->od->router_ports[i]->nbsp->options,
- "router-port");
- if (!peer_name) {
- continue;
- }
-
- struct ovn_port *peer = ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbrp) {
- continue;
- }
-
- if (find_lrp_member_ip(peer, vip)) {
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV4 " == %s",
- peer->json_key, vip);
-
- const char *arp_actions =
- "eth.dst = 00:00:00:00:00:00; next;";
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match),
- arp_actions,
- &op->nbsp->header_);
- break;
- }
- }
- } else {
- struct ovn_port *vp =
- ovn_port_find(ports, op->sb->virtual_parent);
- if (!vp || !vp->nbsp) {
- continue;
- }
-
- for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
- bool found_vip_network = false;
- const char *ea_s = vp->lsp_addrs[i].ea_s;
- for (size_t j = 0; j < vp->od->n_router_ports; j++) {
- /* Get the Logical_Router_Port that the
- * Logical_Switch_Port is connected to, as
- * 'peer'. */
- const char *peer_name = smap_get(
- &vp->od->router_ports[j]->nbsp->options,
- "router-port");
- if (!peer_name) {
- continue;
- }
-
- struct ovn_port *peer =
- ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbrp) {
- continue;
- }
-
- if (!find_lrp_member_ip(peer, vip)) {
- continue;
- }
-
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV4 " == %s",
- peer->json_key, vip);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match),
- ds_cstr(&actions),
- &op->nbsp->header_);
- found_vip_network = true;
- break;
- }
-
- if (found_vip_network) {
- break;
- }
- }
- }
- } else if (!strcmp(op->nbsp->type, "router")) {
- /* This is a logical switch port that connects to a router. */
-
- /* The peer of this switch port is the router port for which
- * we need to add logical flows such that it can resolve
- * ARP entries for all the other router ports connected to
- * the switch in question. */
-
- const char *peer_name = smap_get(&op->nbsp->options,
- "router-port");
- if (!peer_name) {
- continue;
- }
-
- struct ovn_port *peer = ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbrp) {
- continue;
- }
-
- if (peer->od->nbr &&
- smap_get_bool(&peer->od->nbr->options,
- "dynamic_neigh_routers", false)) {
- continue;
- }
-
- for (size_t i = 0; i < op->od->n_router_ports; i++) {
- const char *router_port_name = smap_get(
- &op->od->router_ports[i]->nbsp->options,
- "router-port");
- struct ovn_port *router_port = ovn_port_find(ports,
- router_port_name);
- if (!router_port || !router_port->nbrp) {
- continue;
- }
-
- /* Skip the router port under consideration. */
- if (router_port == peer) {
- continue;
- }
-
- if (router_port->lrp_networks.n_ipv4_addrs) {
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV4 " == ",
- peer->json_key);
- op_put_v4_networks(&match, router_port, false);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- router_port->lrp_networks.ea_s);
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match), ds_cstr(&actions),
- &op->nbsp->header_);
- }
-
- if (router_port->lrp_networks.n_ipv6_addrs) {
- ds_clear(&match);
- ds_put_format(&match, "outport == %s && "
- REG_NEXT_HOP_IPV6 " == ",
- peer->json_key);
- op_put_v6_networks(&match, router_port);
-
- ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- router_port->lrp_networks.ea_s);
- ovn_lflow_add_with_hint(lflows, peer->od,
- S_ROUTER_IN_ARP_RESOLVE, 100,
- ds_cstr(&match), ds_cstr(&actions),
- &op->nbsp->header_);
- }
- }
- }
+ build_lrouter_flows_ingress_arp_resolution_op(
+ op, lflows, ports, &match, &actions);
}
HMAP_FOR_EACH (od, key_node, datapaths) {
- if (!od->nbr) {
- continue;
- }
-
- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
- "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
-
- ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
- "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
+ build_lrouter_flows_ingress_arp_resolution2_od(od, lflows);
}
/* Local router ingress table CHK_PKT_LEN: Check packet length.
@@ -11140,6 +10812,371 @@ build_lrouter_flows_ingress_policy_od(
}
}
+static void
+build_lrouter_flows_ingress_arp_resolution_od(
+ struct ovn_datapath *od, struct hmap *lflows)
+{
+ if (od->nbr) {
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 500,
+ "ip4.mcast || ip6.mcast", "next;");
+ }
+
+}
+
+static void
+build_lrouter_flows_ingress_arp_resolution_op(
+ struct ovn_port *op, struct hmap *lflows,
+ struct hmap *ports,
+ struct ds *match, struct ds *actions)
+{
+ if (op->nbsp && !lsp_is_enabled(op->nbsp)) {
+ return;
+ }
+
+ if (op->nbrp) {
+ /* This is a logical router port. If next-hop IP address in
+ * REG_NEXT_HOP_IPV4/REG_NEXT_HOP_IPV6 matches IP address of this
+ * router port, then the packet is intended to eventually be sent
+ * to this logical port. Set the destination mac address using
+ * this port's mac address.
+ *
+ * The packet is still in peer's logical pipeline. So the match
+ * should be on peer's outport. */
+ if (op->peer && op->nbrp->peer) {
+ if (op->lrp_networks.n_ipv4_addrs) {
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV4 "== ",
+ op->peer->json_key);
+ op_put_v4_networks(match, op, false);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;",
+ op->lrp_networks.ea_s);
+ ovn_lflow_add_with_hint(lflows, op->peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_);
+ }
+
+ if (op->lrp_networks.n_ipv6_addrs) {
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV6 " == ",
+ op->peer->json_key);
+ op_put_v6_networks(match, op);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;",
+ op->lrp_networks.ea_s);
+ ovn_lflow_add_with_hint(lflows, op->peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_);
+ }
+ }
+
+ if (!op->derived && op->od->l3redirect_port) {
+ const char *redirect_type = smap_get(&op->nbrp->options,
+ "redirect-type");
+ if (redirect_type && !strcasecmp(redirect_type, "bridged")) {
+ /* Packet is on a non gateway chassis and
+ * has an unresolved ARP on a network behind gateway
+ * chassis attached router port. Since, redirect type
+ * is "bridged", instead of calling "get_arp"
+ * on this node, we will redirect the packet to gateway
+ * chassis, by setting destination mac router port mac.*/
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ "!is_chassis_resident(%s)", op->json_key,
+ op->od->l3redirect_port->json_key);
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;",
+ op->lrp_networks.ea_s);
+
+ ovn_lflow_add_with_hint(lflows, op->od,
+ S_ROUTER_IN_ARP_RESOLVE, 50,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbrp->header_);
+ }
+ }
+ } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
+ && strcmp(op->nbsp->type, "virtual")) {
+ /* This is a logical switch port that backs a VM or a container.
+ * Extract its addresses. For each of the address, go through all
+ * the router ports attached to the switch (to which this port
+ * connects) and if the address in question is reachable from the
+ * router port, add an ARP/ND entry in that router's pipeline. */
+
+ for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+ const char *ea_s = op->lsp_addrs[i].ea_s;
+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
+ /* Get the Logical_Router_Port that the
+ * Logical_Switch_Port is connected to, as
+ * 'peer'. */
+ const char *peer_name = smap_get(
+ &op->od->router_ports[k]->nbsp->options,
+ "router-port");
+ if (!peer_name) {
+ continue;
+ }
+
+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbrp) {
+ continue;
+ }
+
+ if (!find_lrp_member_ip(peer, ip_s)) {
+ continue;
+ }
+
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV4 " == %s",
+ peer->json_key, ip_s);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match),
+ ds_cstr(actions),
+ &op->nbsp->header_);
+ }
+ }
+
+ for (size_t j = 0; j < op->lsp_addrs[i].n_ipv6_addrs; j++) {
+ const char *ip_s = op->lsp_addrs[i].ipv6_addrs[j].addr_s;
+ for (size_t k = 0; k < op->od->n_router_ports; k++) {
+ /* Get the Logical_Router_Port that the
+ * Logical_Switch_Port is connected to, as
+ * 'peer'. */
+ const char *peer_name = smap_get(
+ &op->od->router_ports[k]->nbsp->options,
+ "router-port");
+ if (!peer_name) {
+ continue;
+ }
+
+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbrp) {
+ continue;
+ }
+
+ if (!find_lrp_member_ip(peer, ip_s)) {
+ continue;
+ }
+
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV6 " == %s",
+ peer->json_key, ip_s);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match),
+ ds_cstr(actions),
+ &op->nbsp->header_);
+ }
+ }
+ }
+ } else if (op->od->n_router_ports && strcmp(op->nbsp->type, "router")
+ && !strcmp(op->nbsp->type, "virtual")) {
+ /* This is a virtual port. Add ARP replies for the virtual ip with
+ * the mac of the present active virtual parent.
+ * If the logical port doesn't have virtual parent set in
+ * Port_Binding table, then add the flow to set eth.dst to
+ * 00:00:00:00:00:00 and advance to next table so that ARP is
+ * resolved by router pipeline using the arp{} action.
+ * The MAC_Binding entry for the virtual ip might be invalid. */
+ ovs_be32 ip;
+
+ const char *vip = smap_get(&op->nbsp->options,
+ "virtual-ip");
+ const char *virtual_parents = smap_get(&op->nbsp->options,
+ "virtual-parents");
+ if (!vip || !virtual_parents ||
+ !ip_parse(vip, &ip) || !op->sb) {
+ return;
+ }
+
+ if (!op->sb->virtual_parent || !op->sb->virtual_parent[0] ||
+ !op->sb->chassis) {
+ /* The virtual port is not claimed yet. */
+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
+ const char *peer_name = smap_get(
+ &op->od->router_ports[i]->nbsp->options,
+ "router-port");
+ if (!peer_name) {
+ continue;
+ }
+
+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbrp) {
+ continue;
+ }
+
+ if (find_lrp_member_ip(peer, vip)) {
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV4 " == %s",
+ peer->json_key, vip);
+
+ const char *arp_actions =
+ "eth.dst = 00:00:00:00:00:00; next;";
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match),
+ arp_actions,
+ &op->nbsp->header_);
+ break;
+ }
+ }
+ } else {
+ struct ovn_port *vp =
+ ovn_port_find(ports, op->sb->virtual_parent);
+ if (!vp || !vp->nbsp) {
+ return;
+ }
+
+ for (size_t i = 0; i < vp->n_lsp_addrs; i++) {
+ bool found_vip_network = false;
+ const char *ea_s = vp->lsp_addrs[i].ea_s;
+ for (size_t j = 0; j < vp->od->n_router_ports; j++) {
+ /* Get the Logical_Router_Port that the
+ * Logical_Switch_Port is connected to, as
+ * 'peer'. */
+ const char *peer_name = smap_get(
+ &vp->od->router_ports[j]->nbsp->options,
+ "router-port");
+ if (!peer_name) {
+ continue;
+ }
+
+ struct ovn_port *peer =
+ ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbrp) {
+ continue;
+ }
+
+ if (!find_lrp_member_ip(peer, vip)) {
+ continue;
+ }
+
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV4 " == %s",
+ peer->json_key, vip);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;", ea_s);
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match),
+ ds_cstr(actions),
+ &op->nbsp->header_);
+ found_vip_network = true;
+ break;
+ }
+
+ if (found_vip_network) {
+ break;
+ }
+ }
+ }
+ } else if (!strcmp(op->nbsp->type, "router")) {
+ /* This is a logical switch port that connects to a router. */
+
+ /* The peer of this switch port is the router port for which
+ * we need to add logical flows such that it can resolve
+ * ARP entries for all the other router ports connected to
+ * the switch in question. */
+
+ const char *peer_name = smap_get(&op->nbsp->options,
+ "router-port");
+ if (!peer_name) {
+ return;
+ }
+
+ struct ovn_port *peer = ovn_port_find(ports, peer_name);
+ if (!peer || !peer->nbrp) {
+ return;
+ }
+
+ if (peer->od->nbr &&
+ smap_get_bool(&peer->od->nbr->options,
+ "dynamic_neigh_routers", false)) {
+ return;
+ }
+
+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
+ const char *router_port_name = smap_get(
+ &op->od->router_ports[i]->nbsp->options,
+ "router-port");
+ struct ovn_port *router_port = ovn_port_find(ports,
+ router_port_name);
+ if (!router_port || !router_port->nbrp) {
+ continue;
+ }
+
+ /* Skip the router port under consideration. */
+ if (router_port == peer) {
+ continue;
+ }
+
+ if (router_port->lrp_networks.n_ipv4_addrs) {
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV4 " == ",
+ peer->json_key);
+ op_put_v4_networks(match, router_port, false);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;",
+ router_port->lrp_networks.ea_s);
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbsp->header_);
+ }
+
+ if (router_port->lrp_networks.n_ipv6_addrs) {
+ ds_clear(match);
+ ds_put_format(match, "outport == %s && "
+ REG_NEXT_HOP_IPV6 " == ",
+ peer->json_key);
+ op_put_v6_networks(match, router_port);
+
+ ds_clear(actions);
+ ds_put_format(actions, "eth.dst = %s; next;",
+ router_port->lrp_networks.ea_s);
+ ovn_lflow_add_with_hint(lflows, peer->od,
+ S_ROUTER_IN_ARP_RESOLVE, 100,
+ ds_cstr(match), ds_cstr(actions),
+ &op->nbsp->header_);
+ }
+ }
+ }
+
+}
+
+static void
+build_lrouter_flows_ingress_arp_resolution2_od(
+ struct ovn_datapath *od, struct hmap *lflows)
+{
+ if (od->nbr) {
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip4",
+ "get_arp(outport, " REG_NEXT_HOP_IPV4 "); next;");
+
+ ovn_lflow_add(lflows, od, S_ROUTER_IN_ARP_RESOLVE, 0, "ip6",
+ "get_nd(outport, " REG_NEXT_HOP_IPV6 "); next;");
+ }
+}
+
/* Updates the Logical_Flow and Multicast_Group tables in the OVN_SB database,
* constructing their contents based on the OVN_NB database. */
static void