Permalink
Browse files

ovn: Add support for new logical port type "localport".

This patch introduces a new type of OVN ports called "localport".
These ports will be present in every hypervisor and may have the
same IP/MAC addresses. They are not bound to any chassis and traffic
to these ports will never go through a tunnel.

Its main use case is the OpenStack metadata API support which relies
on a local agent running on every hypervisor and serving metadata to
VM's locally. This service is described in detail at [0].

An example to illustrate the purpose of this patch:

- One logical switch sw0 with 2 ports (p1, p2) and 1 localport (lp)
- Two hypervisors: HV1 and HV2
- p1 in HV1 (OVS port with external-id:iface-id="p1")
- p2 in HV2 (OVS port with external-id:iface-id="p2")
- lp in both hypevisors (OVS port with external-id:iface-id="lp")
- p1 should be able to reach p2 and viceversa
- lp on HV1 should be able to reach p1 but not p2
- lp on HV2 should be able to reach p2 but not p1

Explicit drop rules are inserted in table 32 with priority 150
in order to prevent traffic originated at a localport to go over
a tunnel.

[0]
https://docs.openstack.org/developer/networking-ovn/design/metadata_api.html

Signed-off-by: Daniel Alvarez <dalvarez@redhat.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
  • Loading branch information...
danalsan authored and blp committed May 26, 2017
1 parent a129fe8 commit 2a38ef4520f646df2ad6e879aa7825e1cec48bac
View
@@ -380,7 +380,10 @@ consider_local_datapath(struct controller_ctx *ctx,
if (iface_rec && qos_map && ctx->ovs_idl_txn) {
get_qos_params(binding_rec, qos_map);
}
our_chassis = true;
/* This port is in our chassis unless it is a localport. */
if (strcmp(binding_rec->type, "localport")) {
our_chassis = true;
}
} else if (!strcmp(binding_rec->type, "l2gateway")) {
const char *chassis_id = smap_get(&binding_rec->options,
"l2gateway-chassis");
@@ -656,7 +656,7 @@ main(int argc, char *argv[])
physical_run(&ctx, mff_ovn_geneve,
br_int, chassis, &ct_zones, &lports,
&flow_table, &local_datapaths);
&flow_table, &local_datapaths, &local_lports);
ofctrl_put(&flow_table, &pending_ct_zones,
get_nb_cfg(ctx.ovnsb_idl));
View
@@ -769,7 +769,8 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
const struct ovsrec_bridge *br_int,
const struct sbrec_chassis *chassis,
const struct simap *ct_zones, struct lport_index *lports,
struct hmap *flow_table, struct hmap *local_datapaths)
struct hmap *flow_table, struct hmap *local_datapaths,
const struct sset *local_lports)
{
/* This bool tracks physical mapping changes. */
@@ -988,15 +989,40 @@ physical_run(struct controller_ctx *ctx, enum mf_field_id mff_ovn_geneve,
*/
struct match match;
match_init_catchall(&match);
ofpbuf_clear(&ofpacts);
match_set_reg_masked(&match, MFF_LOG_FLAGS - MFF_REG0,
MLF_RCV_FROM_VXLAN, MLF_RCV_FROM_VXLAN);
/* Resubmit to table 33. */
ofpbuf_clear(&ofpacts);
put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
&match, &ofpacts);
/* Table 32, priority 150.
* =======================
*
* Handles packets received from ports of type "localport". These ports
* are present on every hypervisor. Traffic that originates at one should
* never go over a tunnel to a remote hypervisor, so resubmit them to table
* 33 for local delivery. */
match_init_catchall(&match);
ofpbuf_clear(&ofpacts);
put_resubmit(OFTABLE_LOCAL_OUTPUT, &ofpacts);
const char *localport;
SSET_FOR_EACH (localport, local_lports) {
/* Iterate over all local logical ports and insert a drop
* rule with higher priority for every localport in this
* datapath. */
const struct sbrec_port_binding *pb = lport_lookup_by_name(
lports, localport);
if (pb && !strcmp(pb->type, "localport")) {
match_set_reg(&match, MFF_LOG_INPORT - MFF_REG0, pb->tunnel_key);
match_set_metadata(&match, htonll(pb->datapath->tunnel_key));
ofctrl_add_flow(flow_table, OFTABLE_REMOTE_OUTPUT, 150, 0,
&match, &ofpacts);
}
}
/* Table 32, Priority 0.
* =======================
*
@@ -32,6 +32,7 @@ struct hmap;
struct ovsdb_idl;
struct ovsrec_bridge;
struct simap;
struct sset;
/* OVN Geneve option information.
*
@@ -45,6 +46,7 @@ void physical_run(struct controller_ctx *, enum mf_field_id mff_ovn_geneve,
const struct ovsrec_bridge *br_int,
const struct sbrec_chassis *chassis,
const struct simap *ct_zones, struct lport_index *,
struct hmap *flow_table, struct hmap *local_datapaths);
struct hmap *flow_table, struct hmap *local_datapaths,
const struct sset *local_lports);
#endif /* ovn/physical.h */
@@ -492,8 +492,8 @@ output;
</pre>
<p>
These flows are omitted for logical ports (other than router ports)
that are down.
These flows are omitted for logical ports (other than router ports or
<code>localport</code> ports) that are down.
</p>
</li>
@@ -519,8 +519,8 @@ nd_na {
</pre>
<p>
These flows are omitted for logical ports (other than router ports)
that are down.
These flows are omitted for logical ports (other than router ports or
<code>localport</code> ports) that are down.
</p>
</li>
View
@@ -3305,9 +3305,11 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
/*
* Add ARP/ND reply flows if either the
* - port is up or
* - port type is router
* - port type is router or
* - port type is localport
*/
if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router")) {
if (!lsp_is_up(op->nbsp) && strcmp(op->nbsp->type, "router") &&
strcmp(op->nbsp->type, "localport")) {
continue;
}
View
@@ -409,6 +409,21 @@
logical patch ports at each such point of connectivity, one on
each side.
</li>
<li>
<dfn>Localport ports</dfn> represent the points of local
connectivity between logical switches and VIFs. These ports are
present in every chassis (not bound to any particular one) and
traffic from them will never go through a tunnel. A
<code>localport</code> is expected to only generate traffic destined
for a local destination, typically in response to a request it
received.
One use case is how OpenStack Neutron uses a <code>localport</code>
port for serving metadata to VM's residing on every hypervisor. A
metadata proxy process is attached to this port on every host and all
VM's within the same network will reach it at the same IP/MAC address
without any traffic being sent over a tunnel. Further details can be
seen at https://docs.openstack.org/developer/networking-ovn/design/metadata_api.html.
</li>
</ul>
</li>
</ul>
@@ -993,15 +1008,31 @@
hypervisor, in the same way as for unicast destinations. If a
multicast group includes a logical port or ports on the local
hypervisor, then its actions also resubmit to table 33. Table 32 also
includes a fallback flow that resubmits to table 33 if there is no
other match. Table 32 also contains a higher priority rule to match
packets received from VXLAN tunnels, based on flag MLF_RCV_FROM_VXLAN
and resubmit these packets to table 33 for local delivery. Packets
received from VXLAN tunnels reach here because of a lack of logical
output port field in the tunnel key and thus these packets needed to
be submitted to table 16 to determine the output port.
includes:
</p>
<ul>
<li>
A higher-priority rule to match packets received from VXLAN tunnels,
based on flag MLF_RCV_FROM_VXLAN, and resubmit these packets to table
33 for local delivery. Packets received from VXLAN tunnels reach
here because of a lack of logical output port field in the tunnel key
and thus these packets needed to be submitted to table 16 to
determine the output port.
</li>
<li>
A higher-priority rule to match packets received from ports of type
<code>localport</code>, based on the logical input port, and resubmit
these packets to table 33 for local delivery. Ports of type
<code>localport</code> exist on every hypervisor and by definition
their traffic should never go out through a tunnel.
</li>
<li>
A fallback flow that resubmits to table 33 if there is no other
match.
</li>
</ul>
<p>
Flows in table 33 resemble those in table 32 but for logical ports that
reside locally rather than remotely. For unicast logical output ports
View
@@ -283,6 +283,15 @@
to model direct connectivity to an existing network.
</dd>
<dt><code>localport</code></dt>
<dd>
A connection to a local VIF. Traffic that arrives on a
<code>localport</code> is never forwarded over a tunnel to another
chassis. These ports are present on every chassis and have the same
address in all of them. This is used to model connectivity to local
services that run on every hypervisor.
</dd>
<dt><code>l2gateway</code></dt>
<dd>
A connection to a physical network.
View
@@ -1802,6 +1802,11 @@ tcp.flags = RST;
connectivity to the corresponding physical network.
</dd>
<dt>localport</dt>
<dd>
Always empty. A localport port is present on every chassis.
</dd>
<dt>l3gateway</dt>
<dd>
The physical location of the L3 gateway. To successfully identify a
@@ -1882,6 +1887,15 @@ tcp.flags = RST;
to model direct connectivity to an existing network.
</dd>
<dt><code>localport</code></dt>
<dd>
A connection to a local VIF. Traffic that arrives on a
<code>localport</code> is never forwarded over a tunnel to another
chassis. These ports are present on every chassis and have the same
address in all of them. This is used to model connectivity to local
services that run on every hypervisor.
</dd>
<dt><code>l2gateway</code></dt>
<dd>
An L2 connection to a physical network. The chassis this
View
@@ -7374,3 +7374,125 @@ OVN_CHECK_PACKETS([hv2/vif1-tx.pcap], [expected])
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP
AT_SETUP([ovn -- 2 HVs, 1 lport/HV, localport ports])
AT_SKIP_IF([test $HAVE_PYTHON = no])
ovn_start
ovn-nbctl ls-add ls1
# Add localport to the switch
ovn-nbctl lsp-add ls1 lp01
ovn-nbctl lsp-set-addresses lp01 f0:00:00:00:00:01
ovn-nbctl lsp-set-type lp01 localport
net_add n1
for i in 1 2; do
sim_add hv$i
as hv$i
ovs-vsctl add-br br-phys
ovn_attach n1 br-phys 192.168.0.$i
ovs-vsctl add-port br-int vif01 -- \
set Interface vif01 external-ids:iface-id=lp01 \
options:tx_pcap=hv${i}/vif01-tx.pcap \
options:rxq_pcap=hv${i}/vif01-rx.pcap \
ofport-request=${i}0
ovs-vsctl add-port br-int vif${i}1 -- \
set Interface vif${i}1 external-ids:iface-id=lp${i}1 \
options:tx_pcap=hv${i}/vif${i}1-tx.pcap \
options:rxq_pcap=hv${i}/vif${i}1-rx.pcap \
ofport-request=${i}1
ovn-nbctl lsp-add ls1 lp${i}1
ovn-nbctl lsp-set-addresses lp${i}1 f0:00:00:00:00:${i}1
ovn-nbctl lsp-set-port-security lp${i}1 f0:00:00:00:00:${i}1
OVS_WAIT_UNTIL([test x`ovn-nbctl lsp-get-up lp${i}1` = xup])
done
ovn-nbctl --wait=sb sync
ovn-sbctl dump-flows
ovn_populate_arp
# Given the name of a logical port, prints the name of the hypervisor
# on which it is located.
vif_to_hv() {
echo hv${1%?}
}
#
# test_packet INPORT DST SRC ETHTYPE EOUT LOUT DEFHV
#
# This shell function causes a packet to be received on INPORT. The packet's
# content has Ethernet destination DST and source SRC (each exactly 12 hex
# digits) and Ethernet type ETHTYPE (4 hex digits). INPORT is specified as
# logical switch port numbers, e.g. 11 for vif11.
#
# EOUT is the end-to-end output port, that is, where the packet will end up
# after possibly bouncing through one or more localnet ports. LOUT is the
# logical output port, which might be a localnet port, as seen by ovn-trace
# (which doesn't know what localnet ports are connected to and therefore can't
# figure out the end-to-end answer).
#
# DEFHV is the default hypervisor from where the packet is going to be sent
# if the source port is a localport.
for i in 1 2; do
for j in 0 1; do
: > $i$j.expected
done
done
test_packet() {
local inport=$1 dst=$2 src=$3 eth=$4 eout=$5 lout=$6 defhv=$7
echo "$@"
# First try tracing the packet.
uflow="inport==\"lp$inport\" && eth.dst==$dst && eth.src==$src && eth.type==0x$eth"
if test $lout != drop; then
echo "output(\"$lout\");"
fi > expout
AT_CAPTURE_FILE([trace])
AT_CHECK([ovn-trace --all ls1 "$uflow" | tee trace | sed '1,/Minimal trace/d'], [0], [expout])
# Then actually send a packet, for an end-to-end test.
local packet=$(echo $dst$src | sed 's/://g')${eth}
hv=`vif_to_hv $inport`
# If hypervisor 0 (localport) use the defhv parameter
if test $hv == hv0; then
hv=$defhv
fi
vif=vif$inport
as $hv ovs-appctl netdev-dummy/receive $vif $packet
if test $eout != drop; then
echo $packet >> ${eout#lp}.expected
fi
}
# lp11 and lp21 are on different hypervisors
test_packet 11 f0:00:00:00:00:21 f0:00:00:00:00:11 1121 lp21 lp21
test_packet 21 f0:00:00:00:00:11 f0:00:00:00:00:21 2111 lp11 lp11
# Both VIFs should be able to reach the localport on their own HV
test_packet 11 f0:00:00:00:00:01 f0:00:00:00:00:11 1101 lp01 lp01
test_packet 21 f0:00:00:00:00:01 f0:00:00:00:00:21 2101 lp01 lp01
# Packet sent from localport on same hv should reach the vif
test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 lp11 lp11 hv1
test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 lp21 lp21 hv2
# Packet sent from localport on different hv should be dropped
test_packet 01 f0:00:00:00:00:21 f0:00:00:00:00:01 0121 drop lp21 hv1
test_packet 01 f0:00:00:00:00:11 f0:00:00:00:00:01 0111 drop lp11 hv2
# Now check the packets actually received against the ones expected.
for i in 1 2; do
for j in 0 1; do
OVN_CHECK_PACKETS([hv$i/vif$i$j-tx.pcap], [$i$j.expected])
done
done
OVN_CLEANUP([hv1],[hv2])
AT_CLEANUP

0 comments on commit 2a38ef4

Please sign in to comment.