@@ -218,6 +218,12 @@ consider_local_datapath(struct controller_ctx *ctx,
sbrec_port_binding_set_chassis(binding_rec, chassis_rec);
add_local_datapath(local_datapaths, binding_rec);
}
+ } else if (!strcmp(binding_rec->type, "gateway")) {
+ const char *chassis = smap_get(&binding_rec->options,
+ "gateway-chassis");
+ if (!strcmp(chassis, chassis_rec->name) && ctx->ovnsb_idl_txn) {
+ add_local_datapath(local_datapaths, binding_rec);
+ }
} else if (chassis_rec && binding_rec->chassis == chassis_rec
&& strcmp(binding_rec->type, "gateway")) {
if (ctx->ovnsb_idl_txn) {
@@ -208,7 +208,7 @@
<code>ovn-controller</code> to connect the integration bridge and
another bridge to implement a <code>l2gateway</code> logical port.
Its value is the name of the logical port with <code>type</code>
- set to <code>l3gateway</code> that the port implements. See
+ set to <code>l2gateway</code> that the port implements. See
<code>external_ids:ovn-bridge-mappings</code>, above, for more
information.
</p>
@@ -222,6 +222,22 @@
</dd>
<dt>
+ <code>external-ids:ovn-l3gateway-port</code> in the <code>Port</code>
+ table
+ </dt>
+
+ <dd>
+ <p>
+ This key identifies a patch port as one created by
+ <code>ovn-controller</code> to implement a <code>l3gateway</code>
+ logical port. Its value is the name of the logical port with type
+ set to <code>l3gateway</code>. This patch port is similar to
+ the OVN logical patch port, except that <code>l3gateway</code>
+ port can only be bound to a paticular chassis.
+ </p>
+ </dd>
+
+ <dt>
<code>external-ids:ovn-logical-patch-port</code> in the
<code>Port</code> table
</dt>
@@ -345,12 +345,14 @@ add_logical_patch_ports(struct controller_ctx *ctx,
const struct sbrec_port_binding *binding;
SBREC_PORT_BINDING_FOR_EACH (binding, ctx->ovnsb_idl) {
+ const char *patch_port_id = "ovn-logical-patch-port";
bool local_port = false;
if (!strcmp(binding->type, "gateway")) {
const char *chassis = smap_get(&binding->options,
"gateway-chassis");
if (chassis && !strcmp(local_chassis_id, chassis)) {
local_port = true;
+ patch_port_id = "ovn-l3gateway-port";
}
}
@@ -363,7 +365,7 @@ add_logical_patch_ports(struct controller_ctx *ctx,
char *src_name = patch_port_name(local, peer);
char *dst_name = patch_port_name(peer, local);
- create_patch_port(ctx, "ovn-logical-patch-port", local,
+ create_patch_port(ctx, patch_port_id, local,
br_int, src_name, br_int, dst_name,
existing_ports);
free(dst_name);
@@ -394,6 +396,7 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
OVSREC_PORT_FOR_EACH (port, ctx->ovs_idl) {
if (smap_get(&port->external_ids, "ovn-localnet-port")
|| smap_get(&port->external_ids, "ovn-l2gateway-port")
+ || smap_get(&port->external_ids, "ovn-l3gateway-port")
|| smap_get(&port->external_ids, "ovn-logical-patch-port")) {
shash_add(&existing_ports, port->name, port);
}
@@ -402,7 +405,8 @@ patch_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int,
/* Create in the database any patch ports that should exist. Remove from
* 'existing_ports' any patch ports that do exist in the database and
* should be there. */
- add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths, chassis_id);
+ add_bridge_mappings(ctx, br_int, &existing_ports, local_datapaths,
+ chassis_id);
add_logical_patch_ports(ctx, br_int, chassis_id, &existing_ports,
patched_datapaths);
@@ -623,6 +623,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
"ovn-localnet-port");
const char *l2gateway = smap_get(&port_rec->external_ids,
"ovn-l2gateway-port");
+ const char *l3gateway = smap_get(&port_rec->external_ids,
+ "ovn-l3gateway-port");
const char *logpatch = smap_get(&port_rec->external_ids,
"ovn-logical-patch-port");
@@ -649,6 +651,10 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
/* L2 gateway patch ports can be handled just like VIFs. */
simap_put(&new_localvif_to_ofport, l2gateway, ofport);
break;
+ } else if (is_patch && l3gateway) {
+ /* L3 gateway patch ports can be handled just like VIFs. */
+ simap_put(&new_localvif_to_ofport, l3gateway, ofport);
+ break;
} else if (is_patch && logpatch) {
/* Logical patch ports can be handled just like VIFs. */
simap_put(&new_localvif_to_ofport, logpatch, ofport);
@@ -712,7 +712,8 @@ destroy_send_garps(void)
/* Add or update a vif for which GARPs need to be announced. */
static void
send_garp_update(const struct sbrec_port_binding *binding_rec,
- struct simap *localnet_ofports, struct hmap *local_datapaths)
+ struct simap *localnet_ofports, struct hmap *local_datapaths,
+ struct shash *nat_addresses)
{
/* Find the localnet ofport to send this GARP. */
struct local_datapath *ld
@@ -724,9 +725,44 @@ send_garp_update(const struct sbrec_port_binding *binding_rec,
ofp_port_t ofport = u16_to_ofp(simap_get(localnet_ofports,
ld->localnet_port->logical_port));
- /* Update GARP if it exists. */
- struct garp_data *garp = shash_find_data(&send_garp_data,
- binding_rec->logical_port);
+ volatile struct garp_data *garp = NULL;
+ /* Update GARP for NAT IP if it exists. */
+ if (!strcmp(binding_rec->type, "gateway")) {
+ struct lport_addresses *laddrs = NULL;
+ laddrs = shash_find_data(nat_addresses, binding_rec->logical_port);
+ if (!laddrs) {
+ return;
+ }
+ int i;
+ for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
+ char *name = xasprintf("%s-%s", binding_rec->logical_port,
+ laddrs->ipv4_addrs[i].addr_s);
+ garp = shash_find_data(&send_garp_data, name);
+ free(name);
+
+ if (garp) {
+ garp->ofport = ofport;
+ continue;
+ }
+ else {
+ struct garp_data *garp = xmalloc(sizeof *garp);
+ garp->ea = laddrs->ea;
+ garp->ipv4 = laddrs->ipv4_addrs[i].addr;
+ garp->announce_time = time_msec() + 1000;
+ garp->backoff = 1;
+ garp->ofport = ofport;
+ char *name = xasprintf("%s-%s", binding_rec->logical_port,
+ laddrs->ipv4_addrs[i].addr_s);
+ shash_add(&send_garp_data, name, garp);
+ free(name);
+ }
+ }
+ destroy_lport_addresses(laddrs);
+ return;
+ }
+
+ /* Update GARP for vif if it exists. */
+ garp = shash_find_data(&send_garp_data, binding_rec->logical_port);
if (garp) {
garp->ofport = ofport;
return;
@@ -806,14 +842,15 @@ send_garp(struct garp_data *garp, long long int current_time)
return garp->announce_time;
}
-/* Get localnet vifs, and ofport for localnet patch ports. */
+/* Get localnet vifs, local l3gw ports and ofport for localnet patch ports. */
static void
-get_localnet_vifs(const struct ovsrec_bridge *br_int,
+get_localnet_vifs_l3gwports(const struct ovsrec_bridge *br_int,
const char *this_chassis_id,
const struct lport_index *lports,
struct hmap *local_datapaths,
struct sset *localnet_vifs,
- struct simap *localnet_ofports)
+ struct simap *localnet_ofports,
+ struct sset *local_l3gw_ports)
{
for (int i = 0; i < br_int->n_ports; i++) {
const struct ovsrec_port *port_rec = br_int->ports[i];
@@ -827,6 +864,8 @@ get_localnet_vifs(const struct ovsrec_bridge *br_int,
}
const char *localnet = smap_get(&port_rec->external_ids,
"ovn-localnet-port");
+ const char *l3_gateway_port = smap_get(&port_rec->external_ids,
+ "ovn-l3gateway-port");
for (int j = 0; j < port_rec->n_interfaces; j++) {
const struct ovsrec_interface *iface_rec = port_rec->interfaces[j];
if (!iface_rec->n_ofport) {
@@ -840,6 +879,10 @@ get_localnet_vifs(const struct ovsrec_bridge *br_int,
simap_put(localnet_ofports, localnet, ofport);
continue;
}
+ if (l3_gateway_port) {
+ sset_add(local_l3gw_ports, l3_gateway_port);
+ continue;
+ }
const char *iface_id = smap_get(&iface_rec->external_ids,
"iface-id");
if (!iface_id) {
@@ -861,6 +904,41 @@ get_localnet_vifs(const struct ovsrec_bridge *br_int,
}
static void
+get_nat_addresses_and_keys(struct sset *nat_address_keys,
+ struct sset *local_l3gw_ports,
+ const struct lport_index *lports,
+ struct shash *nat_addresses)
+{
+ const char *gw_port;
+ SSET_FOR_EACH(gw_port, local_l3gw_ports) {
+ const struct sbrec_port_binding *pb = lport_lookup_by_name(lports,
+ gw_port);
+ if (!pb) {
+ continue;
+ }
+ const char *nat_addresses_options = smap_get(&pb->options,
+ "nat-addresses");
+ if (!nat_addresses_options) {
+ continue;
+ }
+
+ struct lport_addresses *laddrs = xmalloc(sizeof *laddrs);
+ if (!extract_lsp_addresses((char *)nat_addresses_options, laddrs)) {
+ free(laddrs);
+ continue;
+ }
+ int i;
+ for (i = 0; i < laddrs->n_ipv4_addrs; i++) {
+ char *name = xasprintf("%s-%s", pb->logical_port,
+ laddrs->ipv4_addrs[i].addr_s);
+ sset_add(nat_address_keys, name);
+ free(name);
+ }
+ shash_add(nat_addresses, pb->logical_port, laddrs);
+ }
+}
+
+static void
send_garp_wait(void)
{
poll_timer_wait_until(send_garp_time);
@@ -872,15 +950,23 @@ send_garp_run(const struct ovsrec_bridge *br_int, const char *chassis_id,
struct hmap *local_datapaths)
{
struct sset localnet_vifs = SSET_INITIALIZER(&localnet_vifs);
+ struct sset local_l3gw_ports = SSET_INITIALIZER(&local_l3gw_ports);
+ struct sset nat_ip_keys = SSET_INITIALIZER(&nat_ip_keys);
struct simap localnet_ofports = SIMAP_INITIALIZER(&localnet_ofports);
+ struct shash nat_addresses;
+
+ shash_init(&nat_addresses);
- get_localnet_vifs(br_int, chassis_id, lports, local_datapaths,
- &localnet_vifs, &localnet_ofports);
+ get_localnet_vifs_l3gwports(br_int, chassis_id, lports, local_datapaths,
+ &localnet_vifs, &localnet_ofports, &local_l3gw_ports);
- /* For deleted ports, remove from send_garp_data. */
+ get_nat_addresses_and_keys(&nat_ip_keys, &local_l3gw_ports, lports,
+ &nat_addresses);
+ /* For deleted ports and deleted nat ips, remove from send_garp_data. */
struct shash_node *iter, *next;
SHASH_FOR_EACH_SAFE (iter, next, &send_garp_data) {
- if (!sset_contains(&localnet_vifs, iter->name)) {
+ if (!sset_contains(&localnet_vifs, iter->name) &&
+ !sset_contains(&nat_ip_keys, iter->name)) {
send_garp_delete(iter->name);
}
}
@@ -891,7 +977,19 @@ send_garp_run(const struct ovsrec_bridge *br_int, const char *chassis_id,
const struct sbrec_port_binding *pb = lport_lookup_by_name(lports,
iface_id);
if (pb) {
- send_garp_update(pb, &localnet_ofports, local_datapaths);
+ send_garp_update(pb, &localnet_ofports, local_datapaths,
+ &nat_addresses);
+ }
+ }
+
+ /* Update send_garp_data for nat-addresses */
+ const char *gw_port;
+ SSET_FOR_EACH (gw_port, &local_l3gw_ports) {
+ const struct sbrec_port_binding *pb = lport_lookup_by_name(lports,
+ gw_port);
+ if (pb) {
+ send_garp_update(pb, &localnet_ofports, local_datapaths,
+ &nat_addresses);
}
}
@@ -905,7 +1003,9 @@ send_garp_run(const struct ovsrec_bridge *br_int, const char *chassis_id,
}
}
sset_destroy(&localnet_vifs);
+ sset_destroy(&local_l3gw_ports);
simap_destroy(&localnet_ofports);
+ shash_destroy_free_data(&nat_addresses);
}
static void
@@ -824,6 +824,12 @@ ovn_port_update_sbrec(const struct ovn_port *op)
if (chassis) {
smap_add(&new, "gateway-chassis", chassis);
}
+
+ const char *nat_ips = smap_get(&op->nbsp->options,
+ "nat-addresses");
+ if (nat_ips) {
+ smap_add(&new, "nat-addresses", nat_ips);
+ }
sbrec_port_binding_set_options(op->sb, &new);
smap_destroy(&new);
}
@@ -169,6 +169,16 @@
table="Logical_Router_Port"/> to which this logical switch port is
connected.
</column>
+
+ <column name="options" key="nat-addresses">
+ MAC address of the <code>router-port</code> followed by a list of
+ SNAT and DNAT IP adddresses. This is used to send gratutious arps
+ for SNAT and DNAT IP addresses via <code>localnet</code> and is
+ valid for only l3 gateway ports. An examples for value is
+ "80:fa:5b:06:72:b7 158.36.44.22 158.36.44.24". This would result
+ in generation of gratutious arps for IP addresses 158.36.44.22
+ and 158.36.44.24 with a mac address of 80:fa:5b:06:72:b7.
+ </column>
</group>
<group title="Options for localnet ports">
@@ -3818,3 +3818,52 @@ AT_CHECK([ovs-appctl -t ovn-controller version], [0], [ignore])
OVN_CLEANUP([hv1])
AT_CLEANUP
+
+AT_SETUP([ovn -- send gratutious arp for nat ips in localnet])
+AT_KEYWORDS([ovn])
+AT_SKIP_IF([test $HAVE_PYTHON = no])
+ovn_start
+# Create logical switch
+ovn-nbctl ls-add ls0
+# Create gateway router
+ovn-nbctl create Logical_Router name=lr0 options:chassis=hv1
+# Add router port to gateway router
+ovn-nbctl lrp-add lr0 lrp0 f0:00:00:00:00:01 192.168.0.1/24
+ovn-nbctl lsp-add ls0 lrp0-rp -- set Logical_Switch_Port lrp0-rp \
+ type=router options:router-port=lrp0-rp addresses='"f0:00:00:00:00:01"'
+# Add nat-address option
+ovn-nbctl lsp-set-options lrp0-rp router-port=lrp0 nat-addresses="f0:00:00:00:00:01 192.168.0.2"
+
+net_add n1
+sim_add hv1
+as hv1
+ovs-vsctl \
+ -- add-br br-phys \
+ -- add-br br-eth0
+
+ovn_attach n1 br-phys 192.168.0.1
+
+AT_CHECK([ovs-vsctl set Open_vSwitch . external-ids:ovn-bridge-mappings=physnet1:br-eth0])
+AT_CHECK([ovs-vsctl add-port br-eth0 snoopvif -- set Interface snoopvif options:tx_pcap=hv1/snoopvif-tx.pcap options:rxq_pcap=hv1/snoopvif-rx.pcap])
+
+# Create a localnet port.
+AT_CHECK([ovn-nbctl lsp-add ls0 ln_port])
+AT_CHECK([ovn-nbctl lsp-set-addresses ln_port unknown])
+AT_CHECK([ovn-nbctl lsp-set-type ln_port localnet])
+AT_CHECK([ovn-nbctl lsp-set-options ln_port network_name=physnet1])
+
+
+# Wait for packet to be received.
+OVS_WAIT_UNTIL([test `wc -c < "hv1/snoopvif-tx.pcap"` -ge 50])
+trim_zeros() {
+ sed 's/\(00\)\{1,\}$//'
+}
+$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/snoopvif-tx.pcap | trim_zeros > packets
+expected="fffffffffffff0000000000108060001080006040001f00000000001c0a80002000000000000c0a80002"
+echo $expected > expout
+AT_CHECK([sort packets], [0], [expout])
+cat packets
+
+OVN_CLEANUP([hv1])
+
+AT_CLEANUP
In cases where a DNAT IP is moved to a new router or the SNAT IP is reused with a new mac address, the NAT IPs become unreachable because the external switches/routers have stale ARP entries. This commit aims to fix the problem by sending GARPs for NAT IPs via locanet A new options key "nat-addresses" is added to the logical switch port of type router. The value for the key "nat-addresses" is the MAC address of the port followed by a list of SNAT & DNAT IPs. Signed-off-by: Chandra Sekhar Vejendla <csvejend@us.ibm.com> --- ovn/controller/binding.c | 6 ++ ovn/controller/ovn-controller.8.xml | 18 +++++- ovn/controller/patch.c | 8 ++- ovn/controller/physical.c | 6 ++ ovn/controller/pinctrl.c | 124 ++++++++++++++++++++++++++++++++---- ovn/northd/ovn-northd.c | 6 ++ ovn/ovn-nb.xml | 10 +++ tests/ovn.at | 49 ++++++++++++++ 8 files changed, 212 insertions(+), 15 deletions(-)