From: Guru Shetty <g...@ovn.org> static routes are useful when connecting multiple routers with each other.
Signed-off-by: steve.ruan <rua...@cn.ibm.com> Signed-off-by: Gurucharan Shetty <g...@ovn.org> Co-authored-by: Gurucharan Shetty <g...@ovn.org> Reported-by: Na Zhu <na...@cn.ibm.com> Reported-by: Dustin Lundquist <dlundqu...@linux.vnet.ibm.com> Reported-at: https://bugs.launchpad.net/networking-ovn/+bug/1545140 https://bugs.launchpad.net/networking-ovn/+bug/1539347 --- ovn/northd/ovn-northd.8.xml | 5 +- ovn/northd/ovn-northd.c | 101 +++++++++++++++++++++ ovn/ovn-nb.ovsschema | 15 +++- ovn/ovn-nb.xml | 31 +++++++ ovn/utilities/ovn-nbctl.8.xml | 5 ++ ovn/utilities/ovn-nbctl.c | 5 ++ tests/ovn.at | 204 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 362 insertions(+), 4 deletions(-) diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index da776e1..978853c 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -682,8 +682,9 @@ next; </p> <p> - If the route has a gateway, <var>G</var> is the gateway IP address, - otherwise it is <code>ip4.dst</code>. + If the route has a gateway, <var>G</var> is the gateway IP address. + Instead, if the route is from a configured static route, <var>G</var> + is the next hop IP address. Else it is <code>ip4.dst</code>. </p> </li> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 260c02f..5d86219 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -234,6 +234,18 @@ allocate_tnlid(struct hmap *set, const char *name, uint32_t max, return 0; } +/* Holds the next hop ip address and the logical router port via which + * a static route is reachable. */ +struct route_to_port { + ovs_be32 ip; /* network address of the route. */ + ovs_be32 mask; /* network mask of the route. */ + ovs_be32 next_hop; /* next_hop ip address for the above route. */ + struct uuid rport; /* output port specified by CMS, or null if not specified */ + struct ovn_port *ovn_port; /* The logical router port via which the packet + * needs to exit to reach the next hop. */ +}; + + /* The 'key' comes from nbs->header_.uuid or nbr->header_.uuid or * sb->external_ids:logical-switch. */ struct ovn_datapath { @@ -249,6 +261,9 @@ struct ovn_datapath { /* Logical router data (digested from nbr). */ const struct ovn_port *gateway_port; ovs_be32 gateway; + /* Maps a static route to a ovn logical router port via which packet + * needs to exit. */ + struct shash routes_map; /* Logical switch data. */ struct ovn_port **router_ports; @@ -272,6 +287,7 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, od->nbs = nbs; od->nbr = nbr; hmap_init(&od->port_tnlids); + shash_init(&od->routes_map); od->port_key_hint = 0; hmap_insert(datapaths, &od->key_node, uuid_hash(&od->key)); return od; @@ -286,6 +302,7 @@ ovn_datapath_destroy(struct hmap *datapaths, struct ovn_datapath *od) * use it. */ hmap_remove(datapaths, &od->key_node); destroy_tnlids(&od->port_tnlids); + shash_destroy_free_data(&od->routes_map); free(od->router_ports); free(od); } @@ -318,6 +335,50 @@ ovn_datapath_from_sbrec(struct hmap *datapaths, } static void +build_static_route(struct ovn_datapath *od, + const struct nbrec_logical_router_static_route *route) +{ + ovs_be32 prefix, next_hop, mask; + + /* verify nexthop */ + char *error = ip_parse_masked(route->nexthop, &next_hop, &mask); + if (error || mask != OVS_BE32_MAX) { + static struct vlog_rate_limit rl + = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad next hop ip address %s", + route->nexthop); + free(error); + return; + } + + /* verify prefix */ + error = ip_parse_masked(route->prefix, &prefix, &mask); + if (error || !ip_is_cidr(mask)) { + static struct vlog_rate_limit rl + = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "bad 'network' in static routes %s", + route->prefix); + free(error); + return; + } + + struct uuid lrp_uuid; + struct route_to_port *route_port = xmalloc(sizeof *route_port); + route_port->ip = prefix; + route_port->mask = mask; + route_port->next_hop = next_hop; + /* The correct ovn_port will be filled while traversing + * logical_router_ports. */ + route_port->ovn_port = NULL; + memset(&route_port->rport, 0, sizeof (struct uuid)); + if (route->output_port + && uuid_from_string(&lrp_uuid, route->output_port)){ + memcpy(&route_port->rport, &lrp_uuid, sizeof (struct uuid)); + } + shash_add(&od->routes_map, route->prefix, route_port); +} + +static void join_datapaths(struct northd_context *ctx, struct hmap *datapaths, struct ovs_list *sb_only, struct ovs_list *nb_only, struct ovs_list *both) @@ -409,6 +470,14 @@ join_datapaths(struct northd_context *ctx, struct hmap *datapaths, /* Set the gateway port to NULL. If there is a gateway, it will get * filled in as we go through the ports later. */ od->gateway_port = NULL; + + /* Handle static routes set in this logical router. */ + for (int i = 0; i < nbr->n_static_routes; i++) { + const struct nbrec_logical_router_static_route *route; + + route = od->nbr->static_routes[i]; + build_static_route(od, route); + } } } @@ -644,6 +713,29 @@ join_logical_ports(struct northd_context *ctx, od->gateway_port = op; } } + + /* Go through the static routes of the router to see which + * route is reachable via this logical router port. */ + struct shash_node *node; + SHASH_FOR_EACH(node, &od->routes_map) { + struct route_to_port *route_port = node->data; + + if (uuid_equals(&route_port->rport, &nbr->header_.uuid)){ + route_port->ovn_port = op; + } + else{ + /* If 'od' has a static route and 'op' routes to it... */ + if (!((op->network ^ route_port->next_hop) & op->mask)) { + /* .....and if 'op' is a longer match than the current + * choice... */ + const struct ovn_port *curr = route_port->ovn_port; + int len = curr ? ip_count_cidr_bits(curr->mask) : 0; + if (ip_count_cidr_bits(op->mask) > len) { + route_port->ovn_port = op; + } + } + } + } } } } @@ -1986,6 +2078,15 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, continue; } + struct shash_node *node; + SHASH_FOR_EACH(node, &od->routes_map) { + struct route_to_port *route_port = node->data; + if (route_port->ovn_port) { + add_route(lflows, route_port->ovn_port, route_port->ip, + route_port->mask, route_port->next_hop); + } + } + if (od->gateway && od->gateway_port) { add_route(lflows, od->gateway_port, 0, 0, od->gateway); } diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema index 40a7a97..ce2e72d 100644 --- a/ovn/ovn-nb.ovsschema +++ b/ovn/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "2.0.2", - "cksum": "4289495412 4436", + "version": "2.0.3", + "cksum": "3119329675 4999", "tables": { "Logical_Switch": { "columns": { @@ -72,6 +72,11 @@ "min": 0, "max": "unlimited"}}, "default_gw": {"type": {"key": "string", "min": 0, "max": 1}}, + "static_routes": {"type": {"key": {"type": "uuid", + "refTable": "Logical_Router_Static_Route", + "refType": "strong"}, + "min": 0, + "max": "unlimited"}}, "external_ids": { "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, @@ -87,6 +92,12 @@ "type": {"key": "string", "value": "string", "min": 0, "max": "unlimited"}}}, "indexes": [["name"]], + "isRoot": false}, + "Logical_Router_Static_Route": { + "columns": { + "prefix": {"type": "string"}, + "nexthop": {"type": "string"}, + "output_port": {"type": "string"}}, "isRoot": false} } } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index e65bc3a..9e2c742 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -627,6 +627,10 @@ IP address to use as default gateway, if any. </column> + <column name="static_routes"> + One or more static routes, mapping IP prefixes to next hop IP addresses. + </column> + <group title="Common Columns"> <column name="external_ids"> See <em>External IDs</em> at the beginning of this document. @@ -717,4 +721,31 @@ </column> </group> </table> + + <table name="Logical_Router_Static_Route" title="logical router static routes"> + <p> + Each route represents a static route. + </p> + + <column name="prefix"> + <p> + Prefix of this route, example 192.168.100.0/24 + </p> + </column> + + <column name="nexthop"> + <p> + Nexthop of this route, nexthop can be a IP address of neutron port, or + IP address which has been learnt by dynamic ARP + </p> + </column> + + <column name="output_port"> + <p> + Port ID of the logical router port table. It may be configured or may + not be configured + </p> + </column> + </table> + </database> diff --git a/ovn/utilities/ovn-nbctl.8.xml b/ovn/utilities/ovn-nbctl.8.xml index 3b337dd..f6ef803 100644 --- a/ovn/utilities/ovn-nbctl.8.xml +++ b/ovn/utilities/ovn-nbctl.8.xml @@ -231,6 +231,11 @@ A port within an L3 logical router. Records may be identified by name. </dd> + <dt><code>Logical_Router_Static_Route</code></dt> + <dd> + A static route belonging to an L3 logical router. + </dd> + </dl> <xi:include href="lib/db-ctl-base.xml" xmlns:xi="http://www.w3.org/2003/XInclude"/> diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index bdad368..bb3ce3e 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -1101,6 +1101,11 @@ static const struct ctl_table_class tables[] = { NULL}, {NULL, NULL, NULL}}}, + {&nbrec_table_logical_router_static_route, + {{&nbrec_table_logical_router_static_route, NULL, + NULL}, + {NULL, NULL, NULL}}}, + {NULL, {{NULL, NULL, NULL}, {NULL, NULL, NULL}}} }; diff --git a/tests/ovn.at b/tests/ovn.at index 6fea4e0..af3e1c4 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -2192,3 +2192,207 @@ OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) OVS_APP_EXIT_AND_WAIT([ovsdb-server]) AT_CLEANUP + +AT_SETUP([ovn -- 2 HVs, 3 LS, 1 lport/LS, 2 peer LRs, static routes]) +AT_KEYWORDS([ovnstaticroutespeer]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# Logical network: +# Two LRs - R1 and R2 that are connected to each other as peers in 20.0.0.0/24 +# network. R1 has switchess foo (192.168.1.0/24) +# connected to it. +# R2 has alice (172.16.1.0/24) and bob (172.16.2.0/24) connected to it. + +ovn-nbctl create Logical_Router name=R1 +ovn-nbctl create Logical_Router name=R2 + +ovn-nbctl lswitch-add foo +ovn-nbctl lswitch-add alice +ovn-nbctl lswitch-add bob + +# Connect foo to R1 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=foo \ +network=192.168.1.1/24 mac=\"00:00:00:01:02:03\" -- add Logical_Router R1 \ +ports @lrp -- lport-add foo rp-foo + +ovn-nbctl set Logical_port rp-foo type=router options:router-port=foo \ +addresses=\"00:00:00:01:02:03\" + +# Connect alice to R2 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=alice \ +network=172.16.1.1/24 mac=\"00:00:00:01:02:04\" -- add Logical_Router R2 \ +ports @lrp -- lport-add alice rp-alice + +ovn-nbctl set Logical_port rp-alice type=router options:router-port=alice \ +addresses=\"00:00:00:01:02:04\" + +# Connect bob to R2 +ovn-nbctl -- --id=@lrp create Logical_Router_port name=bob \ +network=172.16.2.1/24 mac=\"00:00:00:01:02:05\" -- add Logical_Router R2 \ +ports @lrp -- lport-add bob rp-bob + +ovn-nbctl set Logical_port rp-bob type=router options:router-port=bob \ +addresses=\"00:00:00:01:02:05\" + +# Connect R1 to R2 +lrp1_uuid=`ovn-nbctl -- --id=@lrp create Logical_Router_port name=R1_R2 \ +network="20.0.0.1/24" mac=\"00:00:00:02:03:04\" \ +-- add Logical_Router R1 ports @lrp` + +lrp2_uuid=`ovn-nbctl -- --id=@lrp create Logical_Router_port name=R2_R1 \ +network="20.0.0.2/24" mac=\"00:00:00:02:03:05\" \ +-- add Logical_Router R2 ports @lrp` + +ovn-nbctl set logical_router_port $lrp1_uuid peer="R2_R1" +ovn-nbctl set logical_router_port $lrp2_uuid peer="R1_R2" + +#install static routes +ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ +prefix=172.16.1.0/24 nexthop=20.0.0.2 -- add Logical_Router \ +R1 static_routes @lrt + +ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ +prefix=172.16.2.0/24 nexthop=20.0.0.2 -- add Logical_Router \ +R1 static_routes @lrt + +ovn-nbctl -- --id=@lrt create Logical_Router_Static_Route \ +prefix=192.168.1.0/24 nexthop=20.0.0.1 -- add Logical_Router \ +R2 static_routes @lrt + +# Create logical port foo1 in foo +ovn-nbctl lport-add foo foo1 \ +-- lport-set-addresses foo1 "f0:00:00:01:02:03 192.168.1.2" + +# Create logical port alice1 in alice +ovn-nbctl lport-add alice alice1 \ +-- lport-set-addresses alice1 "f0:00:00:01:02:04 172.16.1.2" + +# Create logical port bob1 in bob +ovn-nbctl lport-add bob bob1 \ +-- lport-set-addresses bob1 "f0:00:00:01:02:05 172.16.2.2" + +# Create two hypervisor and create OVS ports corresponding to logical ports. +net_add n1 + +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 +ovs-vsctl -- add-port br-int hv1-vif1 -- \ + set interface hv1-vif1 external-ids:iface-id=foo1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int hv1-vif2 -- \ + set interface hv1-vif2 external-ids:iface-id=alice1 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +sim_add hv2 +as hv2 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.2 +ovs-vsctl -- add-port br-int hv2-vif1 -- \ + set interface hv2-vif1 external-ids:iface-id=bob1 \ + options:tx_pcap=hv2/vif1-tx.pcap \ + options:rxq_pcap=hv2/vif1-rx.pcap \ + ofport-request=1 + + +# Pre-populate the hypervisors' ARP tables so that we don't lose any +# packets for ARP resolution (native tunneling doesn't queue packets +# for ARP resolution). +ovn_populate_arp + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# Send ip packets between foo1 and alice1 +src_mac="f00000010203" +dst_mac="000000010203" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 1 2` +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + +# Send ip packets between foo1 and bob1 +src_mac="f00000010203" +dst_mac="000000010203" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 2 2` +packet=${dst_mac}${src_mac}08004500001c0000000040110000${src_ip}${dst_ip}0035111100080000 +as hv1 ovs-appctl netdev-dummy/receive hv1-vif1 $packet + +echo "---------NB dump-----" +ovn-nbctl show +echo "---------------------" +ovn-nbctl list logical_router +echo "---------------------" +ovn-nbctl list logical_router_port +echo "---------------------" + +echo "---------SB dump-----" +ovn-sbctl list datapath_binding +echo "---------------------" +ovn-sbctl list port_binding +echo "---------------------" + +echo "------ hv1 dump ----------" +as hv1 ovs-ofctl dump-flows br-int +echo "------ hv2 dump ----------" +as hv2 ovs-ofctl dump-flows br-int + +# Packet to Expect at bob1 +src_mac="000000010205" +dst_mac="f00000010205" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 2 2` +expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv2/vif1-tx.pcap | trim_zeros > received.packets +echo $expected | trim_zeros > expout +AT_CHECK([cat received.packets], [0], [expout]) + +# Packet to Expect at alice1 +src_mac="000000010204" +dst_mac="f00000010204" +src_ip=`ip_to_hex 192 168 1 2` +dst_ip=`ip_to_hex 172 16 1 2` +expected=${dst_mac}${src_mac}08004500001c000000003e110200${src_ip}${dst_ip}0035111100080000 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > received1.packets +echo $expected | trim_zeros > expout +AT_CHECK([cat received1.packets], [0], [expout]) + +for sim in hv1 hv2; do + as $sim + OVS_APP_EXIT_AND_WAIT([ovn-controller]) + OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) + OVS_APP_EXIT_AND_WAIT([ovsdb-server]) +done + +as ovn-sb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as ovn-nb +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +as northd +OVS_APP_EXIT_AND_WAIT([ovn-northd]) + +as main +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +AT_CLEANUP -- 2.5.0 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev