This patch extends Logical Router's routing functionality. Now user may create multiple routing tables within a Logical Router and assign them to Logical Router Ports.
Traffic coming from Logical Router Port with assigned route_table is checked against Logical_Router_Static_Routes with same route_table field value and routes to connected networks. If no route_table option is set to the LRP, routes' lookup is done agains routes with no route_table field value ("", empty string) and against routes to connected networks. A new Logical Router ingress table #10 is added - IN_IP_ROUTING_PRE. In this table packets which come from LRPs with configured options:route_table field are checked against inport and in OVS register 7 unique non-zero value identifying route table is written. If no route_table is configured to LRP, 0 is written to register 7. Then in 11th table IN_IP_ROUTING flows for routes which have non-empty `route_table` field are prefixed in match portion with "reg7 == <id>" where id is an uniqly-generated route_table id (if any) or zero if route has empty route_table value. Signed-off-by: Vladislav Odintsov <odiv...@gmail.com> Acked-by: Numan Siddique <num...@ovn.org> --- northd/northd.c | 154 +++++++++--- northd/ovn-northd.8.xml | 82 ++++-- ovn-nb.ovsschema | 5 +- ovn-nb.xml | 30 +++ tests/ovn-ic.at | 4 + tests/ovn-nbctl.at | 196 ++++++++++++++- tests/ovn-northd.at | 74 +++++- tests/ovn.at | 546 +++++++++++++++++++++++++++++++++++++++- utilities/ovn-nbctl.c | 134 +++++++++- 9 files changed, 1154 insertions(+), 71 deletions(-) diff --git a/northd/northd.c b/northd/northd.c index 0d513f039..bf233766e 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -148,15 +148,16 @@ enum ovn_stage { PIPELINE_STAGE(ROUTER, IN, ECMP_STATEFUL, 7, "lr_in_ecmp_stateful") \ PIPELINE_STAGE(ROUTER, IN, ND_RA_OPTIONS, 8, "lr_in_nd_ra_options") \ PIPELINE_STAGE(ROUTER, IN, ND_RA_RESPONSE, 9, "lr_in_nd_ra_response") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 10, "lr_in_ip_routing") \ - PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 11, "lr_in_ip_routing_ecmp") \ - PIPELINE_STAGE(ROUTER, IN, POLICY, 12, "lr_in_policy") \ - PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 13, "lr_in_policy_ecmp") \ - PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 14, "lr_in_arp_resolve") \ - PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN , 15, "lr_in_chk_pkt_len") \ - PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 16, "lr_in_larger_pkts") \ - PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 17, "lr_in_gw_redirect") \ - PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 18, "lr_in_arp_request") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_PRE, 10, "lr_in_ip_routing_pre") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING, 11, "lr_in_ip_routing") \ + PIPELINE_STAGE(ROUTER, IN, IP_ROUTING_ECMP, 12, "lr_in_ip_routing_ecmp") \ + PIPELINE_STAGE(ROUTER, IN, POLICY, 13, "lr_in_policy") \ + PIPELINE_STAGE(ROUTER, IN, POLICY_ECMP, 14, "lr_in_policy_ecmp") \ + PIPELINE_STAGE(ROUTER, IN, ARP_RESOLVE, 15, "lr_in_arp_resolve") \ + PIPELINE_STAGE(ROUTER, IN, CHK_PKT_LEN, 16, "lr_in_chk_pkt_len") \ + PIPELINE_STAGE(ROUTER, IN, LARGER_PKTS, 17, "lr_in_larger_pkts") \ + PIPELINE_STAGE(ROUTER, IN, GW_REDIRECT, 18, "lr_in_gw_redirect") \ + PIPELINE_STAGE(ROUTER, IN, ARP_REQUEST, 19, "lr_in_arp_request") \ \ /* Logical router egress stages. */ \ PIPELINE_STAGE(ROUTER, OUT, UNDNAT, 0, "lr_out_undnat") \ @@ -225,6 +226,7 @@ enum ovn_stage { #define REG_NEXT_HOP_IPV6 "xxreg0" #define REG_SRC_IPV4 "reg1" #define REG_SRC_IPV6 "xxreg1" +#define REG_ROUTE_TABLE_ID "reg7" #define REG_ORIG_TP_DPORT_ROUTER "reg9[16..31]" @@ -287,8 +289,9 @@ enum ovn_stage { * | R6 | UNUSED | X | | G | IN_IP_ROUTING)| * | | | R | | 1 | | * +-----+--------------------------+ E | UNUSED | | | - * | R7 | UNUSED | G | | | | - * | | | 3 | | | | + * | R7 | ROUTE_TABLE_ID | G | | | | + * | | (>= IN_IP_ROUTING_PRE && | 3 | | | | + * | | <= IN_IP_ROUTING) | | | | | * +-----+--------------------------+---+-----------------+---+---------------+ * | R8 | ECMP_GROUP_ID | | | * | | ECMP_MEMBER_ID | X | | @@ -8647,11 +8650,72 @@ cleanup: ds_destroy(&actions); } +static uint32_t +route_table_add(struct simap *route_tables, const char *route_table_name) +{ + /* route table ids start from 1 */ + uint32_t rtb_id = simap_count(route_tables) + 1; + + if (rtb_id == UINT16_MAX) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "too many route tables for Logical Router."); + return 0; + } + + if (!simap_put(route_tables, route_table_name, rtb_id)) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + VLOG_WARN_RL(&rl, "Route table id unexpectedly appeared"); + } + + return rtb_id; +} + +static uint32_t +get_route_table_id(struct simap *route_tables, const char *route_table_name) +{ + if (!route_table_name || !strlen(route_table_name)) { + return 0; + } + + uint32_t rtb_id = simap_get(route_tables, route_table_name); + if (!rtb_id) { + rtb_id = route_table_add(route_tables, route_table_name); + } + + return rtb_id; +} + +static void +build_route_table_lflow(struct ovn_datapath *od, struct hmap *lflows, + struct nbrec_logical_router_port *lrp, + struct simap *route_tables) +{ + struct ds match = DS_EMPTY_INITIALIZER; + struct ds actions = DS_EMPTY_INITIALIZER; + + const char *route_table_name = smap_get(&lrp->options, "route_table"); + uint32_t rtb_id = get_route_table_id(route_tables, route_table_name); + if (!rtb_id) { + return; + } + + ds_put_format(&match, "inport == \"%s\"", lrp->name); + ds_put_format(&actions, "%s = %d; next;", + REG_ROUTE_TABLE_ID, rtb_id); + + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_PRE, 100, + ds_cstr(&match), ds_cstr(&actions)); + + ds_destroy(&match); + ds_destroy(&actions); +} + struct parsed_route { struct ovs_list list_node; struct in6_addr prefix; unsigned int plen; bool is_src_route; + uint32_t route_table_id; uint32_t hash; const struct nbrec_logical_router_static_route *route; bool ecmp_symmetric_reply; @@ -8676,7 +8740,7 @@ find_static_route_outport(struct ovn_datapath *od, struct hmap *ports, * Otherwise return NULL. */ static struct parsed_route * parsed_routes_add(struct ovn_datapath *od, struct hmap *ports, - struct ovs_list *routes, + struct ovs_list *routes, struct simap *route_tables, const struct nbrec_logical_router_static_route *route, struct hmap *bfd_connections) { @@ -8758,6 +8822,7 @@ parsed_routes_add(struct ovn_datapath *od, struct hmap *ports, struct parsed_route *pr = xzalloc(sizeof *pr); pr->prefix = prefix; pr->plen = plen; + pr->route_table_id = get_route_table_id(route_tables, route->route_table); pr->is_src_route = (route->policy && !strcmp(route->policy, "src-ip")); pr->hash = route_hash(pr); @@ -8792,6 +8857,7 @@ struct ecmp_groups_node { unsigned int plen; bool is_src_route; const char *origin; + uint32_t route_table_id; uint16_t route_count; struct ovs_list route_list; /* Contains ecmp_route_list_node */ }; @@ -8800,7 +8866,7 @@ static void ecmp_groups_add_route(struct ecmp_groups_node *group, const struct parsed_route *route) { - if (group->route_count == UINT16_MAX) { + if (group->route_count == UINT16_MAX) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "too many routes in a single ecmp group."); return; @@ -8830,6 +8896,7 @@ ecmp_groups_add(struct hmap *ecmp_groups, eg->plen = route->plen; eg->is_src_route = route->is_src_route; eg->origin = smap_get_def(&route->route->options, "origin", ""); + eg->route_table_id = route->route_table_id; ovs_list_init(&eg->route_list); ecmp_groups_add_route(eg, route); @@ -8843,7 +8910,8 @@ ecmp_groups_find(struct hmap *ecmp_groups, struct parsed_route *route) HMAP_FOR_EACH_WITH_HASH (eg, hmap_node, route->hash, ecmp_groups) { if (ipv6_addr_equals(&eg->prefix, &route->prefix) && eg->plen == route->plen && - eg->is_src_route == route->is_src_route) { + eg->is_src_route == route->is_src_route && + eg->route_table_id == route->route_table_id) { return eg; } } @@ -8890,7 +8958,8 @@ unique_routes_remove(struct hmap *unique_routes, HMAP_FOR_EACH_WITH_HASH (ur, hmap_node, route->hash, unique_routes) { if (ipv6_addr_equals(&route->prefix, &ur->route->prefix) && route->plen == ur->route->plen && - route->is_src_route == ur->route->is_src_route) { + route->is_src_route == ur->route->is_src_route && + route->route_table_id == ur->route->route_table_id) { hmap_remove(unique_routes, &ur->hmap_node); const struct parsed_route *existed_route = ur->route; free(ur); @@ -8928,9 +8997,9 @@ build_route_prefix_s(const struct in6_addr *prefix, unsigned int plen) } static void -build_route_match(const struct ovn_port *op_inport, const char *network_s, - int plen, bool is_src_route, bool is_ipv4, struct ds *match, - uint16_t *priority, int ofs) +build_route_match(const struct ovn_port *op_inport, uint32_t rtb_id, + const char *network_s, int plen, bool is_src_route, + bool is_ipv4, struct ds *match, uint16_t *priority, int ofs) { const char *dir; /* The priority here is calculated to implement longest-prefix-match @@ -8947,6 +9016,9 @@ build_route_match(const struct ovn_port *op_inport, const char *network_s, if (op_inport) { ds_put_format(match, "inport == %s && ", op_inport->json_key); } + if (rtb_id || ofs == ROUTE_PRIO_OFFSET_STATIC) { + ds_put_format(match, "%s == %d && ", REG_ROUTE_TABLE_ID, rtb_id); + } ds_put_format(match, "ip%s.%s == %s/%d", is_ipv4 ? "4" : "6", dir, network_s, plen); } @@ -9117,8 +9189,8 @@ build_ecmp_route_flow(struct hmap *lflows, struct ovn_datapath *od, char *prefix_s = build_route_prefix_s(&eg->prefix, eg->plen); int ofs = !strcmp(eg->origin, ROUTE_ORIGIN_CONNECTED) ? ROUTE_PRIO_OFFSET_CONNECTED: ROUTE_PRIO_OFFSET_STATIC; - build_route_match(NULL, prefix_s, eg->plen, eg->is_src_route, is_ipv4, - &route_match, &priority, ofs); + build_route_match(NULL, eg->route_table_id, prefix_s, eg->plen, + eg->is_src_route, is_ipv4, &route_match, &priority, ofs); free(prefix_s); struct ds actions = DS_EMPTY_INITIALIZER; @@ -9193,8 +9265,9 @@ static void add_route(struct hmap *lflows, struct ovn_datapath *od, const struct ovn_port *op, const char *lrp_addr_s, const char *network_s, int plen, const char *gateway, - bool is_src_route, const struct ovsdb_idl_row *stage_hint, - bool is_discard_route, int ofs) + bool is_src_route, const uint32_t rtb_id, + const struct ovsdb_idl_row *stage_hint, bool is_discard_route, + int ofs) { bool is_ipv4 = strchr(network_s, '.') ? true : false; struct ds match = DS_EMPTY_INITIALIZER; @@ -9209,8 +9282,8 @@ add_route(struct hmap *lflows, struct ovn_datapath *od, op_inport = op; } } - build_route_match(op_inport, network_s, plen, is_src_route, is_ipv4, - &match, &priority, ofs); + build_route_match(op_inport, rtb_id, network_s, plen, is_src_route, + is_ipv4, &match, &priority, ofs); struct ds common_actions = DS_EMPTY_INITIALIZER; struct ds actions = DS_EMPTY_INITIALIZER; @@ -9277,8 +9350,8 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od, char *prefix_s = build_route_prefix_s(&route_->prefix, route_->plen); add_route(lflows, route_->is_discard_route ? od : out_port->od, out_port, lrp_addr_s, prefix_s, route_->plen, route->nexthop, - route_->is_src_route, &route->header_, route_->is_discard_route, - ofs); + route_->is_src_route, route_->route_table_id, &route->header_, + route_->is_discard_route, ofs); free(prefix_s); } @@ -10665,6 +10738,18 @@ build_ND_RA_flows_for_lrouter(struct ovn_datapath *od, struct hmap *lflows) } } +/* Logical router ingress table IP_ROUTING_PRE: + * by default goto next. (priority 0). */ +static void +build_ip_routing_pre_flows_for_lrouter(struct ovn_datapath *od, + struct hmap *lflows) +{ + if (od->nbr) { + ovn_lflow_add(lflows, od, S_ROUTER_IN_IP_ROUTING_PRE, 0, "1", + REG_ROUTE_TABLE_ID" = 0; next;"); + } +} + /* Logical router ingress table IP_ROUTING : IP Routing. * * A packet that arrives at this table is an IP packet that should be @@ -10690,14 +10775,14 @@ build_ip_routing_flows_for_lrouter_port( for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) { add_route(lflows, op->od, op, op->lrp_networks.ipv4_addrs[i].addr_s, op->lrp_networks.ipv4_addrs[i].network_s, - op->lrp_networks.ipv4_addrs[i].plen, NULL, false, + op->lrp_networks.ipv4_addrs[i].plen, NULL, false, 0, &op->nbrp->header_, false, ROUTE_PRIO_OFFSET_CONNECTED); } for (int i = 0; i < op->lrp_networks.n_ipv6_addrs; i++) { add_route(lflows, op->od, op, op->lrp_networks.ipv6_addrs[i].addr_s, op->lrp_networks.ipv6_addrs[i].network_s, - op->lrp_networks.ipv6_addrs[i].plen, NULL, false, + op->lrp_networks.ipv6_addrs[i].plen, NULL, false, 0, &op->nbrp->header_, false, ROUTE_PRIO_OFFSET_CONNECTED); } } else if (lsp_is_router(op->nbsp)) { @@ -10720,7 +10805,7 @@ build_ip_routing_flows_for_lrouter_port( add_route(lflows, peer->od, peer, peer->lrp_networks.ipv4_addrs[0].addr_s, laddrs->ipv4_addrs[k].network_s, - laddrs->ipv4_addrs[k].plen, NULL, false, + laddrs->ipv4_addrs[k].plen, NULL, false, 0, &peer->nbrp->header_, false, ROUTE_PRIO_OFFSET_CONNECTED); } @@ -10741,10 +10826,17 @@ build_static_route_flows_for_lrouter( struct hmap ecmp_groups = HMAP_INITIALIZER(&ecmp_groups); struct hmap unique_routes = HMAP_INITIALIZER(&unique_routes); struct ovs_list parsed_routes = OVS_LIST_INITIALIZER(&parsed_routes); + struct simap route_tables = SIMAP_INITIALIZER(&route_tables); struct ecmp_groups_node *group; + + for (int i = 0; i < od->nbr->n_ports; i++) { + build_route_table_lflow(od, lflows, od->nbr->ports[i], + &route_tables); + } + for (int i = 0; i < od->nbr->n_static_routes; i++) { struct parsed_route *route = - parsed_routes_add(od, ports, &parsed_routes, + parsed_routes_add(od, ports, &parsed_routes, &route_tables, od->nbr->static_routes[i], bfd_connections); if (!route) { continue; @@ -10777,6 +10869,7 @@ build_static_route_flows_for_lrouter( ecmp_groups_destroy(&ecmp_groups); unique_routes_destroy(&unique_routes); parsed_routes_destroy(&parsed_routes); + simap_destroy(&route_tables); } } @@ -12960,6 +13053,7 @@ build_lswitch_and_lrouter_iterate_by_od(struct ovn_datapath *od, build_neigh_learning_flows_for_lrouter(od, lsi->lflows, &lsi->match, &lsi->actions, lsi->meter_groups); build_ND_RA_flows_for_lrouter(od, lsi->lflows); + build_ip_routing_pre_flows_for_lrouter(od, lsi->lflows); build_static_route_flows_for_lrouter(od, lsi->lflows, lsi->ports, lsi->bfd_connections); build_mcast_lookup_flows_for_lrouter(od, lsi->lflows, &lsi->match, diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml index 4f3a9d5e3..e7dce5c16 100644 --- a/northd/ovn-northd.8.xml +++ b/northd/ovn-northd.8.xml @@ -3291,7 +3291,37 @@ output; </li> </ul> - <h3>Ingress Table 10: IP Routing</h3> + <h3>Ingress Table 10: IP Routing Pre</h3> + + <p> + If a packet arrived at this table from Logical Router Port <var>P</var> + which has <code>options:route_table</code> value set, a logical flow with + match <code>inport == "<var>P</var>"</code> with priority 100 and action, + setting unique-generated per-datapath 32-bit value (non-zero) in OVS + register 7. This register's value is checked in next table. If packet + didn't match any configured inport (<var><main></var> route table), + register 7 value is set to 0. + </p> + + <p> + This table contains the following logical flows: + </p> + + <ul> + <li> + <p> + Priority-100 flow with match <code>inport == "LRP_NAME"</code> value + and action, which set route table identifier in reg7. + </p> + + <p> + A priority-0 logical flow with match <code>1</code> has actions + <code>reg7 = 0; next;</code>. + </p> + </li> + </ul> + + <h3>Ingress Table 11: IP Routing</h3> <p> A packet that arrives at this table is an IP packet that should be @@ -3331,14 +3361,14 @@ output; <ul> <li> <p> - Priority-550 flow that drops IPv6 Router Solicitation/Advertisement + Priority-10550 flow that drops IPv6 Router Solicitation/Advertisement packets that were not processed in previous tables. </p> </li> <li> <p> - Priority-500 flows that match IP multicast traffic destined to + Priority-10500 flows that match IP multicast traffic destined to groups registered on any of the attached switches and sets <code>outport</code> to the associated multicast group that will eventually flood the traffic to all interested attached logical @@ -3348,7 +3378,7 @@ output; <li> <p> - Priority-450 flow that matches unregistered IP multicast traffic + Priority-10450 flow that matches unregistered IP multicast traffic and sets <code>outport</code> to the <code>MC_STATIC</code> multicast group, which <code>ovn-northd</code> populates with the logical ports that have @@ -3362,10 +3392,11 @@ output; <p> IPv4 routing table. For each route to IPv4 network <var>N</var> with netmask <var>M</var>, on router port <var>P</var> with IP address - <var>A</var> and Ethernet - address <var>E</var>, a logical flow with match <code>ip4.dst == - <var>N</var>/<var>M</var></code>, whose priority is the number of - 1-bits in <var>M</var>, has the following actions: + <var>A</var> and Ethernet address <var>E</var>, a logical flow with + match <code>ip4.dst == <var>N</var>/<var>M</var></code>, whose + priority is the number of 1-bits in <var>M</var> multiplied by 3 + + route offset: 0 for src-ip route policy, 1 for dst-ip static routes. + This flow has the following actions: </p> <pre> @@ -3398,8 +3429,9 @@ next; <var>P</var> with IP address <var>A</var> and Ethernet address <var>E</var>, a logical flow with match in CIDR notation <code>ip6.dst == <var>N</var>/<var>M</var></code>, - whose priority is the integer value of <var>M</var>, has the - following actions: + whose priority is the integer value of <var>M</var> multiplied by 3 + + route offset: 0 for src-ip route policy, 1 for dst-ip static routes. + This flow has the following actions: </p> <pre> @@ -3428,6 +3460,20 @@ next; If the address <var>A</var> is in the link-local scope, the route will be limited to sending on the ingress port. </p> + + <p> + For each static route the <code>reg7 == id &&</code> is + prefixed in logical flow match portion. For routes with + <code>route_table</code> value set a unique non-zero id is used. + For routes within <code><main></code> route table (no route + table set), this id value is 0. + </p> + <p> + For each <var>connected</var> route (route to the LRP's subnet CIDR) + the logical flow match portion has no + <code>reg7 == id &&</code> prefix to have route to LRP's + subnets in all routing tables. + </p> </li> <li> @@ -3454,7 +3500,7 @@ select(reg8[16..31], <var>MID1</var>, <var>MID2</var>, ...); </li> </ul> - <h3>Ingress Table 11: IP_ROUTING_ECMP</h3> + <h3>Ingress Table 12: IP_ROUTING_ECMP</h3> <p> This table implements the second part of IP routing for ECMP routes @@ -3506,7 +3552,7 @@ outport = <var>P</var>; </li> </ul> - <h3>Ingress Table 12: Router policies</h3> + <h3>Ingress Table 13: Router policies</h3> <p> This table adds flows for the logical router policies configured on the logical router. Please see the @@ -3578,7 +3624,7 @@ next; </li> </ul> - <h3>Ingress Table 13: ECMP handling for router policies</h3> + <h3>Ingress Table 14: ECMP handling for router policies</h3> <p> This table handles the ECMP for the router policies configured with multiple nexthops. @@ -3622,7 +3668,7 @@ outport = <var>P</var> </li> </ul> - <h3>Ingress Table 14: ARP/ND Resolution</h3> + <h3>Ingress Table 15: ARP/ND Resolution</h3> <p> Any packet that reaches this table is an IP packet whose next-hop @@ -3813,7 +3859,7 @@ outport = <var>P</var> </ul> - <h3>Ingress Table 15: Check packet length</h3> + <h3>Ingress Table 16: Check packet length</h3> <p> For distributed logical routers or gateway routers with gateway @@ -3843,7 +3889,7 @@ REGBIT_PKT_LARGER = check_pkt_larger(<var>L</var>); next; and advances to the next table. </p> - <h3>Ingress Table 16: Handle larger packets</h3> + <h3>Ingress Table 17: Handle larger packets</h3> <p> For distributed logical routers or gateway routers with gateway port @@ -3906,7 +3952,7 @@ icmp6 { and advances to the next table. </p> - <h3>Ingress Table 17: Gateway Redirect</h3> + <h3>Ingress Table 18: Gateway Redirect</h3> <p> For distributed logical routers where one or more of the logical router @@ -3953,7 +3999,7 @@ icmp6 { </li> </ul> - <h3>Ingress Table 18: ARP Request</h3> + <h3>Ingress Table 19: ARP Request</h3> <p> In the common case where the Ethernet destination has been resolved, this diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema index 5dee04fe9..55977339a 100644 --- a/ovn-nb.ovsschema +++ b/ovn-nb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Northbound", - "version": "5.33.1", - "cksum": "1931852754 30731", + "version": "5.34.1", + "cksum": "2177334725 30782", "tables": { "NB_Global": { "columns": { @@ -407,6 +407,7 @@ "isRoot": false}, "Logical_Router_Static_Route": { "columns": { + "route_table": {"type": "string"}, "ip_prefix": {"type": "string"}, "policy": {"type": {"key": {"type": "string", "enum": ["set", ["src-ip", diff --git a/ovn-nb.xml b/ovn-nb.xml index 036ffa64f..4a1d4abdf 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -2820,6 +2820,14 @@ prefix according to RFC3663 </p> </column> + + <column name="options" key="route_table"> + Designates lookup Logical_Router_Static_Routes with specified + <code>route_table</code> value. Routes to directly connected networks + from same Logical Router and routes without <code>route_table</code> + option set have higher priority than routes with + <code>route_table</code> option set. + </column> </group> <group title="Attachment"> @@ -2939,6 +2947,28 @@ </p> </column> + <column name="route_table"> + <p> + Any string to place route to separate routing table. If Logical Router + Port has configured value in <ref table="Logical_Router_Port" + column="options" key="route_table"/> other than empty string, OVN + performs route lookup for all packets entering Logical Router ingress + pipeline from this port in the following manner: + </p> + + <ul> + <li> + 1. First lookup among "global" routes: routes without + <code>route_table</code> value set and routes to directly connected + networks. + </li> + <li> + 2. Next lookup among routes with same <code>route_table</code> value + as specified in LRP's options:route_table field. + </li> + </ul> + </column> + <column name="external_ids" key="ic-learned-route"> <code>ovn-ic</code> populates this key if the route is learned from the global <ref db="OVN_IC_Southbound"/> database. In this case the value diff --git a/tests/ovn-ic.at b/tests/ovn-ic.at index 7e8498b2f..bb4b98d0b 100644 --- a/tests/ovn-ic.at +++ b/tests/ovn-ic.at @@ -282,6 +282,7 @@ done AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl IPv4 Routes +Route Table <main>: 10.11.1.0/24 169.254.0.1 dst-ip 10.11.2.0/24 169.254.100.2 dst-ip (learned) 10.22.1.0/24 169.254.0.2 src-ip @@ -300,6 +301,7 @@ ovn_as az1 ovn-nbctl set nb_global . options:ic-route-learn=false OVS_WAIT_WHILE([ovn_as az1 ovn-nbctl lr-route-list lr1 | grep learned]) AT_CHECK([ovn_as az1 ovn-nbctl lr-route-list lr1], [0], [dnl IPv4 Routes +Route Table <main>: 10.11.1.0/24 169.254.0.1 dst-ip 10.22.1.0/24 169.254.0.2 src-ip ]) @@ -315,6 +317,7 @@ ovn_as az1 ovn-nbctl set nb_global . options:ic-route-adv=false OVS_WAIT_WHILE([ovn_as az2 ovn-nbctl lr-route-list lr2 | grep learned]) AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl IPv4 Routes +Route Table <main>: 10.11.2.0/24 169.254.0.1 dst-ip 10.22.2.0/24 169.254.0.2 src-ip ]) @@ -333,6 +336,7 @@ done # Default route should NOT get advertised or learned, by default. AT_CHECK([ovn_as az2 ovn-nbctl lr-route-list lr2], [0], [dnl IPv4 Routes +Route Table <main>: 10.11.1.0/24 169.254.100.1 dst-ip (learned) 10.11.2.0/24 169.254.0.1 dst-ip 10.22.2.0/24 169.254.0.2 src-ip diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at index a8946fef8..911310aeb 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -1522,6 +1522,7 @@ AT_CHECK([ovn-nbctl --ecmp --policy=src-ip lr-route-add lr0 20.0.0.0/24 11.0.0.1 AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.1 dst-ip 10.0.1.0/24 11.0.1.1 dst-ip lp0 10.0.10.0/24 dst-ip lp0 @@ -1536,6 +1537,7 @@ AT_CHECK([ovn-nbctl lrp-add lr0 lp1 f0:00:00:00:00:02 11.0.0.254/24]) AT_CHECK([ovn-nbctl --may-exist lr-route-add lr0 10.0.0.111/24 11.0.0.1 lp1]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.1 dst-ip lp1 10.0.1.0/24 11.0.1.1 dst-ip lp0 10.0.10.0/24 dst-ip lp0 @@ -1566,6 +1568,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 9.16.1.0/24]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.1 dst-ip lp1 10.0.10.0/24 dst-ip lp0 10.0.0.0/24 11.0.0.2 src-ip @@ -1577,6 +1580,7 @@ AT_CHECK([ovn-nbctl --policy=dst-ip lr-route-del lr0 10.0.0.0/24]) AT_CHECK([ovn-nbctl --policy=src-ip lr-route-del lr0 10.0.0.0/24]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.10.0/24 dst-ip lp0 0.0.0.0/0 192.168.0.1 dst-ip ]) @@ -1587,6 +1591,7 @@ AT_CHECK([ovn-nbctl --policy=src-ip lr-route-add lr0 10.0.0.0/24 11.0.0.2]) AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.10.0/24 dst-ip lp0 0.0.0.0/0 192.168.0.1 dst-ip ]) @@ -1603,6 +1608,7 @@ AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.3]) AT_CHECK([ovn-nbctl --ecmp lr-route-add lr0 10.0.0.0/24 11.0.0.4 lp0]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.1 dst-ip ecmp 10.0.0.0/24 11.0.0.2 dst-ip ecmp 10.0.0.0/24 11.0.0.3 dst-ip ecmp @@ -1617,6 +1623,7 @@ dnl Delete ecmp routes AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.1]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.2 dst-ip ecmp 10.0.0.0/24 11.0.0.3 dst-ip ecmp 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp @@ -1624,12 +1631,14 @@ IPv4 Routes AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.2]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.3 dst-ip ecmp 10.0.0.0/24 11.0.0.4 dst-ip lp0 ecmp ]) AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.4 lp0]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.3 dst-ip ]) AT_CHECK([ovn-nbctl lr-route-del lr0 10.0.0.0/24 11.0.0.3]) @@ -1643,6 +1652,7 @@ AT_CHECK([ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv6 Routes +Route Table <main>: 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ::/0 2001:db8:0:f101::1 dst-ip @@ -1652,6 +1662,7 @@ AT_CHECK([ovn-nbctl lr-route-del lr0 2001:0db8:0::/64]) AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv6 Routes +Route Table <main>: 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ::/0 2001:db8:0:f101::1 dst-ip ]) @@ -1679,11 +1690,13 @@ AT_CHECK([ovn-nbctl --may-exist --ecmp-symmetric-reply lr-route-add lr0 2003:0db AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl IPv4 Routes +Route Table <main>: 10.0.0.0/24 11.0.0.1 dst-ip 10.0.1.0/24 11.0.1.1 dst-ip lp0 0.0.0.0/0 192.168.0.1 dst-ip IPv6 Routes +Route Table <main>: 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip ecmp 2001:db8:1::/64 2001:db8:0:f103::2 dst-ip ecmp @@ -1698,7 +1711,188 @@ AT_CHECK([ovn-nbctl lrp-add lr0 lr0-p0 00:00:01:01:02:03 192.168.10.1/24]) bfd_uuid=$(ovn-nbctl create bfd logical_port=lr0-p0 dst_ip=100.0.0.50 status=down min_tx=250 min_rx=250 detect_mult=10) AT_CHECK([ovn-nbctl lr-route-add lr0 100.0.0.0/24 192.168.0.1]) route_uuid=$(fetch_column nb:logical_router_static_route _uuid ip_prefix="100.0.0.0/24") -AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid])]) +AT_CHECK([ovn-nbctl set logical_router_static_route $route_uuid bfd=$bfd_uuid]) + +check ovn-nbctl lr-route-del lr0 +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +]) + +dnl Check IPv4 routes in route table +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 0.0.0.0/0 192.168.0.1 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 10.0.0.1/24 11.0.0.1 +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table rtb-1: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip +]) + +check ovn-nbctl lr-route-del lr0 +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +]) + +dnl Check IPv6 routes in route table +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1 + +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +IPv6 Routes +Route Table rtb-1: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +dnl Check IPv4 and IPv6 routes in route table +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 0.0.0.0/0 192.168.0.1 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 10.0.0.1/24 11.0.0.1 + +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table rtb-1: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +IPv6 Routes +Route Table rtb-1: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +# Add routes in another route table +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 0.0.0.0/0 192.168.0.1 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 10.0.0.1/24 11.0.0.1 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1 + +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table rtb-1: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +Route Table rtb-2: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +IPv6 Routes +Route Table rtb-1: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip + +Route Table rtb-2: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +# Add routes to <main> route table +check ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.1 +check ovn-nbctl lr-route-add lr0 10.0.1.1/24 11.0.1.1 lp0 +check ovn-nbctl lr-route-add lr0 10.0.0.1/24 11.0.0.1 +check ovn-nbctl lr-route-add lr0 0:0:0:0:0:0:0:0/0 2001:0db8:0:f101::1 +check ovn-nbctl lr-route-add lr0 2001:0db8:0::/64 2001:0db8:0:f102::1 lp0 +check check ovn-nbctl lr-route-add lr0 2001:0db8:1::/64 2001:0db8:0:f103::1 + +AT_CHECK([ovn-nbctl lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table <main>: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +Route Table rtb-1: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +Route Table rtb-2: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +IPv6 Routes +Route Table <main>: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip + +Route Table rtb-1: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip + +Route Table rtb-2: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +# delete IPv4 route from rtb-1 +check ovn-nbctl --route-table=rtb-1 lr-route-del lr0 10.0.0.0/24 +AT_CHECK([ovn-nbctl --route-table=rtb-1 lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table rtb-1: + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +IPv6 Routes +Route Table rtb-1: + 2001:db8::/64 2001:db8:0:f102::1 dst-ip lp0 + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +# delete IPv6 route from rtb-2 +check ovn-nbctl --route-table=rtb-2 lr-route-del lr0 2001:db8::/64 +AT_CHECK([ovn-nbctl --route-table=rtb-2 lr-route-list lr0], [0], [dnl +IPv4 Routes +Route Table rtb-2: + 10.0.0.0/24 11.0.0.1 dst-ip + 10.0.1.0/24 11.0.1.1 dst-ip lp0 + 0.0.0.0/0 192.168.0.1 dst-ip + +IPv6 Routes +Route Table rtb-2: + 2001:db8:1::/64 2001:db8:0:f103::1 dst-ip + ::/0 2001:db8:0:f101::1 dst-ip +]) + +check ovn-nbctl lr-route-del lr0 + +# ECMP route in route table +check ovn-nbctl --route-table=rtb1 lr-route-add lr0 0.0.0.0/0 192.168.0.1 +check ovn-nbctl --ecmp --route-table=rtb1 lr-route-add lr0 0.0.0.0/0 192.168.0.2 + +# Negative route table case: same prefix +AT_CHECK([ovn-nbctl --route-table=rtb1 lr-route-add lr0 0.0.0.0/0 192.168.0.1], [1], [], [dnl +ovn-nbctl: duplicate prefix: 0.0.0.0/0 (policy: dst-ip). Use option --ecmp to allow this for ECMP routing. +]) + +# Negative route table case: same prefix & nexthop with ecmp +AT_CHECK([ovn-nbctl --ecmp --route-table=rtb1 lr-route-add lr0 0.0.0.0/0 192.168.0.2], [1], [], [dnl +ovn-nbctl: duplicate nexthop for the same ECMP route +]) + +# Add routes to <main> route table +check ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:00:01 1.1.1.1/24 +check ovn-nbctl lrp-set-options lrp0 route_table=rtb1 +AT_CHECK([ovn-nbctl get logical-router-port lrp0 options:route_table], [0], [dnl +rtb1 +]) +check `ovn-nbctl show lr0 | grep lrp0 -A3 | grep route_table=rtb1` +]) dnl --------------------------------------------------------------------- diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 3c1a97f73..333793af0 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -5430,7 +5430,7 @@ check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.16 ovn-sbctl dump-flows lr0 > lr0flows AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl - table=??(lr_in_ip_routing ), priority=97 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) + table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) ]) AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' | sed 's/table=../table=??/' | sort], [0], [dnl table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) @@ -5443,7 +5443,7 @@ check ovn-nbctl --wait=sb --ecmp-symmetric-reply lr-route-add lr0 1.0.0.1 192.16 ovn-sbctl dump-flows lr0 > lr0flows AT_CHECK([grep -e "lr_in_ip_routing.*select" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl - table=??(lr_in_ip_routing ), priority=97 , match=(ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) + table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 0 && ip4.dst == 1.0.0.1/32), action=(ip.ttl--; flags.loopback = 1; reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);) ]) AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed 's/192\.168\.0\..0/192.168.0.??/' | sed 's/table=../table=??/' | sort], [0], [dnl table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] == 1 && reg8[[16..31]] == 1), action=(reg0 = 192.168.0.??; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; next;) @@ -5458,14 +5458,14 @@ check ovn-nbctl --wait=sb lr-route-add lr0 1.0.0.0/24 192.168.0.10 ovn-sbctl dump-flows lr0 > lr0flows AT_CHECK([grep -e "lr_in_ip_routing.*192.168.0.10" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl - table=??(lr_in_ip_routing ), priority=73 , match=(ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 0 && ip4.dst == 1.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) ]) check ovn-nbctl --wait=sb lr-route-add lr0 2.0.0.0/24 lr0-public ovn-sbctl dump-flows lr0 > lr0flows AT_CHECK([grep -e "lr_in_ip_routing.*2.0.0.0" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl - table=??(lr_in_ip_routing ), priority=73 , match=(ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 0 && ip4.dst == 2.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:20:20:12:13; outport = "lr0-public"; flags.loopback = 1; next;) ]) AT_CLEANUP @@ -5655,3 +5655,69 @@ ct_dnat /* assuming no un-dnat entry, so no change */ /* default (use --ct to cu AT_CLEANUP ]) + +AT_SETUP([route tables -- flows]) +AT_KEYWORDS([route-tables-flows]) +ovn_start + +check ovn-nbctl lr-add lr0 +check ovn-nbctl lrp-add lr0 lrp0 00:00:00:00:00:01 192.168.0.1/24 +check ovn-nbctl lrp-add lr0 lrp1 00:00:00:00:01:01 192.168.1.1/24 +check ovn-nbctl lrp-add lr0 lrp2 00:00:00:00:02:01 192.168.2.1/24 +check ovn-nbctl lrp-set-options lrp1 route_table=rtb-1 +check ovn-nbctl lrp-set-options lrp2 route_table=rtb-2 + +check ovn-nbctl lr-route-add lr0 0.0.0.0/0 192.168.0.10 +check ovn-nbctl --route-table=rtb-1 lr-route-add lr0 192.168.0.0/24 192.168.1.10 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 0.0.0.0/0 192.168.0.10 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 1.1.1.1/32 192.168.0.20 +check ovn-nbctl --route-table=rtb-2 lr-route-add lr0 2.2.2.2/32 192.168.0.30 +check ovn-nbctl --route-table=rtb-2 --ecmp lr-route-add lr0 2.2.2.2/32 192.168.0.31 +check ovn-nbctl --wait=sb sync + +ovn-sbctl dump-flows lr0 > lr0flows +AT_CAPTURE_FILE([lr0flows]) + +AT_CHECK([grep -e "lr_in_ip_routing_pre.*match=(1)" lr0flows | sed 's/table=../table=??/'], [0], [dnl + table=??(lr_in_ip_routing_pre), priority=0 , match=(1), action=(reg7 = 0; next;) +]) + +p1_reg=$(grep -oP "lr_in_ip_routing_pre.*lrp1.*action=\(reg7 = \K." lr0flows) +p2_reg=$(grep -oP "lr_in_ip_routing_pre.*lrp2.*action=\(reg7 = \K." lr0flows) +echo $p1_reg +echo $p2_reg + +# exact register values are not predictable +if [[ $p1_reg -eq 2 ] && [ $p2_reg -eq 1 ]]; then + echo "swap reg values in dump" + sed -i -r s'/^(.*lrp2.*action=\(reg7 = )(1)(.*)/\12\3/g' lr0flows # "reg7 = 1" -> "reg7 = 2" + sed -i -r s'/^(.*lrp1.*action=\(reg7 = )(2)(.*)/\11\3/g' lr0flows # "reg7 = 2" -> "reg7 = 1" + sed -i -r s'/^(.*match=\(reg7 == )(2)( &&.*lrp1.*)/\11\3/g' lr0flows # "reg7 == 2" -> "reg7 == 1" + sed -i -r s'/^(.*match=\(reg7 == )(1)( &&.*lrp0.*)/\12\3/g' lr0flows # "reg7 == 1" -> "reg7 == 2" +fi + +check test "$p1_reg" != "$p2_reg" -a $((p1_reg * p2_reg)) -eq 2 + +AT_CHECK([grep "lr_in_ip_routing_pre" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_in_ip_routing_pre), priority=0 , match=(1), action=(reg7 = 0; next;) + table=??(lr_in_ip_routing_pre), priority=100 , match=(inport == "lrp1"), action=(reg7 = 1; next;) + table=??(lr_in_ip_routing_pre), priority=100 , match=(inport == "lrp2"), action=(reg7 = 2; next;) +]) + +grep -e "(lr_in_ip_routing ).*outport" lr0flows + +AT_CHECK([grep -e "(lr_in_ip_routing ).*outport" lr0flows | sed 's/table=../table=??/' | sort], [0], [dnl + table=??(lr_in_ip_routing ), priority=1 , match=(reg7 == 0 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=1 , match=(reg7 == 2 && ip4.dst == 0.0.0.0/0), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.10; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp0" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp1" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:101; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=194 , match=(inport == "lrp2" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0; xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:201; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=73 , match=(reg7 == 1 && ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.1.10; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.1.1; eth.src = 00:00:00:00:01:01; outport = "lrp1"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=74 , match=(ip4.dst == 192.168.2.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg1 = 192.168.2.1; eth.src = 00:00:00:00:02:01; outport = "lrp2"; flags.loopback = 1; next;) + table=??(lr_in_ip_routing ), priority=97 , match=(reg7 == 2 && ip4.dst == 1.1.1.1/32), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = 192.168.0.20; reg1 = 192.168.0.1; eth.src = 00:00:00:00:00:01; outport = "lrp0"; flags.loopback = 1; next;) +]) + +AT_CLEANUP +]) diff --git a/tests/ovn.at b/tests/ovn.at index 51e0dae0b..28e5d679b 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -18257,7 +18257,7 @@ eth_dst=00000000ff01 ip_src=$(ip_to_hex 10 0 0 10) ip_dst=$(ip_to_hex 172 168 0 101) send_icmp_packet 1 1 $eth_src $eth_dst $ip_src $ip_dst c4c9 0000000000000000000000 -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=25, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | awk '/table=26, n_packets=1, n_bytes=45/{print $7" "$8}'],[0],[dnl priority=80,ip,reg15=0x3,metadata=0x3,nw_src=10.0.0.10 actions=drop ]) @@ -22698,6 +22698,538 @@ OVN_CLEANUP([hv1]) AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route tables -- <main> route table routes]) +ovn_start + +# Logical network: +# ls1 (192.168.1.0/24) - lrp-lr1-ls1 - lr1 - lrp-lr1-ls2 - ls2 (192.168.2.0/24) +# +# ls1 has lsp11 (192.168.1.11) and +# ls2 has lsp21 (192.168.2.21) and lsp22 (192.168.2.22) +# +# lrp-lr1-ls1 set options:route_table=rtb-1 +# +# Static routes on lr1: +# 0.0.0.0/0 nexthop 192.168.2.21 +# 1.1.1.1/32 nexthop 192.168.2.22 route_table=rtb-1 +# +# Test 1: +# lsp11 send packet to 2.2.2.2 +# +# Expected result: +# each port should receive no traffic +# +# Test 2: +# lsp11 send packet to 1.1.1.1 +# +# Expected result: +# lsp22 should receive traffic, lsp21 should not +# +# Test 3: +# lsp21 send packet to 1.1.1.1 +# +# Expected result: +# lsp11 should receive traffic, lsp21 should not receive any traffic + +ovn-nbctl lr-add lr1 + +for i in 1 2; do + ovn-nbctl ls-add ls${i} + ovn-nbctl lrp-add lr1 lrp-lr1-ls${i} 00:00:00:01:0${i}:01 192.168.${i}.1/24 + ovn-nbctl lsp-add ls${i} lsp-ls${i}-lr1 -- lsp-set-type lsp-ls${i}-lr1 router \ + -- lsp-set-options lsp-ls${i}-lr1 router-port=lrp-lr1-ls${i} \ + -- lsp-set-addresses lsp-ls${i}-lr1 router +done + +# install static routes +ovn-nbctl lr-route-add lr1 0.0.0.0/0 192.168.2.21 +ovn-nbctl --route-table=rtb-1 lr-route-add lr1 1.1.1.1/32 192.168.2.22 + +# set lrp-lr1-ls1 route table +ovn-nbctl lrp-set-options lrp-lr1-ls1 route_table=rtb-1 + +# Create logical ports +ovn-nbctl lsp-add ls1 lsp11 -- \ + lsp-set-addresses lsp11 "f0:00:00:00:01:11 192.168.1.11" +ovn-nbctl lsp-add ls2 lsp21 -- \ + lsp-set-addresses lsp21 "f0:00:00:00:02:21 192.168.2.21" +ovn-nbctl lsp-add ls2 lsp22 -- \ + lsp-set-addresses lsp22 "f0:00:00:00:02:22 192.168.2.22" + +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=lsp11 \ + 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=lsp21 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +ovs-vsctl -- add-port br-int hv1-vif3 -- \ + set interface hv1-vif3 external-ids:iface-id=lsp22 \ + options:tx_pcap=hv1/vif3-tx.pcap \ + options:rxq_pcap=hv1/vif3-rx.pcap \ + ofport-request=3 + +# wait for earlier changes to take effect +check ovn-nbctl --wait=hv sync +wait_for_ports_up + +# Test 1 +packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 && + ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==2.2.2.2 && icmp" +AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +# Assume no packets go neither to lsp21 nor to lsp22. +> expected_lsp21 +> expected_lsp22 + +ovn-sbctl dump-flows lr1 + + +OVS_WAIT_UNTIL([ + rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l` + rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > lsp22.packets && cat lsp22.packets | wc -l` + echo $rcv_n1 $rcv_n2 + test $rcv_n1 -eq 0 -a $rcv_n2 -eq 0]) + +for i in 1 2; do + sort expected_lsp2$i > expout + AT_CHECK([cat lsp2${i}.packets | sort], [0], [expout]) +done + +# cleanup pcap +for i in 2 3; do + ovs-vsctl remove interface hv1-vif$i options tx_pcap + > hv1/vif$i-tx.pcap + ovs-vsctl set interface hv1-vif$i options:tx_pcap=hv1/vif$i-tx.pcap +done + +# Test 2 +packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 && + ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==1.1.1.1 && icmp" +AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +# Assume all packets go to lsp22. +exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:22 && + ip4 && ip.ttl==63 && ip4.src==192.168.1.11 && ip4.dst==1.1.1.1 && icmp" +echo $exp_packet | ovstest test-ovn expr-to-packets > expected_lsp22 +> expected_lsp21 + +# lsp21 should recieve 1 packet and lsp22 should recieve no packets +OVS_WAIT_UNTIL([ + rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l` + rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > lsp22.packets && cat lsp22.packets | wc -l` + echo $rcv_n1 $rcv_n2 + test $rcv_n1 -eq 0 -a $rcv_n2 -eq 1]) + +for i in 1 2; do + sort expected_lsp2$i > expout + AT_CHECK([cat lsp2${i}.packets | sort], [0], [expout]) +done + +# cleanup pcap +for i in 2 3; do + ovs-vsctl remove interface hv1-vif$i options tx_pcap + > hv1/vif$i-tx.pcap + ovs-vsctl set interface hv1-vif$i options:tx_pcap=hv1/vif$i-tx.pcap +done + +# Test 3 +packet="inport==\"lsp21\" && eth.src==f0:00:00:00:02:21 && eth.dst==00:00:00:01:02:01 && + ip4 && ip.ttl==64 && ip4.src==192.168.2.21 && ip4.dst==2.2.2.2 && icmp" +AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +# Assume all packets go to lsp21. +exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 && + ip4 && ip.ttl==63 && ip4.src==192.168.2.21 && ip4.dst==2.2.2.2 && icmp" +echo $exp_packet | ovstest test-ovn expr-to-packets > expected_lsp21 +> expected_lsp22 + +# lsp21 should recieve 1 packet and lsp22 should recieve no packets +OVS_WAIT_UNTIL([ + rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l` + rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif3-tx.pcap > lsp22.packets && cat lsp22.packets | wc -l` + echo $rcv_n1 $rcv_n2 + test $rcv_n1 -eq 1 -a $rcv_n2 -eq 0]) + +for i in 1 2; do + sort expected_lsp2$i > expout + AT_CHECK([cat lsp2${i}.packets | sort], [0], [expout]) +done + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) + + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route tables -- directly connected routes]) +ovn_start + +# Logical network: +# ls1 (192.168.1.0/24) - lrp-lr1-ls1 - lr1 - lrp-lr1-ls2 - ls2 (192.168.2.0/24) +# +# ls1 has lsp11 (192.168.1.11) and ls2 has lsp21 (192.168.2.21) +# +# lrp-lr1-ls1 set options:route_table=rtb-1 +# +# Static routes on lr1: +# 192.168.2.128/25 nexthop 192.168.1.11 route_table=rtb-1 +# +# Test 1: +# lsp11 send packet to 192.168.2.21 +# +# Expected result: +# lsp21 should receive traffic, lsp11 should not receive any traffic +# +# Test 2: +# lsp11 send packet to 192.168.2.200 +# +# Expected result: +# lsp11 should receive traffic, lsp21 should not receive any traffic + +ovn-nbctl lr-add lr1 + +for i in 1 2; do + ovn-nbctl ls-add ls${i} + ovn-nbctl lrp-add lr1 lrp-lr1-ls${i} 00:00:00:01:0${i}:01 192.168.${i}.1/24 + ovn-nbctl lsp-add ls${i} lsp-ls${i}-lr1 -- lsp-set-type lsp-ls${i}-lr1 router \ + -- lsp-set-options lsp-ls${i}-lr1 router-port=lrp-lr1-ls${i} \ + -- lsp-set-addresses lsp-ls${i}-lr1 router +done + +# install static route, which is more specific than directly-connected route +ovn-nbctl --route-table=rtb-1 lr-route-add lr1 192.168.2.128/25 192.168.1.11 + +# set lrp-lr1-ls1 route table +ovn-nbctl lrp-set-options lrp-lr1-ls1 route_table=rtb-1 + +# Create logical ports +ovn-nbctl lsp-add ls1 lsp11 -- \ + lsp-set-addresses lsp11 "f0:00:00:00:01:11 192.168.1.11" +ovn-nbctl lsp-add ls2 lsp21 -- \ + lsp-set-addresses lsp21 "f0:00:00:00:02:21 192.168.2.21" + +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=lsp11 \ + 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=lsp21 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=2 + +# wait for earlier changes to take effect +check ovn-nbctl --wait=hv sync +wait_for_ports_up + +# test 1 +packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 && + ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.21 && icmp" +AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +# Assume all packets go to lsp21. +exp_packet="eth.src==00:00:00:01:02:01 && eth.dst==f0:00:00:00:02:21 && ip4 && + ip.ttl==63 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.21 && icmp" +echo $exp_packet | ovstest test-ovn expr-to-packets > expected_lsp21 +> expected_lsp11 + +# lsp21 should recieve 1 icmp packet and lsp11 should recieve no packets +OVS_WAIT_UNTIL([ + rcv_n11=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > lsp11.packets && cat lsp11.packets | wc -l` + rcv_n21=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l` + echo $rcv_n11 $rcv_n21 + test $rcv_n11 -eq 0 -a $rcv_n21 -eq 1]) + +for i in 11 21; do + sort expected_lsp$i > expout + AT_CHECK([cat lsp${i}.packets | sort], [0], [expout]) +done + +for i in 11 21; do + > lsp${i}.packets +done + +# flush tx pcaps: +ovs-vsctl remove interface hv1-vif1 options tx_pcap +ovs-vsctl remove interface hv1-vif2 options tx_pcap +rm -f hv1/vif1-tx.pcap hv1/vif2-tx.pcap +ovs-vsctl set interface hv1-vif1 options:tx_pcap=hv1/vif1-tx.pcap +ovs-vsctl set interface hv1-vif2 options:tx_pcap=hv1/vif2-tx.pcap + +# test 2 +packet="inport==\"lsp11\" && eth.src==f0:00:00:00:01:11 && eth.dst==00:00:00:01:01:01 && + ip4 && ip.ttl==64 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.200 && icmp" +AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + +# Assume all packets go to lsp11. +exp_packet="eth.src==00:00:00:01:01:01 && eth.dst==f0:00:00:00:01:11 && ip4 && + ip.ttl==63 && ip4.src==192.168.1.11 && ip4.dst==192.168.2.200 && icmp" +echo $exp_packet | ovstest test-ovn expr-to-packets > expected_lsp11 +> expected_lsp21 + +# lsp11 should recieve 1 icmp packet and lsp21 should recieve no packets +OVS_WAIT_UNTIL([ + rcv_n11=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif1-tx.pcap > lsp11.packets && cat lsp11.packets | wc -l` + rcv_n21=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap > lsp21.packets && cat lsp21.packets | wc -l` + echo $rcv_n11 $rcv_n21 + test $rcv_n11 -eq 1 -a $rcv_n21 -eq 0]) + +for i in 11 21; do + echo I=$i + sort expected_lsp$i > expout + AT_CHECK([cat lsp${i}.packets | sort], [0], [expout]) +done + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) + + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route tables -- overlapping subnets]) +ovn_start + +# Logical network: +# +# ls1 (192.168.1.0/24) - lrp-lr1-ls1 -\ /- lrp-lr1-ls2 - ls2 (192.168.2.0/24) +# lr1 +# ls3 (192.168.3.0/24) - lrp-lr1-ls3 -/ \- lrp-lr1-ls4 - ls4 (192.168.4.0/24) +# +# ls1 has lsp11 (192.168.1.11) +# ls2 has lsp21 (192.168.2.21) +# ls3 has lsp31 (192.168.3.31) +# ls4 has lsp41 (192.168.4.41) +# +# lrp-lr1-ls1 set options:route_table=rtb-1 +# +# Static routes on lr1: +# 10.0.0.0/24 nexthop 192.168.3.31 route_table=rtb-1 +# 10.0.0.0/24 nexthop 192.168.4.41 (<main> route table) +# +# Test 1: +# lsp11 send packet to 10.0.0.1 +# +# Expected result: +# lsp31 should receive traffic, lsp41 should not receive any traffic +# +# Test 2: +# lsp21 send packet to 10.0.0.1 +# +# Expected result: +# lsp41 should receive traffic, lsp31 should not receive any traffic + +ovn-nbctl lr-add lr1 + +# Create logical topology +for i in $(seq 1 4); do + ovn-nbctl ls-add ls${i} + ovn-nbctl lrp-add lr1 lrp-lr1-ls${i} 00:00:00:01:0${i}:01 192.168.${i}.1/24 + ovn-nbctl lsp-add ls${i} lsp-ls${i}-lr1 -- lsp-set-type lsp-ls${i}-lr1 router \ + -- lsp-set-options lsp-ls${i}-lr1 router-port=lrp-lr1-ls${i} \ + -- lsp-set-addresses lsp-ls${i}-lr1 router + ovn-nbctl lsp-add ls$i lsp${i}1 -- \ + lsp-set-addresses lsp${i}1 "f0:00:00:00:0${i}:1${i} 192.168.${i}.${i}1" +done + +# install static routes +ovn-nbctl --route-table=rtb-1 lr-route-add lr1 10.0.0.0/24 192.168.3.31 +ovn-nbctl lr-route-add lr1 10.0.0.0/24 192.168.4.41 + +# set lrp-lr1-ls1 route table +ovn-nbctl lrp-set-options lrp-lr1-ls1 route_table=rtb-1 + +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 + +for i in $(seq 1 4); do + ovs-vsctl -- add-port br-int hv1-vif${i} -- \ + set interface hv1-vif${i} external-ids:iface-id=lsp${i}1 \ + options:tx_pcap=hv1/vif${i}-tx.pcap \ + options:rxq_pcap=hv1/vif${i}-rx.pcap \ + ofport-request=${i} +done + +# wait for earlier changes to take effect +check ovn-nbctl --wait=hv sync +wait_for_ports_up + +# lsp31 should recieve packet coming from lsp11 +# lsp41 should recieve packet coming from lsp21 +for i in $(seq 1 2); do + di=$(( i + 2)) # dst index + ri=$(( 5 - i)) # reverse index + packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} && + eth.dst==00:00:00:01:0${i}:01 && ip4 && ip.ttl==64 && + ip4.src==192.168.${i}.${i}1 && ip4.dst==10.0.0.1 && icmp" + AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + + # Assume all packets go to lsp${di}1. + exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} && + ip4 && ip.ttl==63 && ip4.src==192.168.${i}.${i}1 && ip4.dst==10.0.0.1 && icmp" + echo $exp_packet | ovstest test-ovn expr-to-packets >> expected_lsp${di}1 + > expected_lsp${ri}1 + + OVS_WAIT_UNTIL([ + rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif${di}-tx.pcap > lsp${di}1.packets && cat lsp${di}1.packets | wc -l` + rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif${ri}-tx.pcap > lsp${ri}1.packets && cat lsp${ri}1.packets | wc -l` + echo $rcv_n1 $rcv_n2 + test $rcv_n1 -eq 1 -a $rcv_n2 -eq 0]) + + for j in "${di}1" "${ri}1"; do + sort expected_lsp${j} > expout + AT_CHECK([cat lsp${j}.packets | sort], [0], [expout]) + done + + # cleanup tx pcap files + for j in "${di}1" "${ri}1"; do + ovs-vsctl -- remove interface hv1-vif${di} options tx_pcap + > hv1/vif${di}-tx.pcap + ovs-vsctl -- set interface hv1-vif${di} external-ids:iface-id=lsp${di}1 \ + options:tx_pcap=hv1/vif${di}-tx.pcap + done +done + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) + + +OVN_FOR_EACH_NORTHD([ +AT_SETUP([route tables IPv6 -- overlapping subnets]) +ovn_start + +# Logical network: +# +# ls1 (2001:db8:1::/64) - lrp-lr1-ls1 -\ /- lrp-lr1-ls2 - ls2 (2001:db8:2::/64) +# lr1 +# ls3 (2001:db8:3::/64) - lrp-lr1-ls3 -/ \- lrp-lr1-ls4 - ls4 (2001:db8:4::/64) +# +# ls1 has lsp11 (2001:db8:1::11) +# ls2 has lsp21 (2001:db8:2::21) +# ls3 has lsp31 (2001:db8:3::31) +# ls4 has lsp41 (2001:db8:4::41) +# +# lrp-lr1-ls1 set options:route_table=rtb-1 +# lrp-lr1-ls2 set options:route_table=rtb-2 +# +# Static routes on lr1: +# 2001:db8:2000::/64 nexthop 2001:db8:3::31 route_table=rtb-1 +# 2001:db8:2000::/64 nexthop 2001:db8:3::41 route_table=rtb-2 +# +# Test 1: +# lsp11 send packet to 2001:db8:2000::1 +# +# Expected result: +# lsp31 should receive traffic, lsp41 should not receive any traffic +# +# Test 2: +# lsp21 send packet to 2001:db8:2000::1 +# +# Expected result: +# lsp41 should receive traffic, lsp31 should not receive any traffic + +ovn-nbctl lr-add lr1 + +# Create logical topology +for i in $(seq 1 4); do + ovn-nbctl ls-add ls${i} + ovn-nbctl lrp-add lr1 lrp-lr1-ls${i} 00:00:00:01:0${i}:01 2001:db8:${i}::1/64 + ovn-nbctl lsp-add ls${i} lsp-ls${i}-lr1 -- lsp-set-type lsp-ls${i}-lr1 router \ + -- lsp-set-options lsp-ls${i}-lr1 router-port=lrp-lr1-ls${i} \ + -- lsp-set-addresses lsp-ls${i}-lr1 router + ovn-nbctl lsp-add ls$i lsp${i}1 -- \ + lsp-set-addresses lsp${i}1 "f0:00:00:00:0${i}:1${i} 2001:db8:${i}::${i}1" +done + +# install static routes +ovn-nbctl --route-table=rtb-1 lr-route-add lr1 2001:db8:2000::/64 2001:db8:3::31 +ovn-nbctl --route-table=rtb-2 lr-route-add lr1 2001:db8:2000::/64 2001:db8:4::41 + +# set lrp-lr1-ls{1,2} route tables +ovn-nbctl lrp-set-options lrp-lr1-ls1 route_table=rtb-1 +ovn-nbctl lrp-set-options lrp-lr1-ls2 route_table=rtb-2 + +net_add n1 +sim_add hv1 +as hv1 +ovs-vsctl add-br br-phys +ovn_attach n1 br-phys 192.168.0.1 + +for i in $(seq 1 4); do + ovs-vsctl -- add-port br-int hv1-vif${i} -- \ + set interface hv1-vif${i} external-ids:iface-id=lsp${i}1 \ + options:tx_pcap=hv1/vif${i}-tx.pcap \ + options:rxq_pcap=hv1/vif${i}-rx.pcap \ + ofport-request=${i} +done + +# wait for earlier changes to take effect +AT_CHECK([ovn-nbctl --timeout=3 --wait=hv sync], [0], [ignore]) + +# lsp31 should recieve packet coming from lsp11 +# lsp41 should recieve packet coming from lsp21 +for i in $(seq 1 2); do + di=$(( i + 2)) # dst index + ri=$(( 5 - i)) # reverse index + packet="inport==\"lsp${i}1\" && eth.src==f0:00:00:00:0${i}:1${i} && + eth.dst==00:00:00:01:0${i}:01 && ip6 && ip.ttl==64 && + ip6.src==2001:db8:${i}::${i}1 && ip6.dst==2001:db8:2000::1 && icmp6" + AT_CHECK([as hv1 ovs-appctl -t ovn-controller inject-pkt "$packet"]) + + # Assume all packets go to lsp${di}1. + exp_packet="eth.src==00:00:00:01:0${di}:01 && eth.dst==f0:00:00:00:0${di}:1${di} && ip6 && + ip.ttl==63 && ip6.src==2001:db8:${i}::${i}1 && ip6.dst==2001:db8:2000::1 && icmp6" + echo $exp_packet | ovstest test-ovn expr-to-packets >> expected_lsp${di}1 + > expected_lsp${ri}1 + + OVS_WAIT_UNTIL([ + rcv_n1=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif${di}-tx.pcap > lsp${di}1.packets && cat lsp${di}1.packets | wc -l` + rcv_n2=`$PYTHON "$ovs_srcdir/utilities/ovs-pcap.in" hv1/vif${ri}-tx.pcap > lsp${ri}1.packets && cat lsp${ri}1.packets | wc -l` + echo $rcv_n1 $rcv_n2 + test $rcv_n1 -eq 1 -a $rcv_n2 -eq 0]) + + for j in "${di}1" "${ri}1"; do + sort expected_lsp${j} > expout + AT_CHECK([cat lsp${j}.packets | sort], [0], [expout]) + done + + # cleanup tx pcap files + for j in "${di}1" "${ri}1"; do + ovs-vsctl -- remove interface hv1-vif${di} options tx_pcap + > hv1/vif${di}-tx.pcap + ovs-vsctl -- set interface hv1-vif${di} external-ids:iface-id=lsp${di}1 \ + options:tx_pcap=hv1/vif${di}-tx.pcap + done +done + +OVN_CLEANUP([hv1]) +AT_CLEANUP +]) + + OVN_FOR_EACH_NORTHD([ AT_SETUP([forwarding group: 3 HVs, 1 LR, 2 LS]) AT_KEYWORDS([forwarding-group]) @@ -23453,7 +23985,7 @@ ovn-sbctl dump-flows > sbflows AT_CAPTURE_FILE([sbflows]) AT_CAPTURE_FILE([offlows]) OVS_WAIT_UNTIL([ - as hv1 ovs-ofctl dump-flows br-int table=20 > offlows + as hv1 ovs-ofctl dump-flows br-int table=21 > offlows test $(grep -c "load:0x64->NXM_NX_PKT_MARK" offlows) = 1 && \ test $(grep -c "load:0x3->NXM_NX_PKT_MARK" offlows) = 1 && \ test $(grep -c "load:0x4->NXM_NX_PKT_MARK" offlows) = 1 && \ @@ -23546,12 +24078,12 @@ send_ipv4_pkt hv1 hv1-vif1 505400000003 00000000ff01 \ $(ip_to_hex 10 0 0 3) $(ip_to_hex 172 168 0 120) OVS_WAIT_UNTIL([ - test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ + test 1 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ grep "load:0x2->NXM_NX_PKT_MARK" -c) ]) AT_CHECK([ - test 0 -eq $(as hv1 ovs-ofctl dump-flows br-int table=20 | \ + test 0 -eq $(as hv1 ovs-ofctl dump-flows br-int table=21 | \ grep "load:0x64->NXM_NX_PKT_MARK" -c) ]) @@ -24278,7 +24810,7 @@ AT_CHECK([ grep "priority=100" | \ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" - grep table=22 hv${hv}flows | \ + grep table=23 hv${hv}flows | \ grep "priority=200" | \ grep -c "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" done; :], [0], [dnl @@ -24403,7 +24935,7 @@ AT_CHECK([ grep "priority=100" | \ grep -c "ct(commit,zone=NXM_NX_REG11\\[[0..15\\]],.*exec(move:NXM_OF_ETH_SRC\\[[\\]]->NXM_NX_CT_LABEL\\[[32..79\\]],load:0x[[0-9]]->NXM_NX_CT_LABEL\\[[80..95\\]]))" - grep table=22 hv${hv}flows | \ + grep table=23 hv${hv}flows | \ grep "priority=200" | \ grep -c "actions=move:NXM_NX_CT_LABEL\\[[32..79\\]]->NXM_OF_ETH_DST\\[[\\]]" done; :], [0], [dnl @@ -25025,7 +25557,7 @@ AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep "actions=controller" | grep ]) # The packet should've been dropped in the lr_in_arp_resolve stage. -AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=22, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl +AT_CHECK([as hv1 ovs-ofctl dump-flows br-int | grep -E "table=23, n_packets=1,.* priority=1,ip,metadata=0x${sw_key},nw_dst=10.0.1.1 actions=drop" -c], [0], [dnl 1 ]) diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c index 2a68ccd16..8bdcb19a3 100644 --- a/utilities/ovn-nbctl.c +++ b/utilities/ovn-nbctl.c @@ -329,6 +329,8 @@ Logical router port commands:\n\ add logical port PORT on ROUTER\n\ lrp-set-gateway-chassis PORT CHASSIS [PRIORITY]\n\ set gateway chassis for port PORT\n\ + lrp-set-options PORT KEY=VALUE [KEY=VALUE]...\n\ + set router port options\n\ lrp-del-gateway-chassis PORT CHASSIS\n\ delete gateway chassis from port PORT\n\ lrp-get-gateway-chassis PORT\n\ @@ -351,11 +353,17 @@ Logical router port commands:\n\ ('overlay' or 'bridged')\n\ \n\ Route commands:\n\ - [--policy=POLICY] [--ecmp] [--ecmp-symmetric-reply] lr-route-add ROUTER \n\ - PREFIX NEXTHOP [PORT]\n\ + [--policy=POLICY]\n\ + [--ecmp]\n\ + [--ecmp-symmetric-reply]\n\ + [--route-table=ROUTE_TABLE]\n\ + lr-route-add ROUTER PREFIX NEXTHOP [PORT]\n\ add a route to ROUTER\n\ - [--policy=POLICY] lr-route-del ROUTER [PREFIX [NEXTHOP [PORT]]]\n\ + [--policy=POLICY]\n\ + [--route-table=ROUTE_TABLE]\n\ + lr-route-del ROUTER [PREFIX [NEXTHOP [PORT]]]\n\ remove routes from ROUTER\n\ + [--route-table=ROUTE_TABLE]\n\ lr-route-list ROUTER print routes for ROUTER\n\ \n\ Policy commands:\n\ @@ -743,6 +751,11 @@ print_lr(const struct nbrec_logical_router *lr, struct ds *s) ds_put_cstr(s, "]\n"); } + const char *route_table = smap_get(&lrp->options, "route_table"); + if (route_table) { + ds_put_format(s, " route-table: %s\n", route_table); + } + if (lrp->n_gateway_chassis) { const struct nbrec_gateway_chassis **gcs; @@ -862,6 +875,7 @@ nbctl_pre_show(struct ctl_context *ctx) ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_mac); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_networks); + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_options); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_gateway_chassis); ovsdb_idl_add_column(ctx->idl, &nbrec_gateway_chassis_col_chassis_name); @@ -4020,11 +4034,19 @@ nbctl_lr_policy_list(struct ctl_context *ctx) static struct nbrec_logical_router_static_route * nbctl_lr_get_route(const struct nbrec_logical_router *lr, char *prefix, - char *next_hop, bool is_src_route, bool ecmp) + char *next_hop, bool is_src_route, bool ecmp, + char *route_table) { for (int i = 0; i < lr->n_static_routes; i++) { struct nbrec_logical_router_static_route *route = lr->static_routes[i]; + /* Strict compare for route_table. + * If route_table was not specified, + * lookup for routes with empty route_table value. */ + if (strcmp(route->route_table, route_table ? route_table : "")) { + continue; + } + /* Compare route policy. */ char *nb_policy = route->policy; bool nb_is_src_route = false; @@ -4080,6 +4102,8 @@ nbctl_pre_lr_route_add(struct ctl_context *ctx) &nbrec_logical_router_static_route_col_bfd); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_static_route_col_options); + ovsdb_idl_add_column(ctx->idl, + &nbrec_logical_router_static_route_col_route_table); } static char * OVS_WARN_UNUSED_RESULT @@ -4110,6 +4134,7 @@ nbctl_lr_route_add(struct ctl_context *ctx) } } + char *route_table = shash_find_data(&ctx->options, "--route-table"); bool v6_prefix = false; prefix = normalize_ipv4_prefix_str(ctx->argv[2]); if (!prefix) { @@ -4186,7 +4211,8 @@ nbctl_lr_route_add(struct ctl_context *ctx) bool ecmp = shash_find(&ctx->options, "--ecmp") != NULL || ecmp_symmetric_reply; struct nbrec_logical_router_static_route *route = - nbctl_lr_get_route(lr, prefix, next_hop, is_src_route, ecmp); + nbctl_lr_get_route(lr, prefix, next_hop, is_src_route, ecmp, + route_table); /* Validations for nexthop = "discard" */ if (is_discard_route) { @@ -4250,7 +4276,8 @@ nbctl_lr_route_add(struct ctl_context *ctx) } struct nbrec_logical_router_static_route *discard_route = - nbctl_lr_get_route(lr, prefix, "discard", is_src_route, true); + nbctl_lr_get_route(lr, prefix, "discard", is_src_route, true, + route_table); if (discard_route) { ctl_error(ctx, "discard nexthop for the same ECMP route exists."); goto cleanup; @@ -4266,6 +4293,9 @@ nbctl_lr_route_add(struct ctl_context *ctx) if (policy) { nbrec_logical_router_static_route_set_policy(route, policy); } + if (route_table) { + nbrec_logical_router_static_route_set_route_table(route, route_table); + } if (ecmp_symmetric_reply) { const struct smap options = SMAP_CONST1(&options, @@ -4309,6 +4339,8 @@ nbctl_pre_lr_route_del(struct ctl_context *ctx) &nbrec_logical_router_static_route_col_nexthop); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_static_route_col_output_port); + ovsdb_idl_add_column(ctx->idl, + &nbrec_logical_router_static_route_col_route_table); } @@ -4322,6 +4354,7 @@ nbctl_lr_route_del(struct ctl_context *ctx) return; } + const char *route_table = shash_find_data(&ctx->options, "--route-table"); const char *policy = shash_find_data(&ctx->options, "--policy"); bool is_src_route = false; if (policy) { @@ -4412,6 +4445,14 @@ nbctl_lr_route_del(struct ctl_context *ctx) } } + /* Strict compare for route_table. + * If route_table was not specified, + * lookup for routes with empty route_table value. */ + if (strcmp(lr->static_routes[i]->route_table, + route_table ? route_table : "")) { + continue; + } + /* Compare output_port, if specified. */ if (output_port) { char *rt_output_port = lr->static_routes[i]->output_port; @@ -5135,6 +5176,41 @@ nbctl_pre_lrp_del_gateway_chassis(struct ctl_context *ctx) ovsdb_idl_add_column(ctx->idl, &nbrec_gateway_chassis_col_chassis_name); } +static void +nbctl_pre_lrp_options(struct ctl_context *ctx) +{ + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_name); + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_port_col_options); +} + +static void +nbctl_lrp_set_options(struct ctl_context *ctx) +{ + const char *id = ctx->argv[1]; + const struct nbrec_logical_router_port *lrp = NULL; + size_t i; + struct smap options = SMAP_INITIALIZER(&options); + + char *error = lrp_by_name_or_uuid(ctx, id, true, &lrp); + if (error) { + ctx->error = error; + return; + } + for (i = 2; i < ctx->argc; i++) { + char *key, *value; + value = xstrdup(ctx->argv[i]); + key = strsep(&value, "="); + if (value) { + smap_add(&options, key, value); + } + free(key); + } + + nbrec_logical_router_port_set_options(lrp, &options); + + smap_destroy(&options); +} + /* Removes logical router port 'lrp->gateway_chassis[idx]'. */ static void remove_gc(const struct nbrec_logical_router_port *lrp, size_t idx) @@ -5911,6 +5987,7 @@ route_cmp_details(const struct nbrec_logical_router_static_route *r1, } return r1->output_port ? 1 : -1; } + struct ipv4_route { int priority; ovs_be32 addr; @@ -5920,6 +5997,11 @@ struct ipv4_route { static int __ipv4_route_cmp(const struct ipv4_route *r1, const struct ipv4_route *r2) { + int rtb_cmp = strcmp(r1->route->route_table, + r2->route->route_table); + if (rtb_cmp) { + return rtb_cmp; + } if (r1->priority != r2->priority) { return r1->priority > r2->priority ? -1 : 1; } @@ -5951,6 +6033,11 @@ struct ipv6_route { static int __ipv6_route_cmp(const struct ipv6_route *r1, const struct ipv6_route *r2) { + int rtb_cmp = strcmp(r1->route->route_table, + r2->route->route_table); + if (rtb_cmp) { + return rtb_cmp; + } if (r1->priority != r2->priority) { return r1->priority > r2->priority ? -1 : 1; } @@ -6038,6 +6125,8 @@ nbctl_pre_lr_route_list(struct ctl_context *ctx) &nbrec_logical_router_static_route_col_options); ovsdb_idl_add_column(ctx->idl, &nbrec_logical_router_static_route_col_bfd); + ovsdb_idl_add_column(ctx->idl, + &nbrec_logical_router_static_route_col_route_table); } static void @@ -6055,12 +6144,17 @@ nbctl_lr_route_list(struct ctl_context *ctx) return; } + char *route_table = shash_find_data(&ctx->options, "--route-table"); + ipv4_routes = xmalloc(sizeof *ipv4_routes * lr->n_static_routes); ipv6_routes = xmalloc(sizeof *ipv6_routes * lr->n_static_routes); for (int i = 0; i < lr->n_static_routes; i++) { const struct nbrec_logical_router_static_route *route = lr->static_routes[i]; + if (route_table && strcmp(route->route_table, route_table)) { + continue; + } unsigned int plen; ovs_be32 ipv4; const char *policy = route->policy ? route->policy : "dst-ip"; @@ -6101,6 +6195,7 @@ nbctl_lr_route_list(struct ctl_context *ctx) if (n_ipv4_routes) { ds_put_cstr(&ctx->output, "IPv4 Routes\n"); } + const struct nbrec_logical_router_static_route *route; for (int i = 0; i < n_ipv4_routes; i++) { bool ecmp = false; if (i < n_ipv4_routes - 1 && @@ -6111,6 +6206,15 @@ nbctl_lr_route_list(struct ctl_context *ctx) &ipv4_routes[i - 1])) { ecmp = true; } + + route = ipv4_routes[i].route; + if (!i || (i > 0 && strcmp(route->route_table, + ipv4_routes[i - 1].route->route_table))) { + ds_put_format(&ctx->output, "%sRoute Table %s:\n", i ? "\n" : "", + strlen(route->route_table) ? route->route_table + : "<main>"); + } + print_route(ipv4_routes[i].route, &ctx->output, ecmp); } @@ -6128,6 +6232,15 @@ nbctl_lr_route_list(struct ctl_context *ctx) &ipv6_routes[i - 1])) { ecmp = true; } + + route = ipv6_routes[i].route; + if (!i || (i > 0 && strcmp(route->route_table, + ipv6_routes[i - 1].route->route_table))) { + ds_put_format(&ctx->output, "%sRoute Table %s:\n", i ? "\n" : "", + strlen(route->route_table) ? route->route_table + : "<main>"); + } + print_route(ipv6_routes[i].route, &ctx->output, ecmp); } @@ -6949,6 +7062,8 @@ static const struct ctl_command_syntax nbctl_commands[] = { "PORT CHASSIS [PRIORITY]", nbctl_pre_lrp_set_gateway_chassis, nbctl_lrp_set_gateway_chassis, NULL, "--may-exist", RW }, + { "lrp-set-options", 1, INT_MAX, "PORT KEY=VALUE [KEY=VALUE]...", + nbctl_pre_lrp_options, nbctl_lrp_set_options, NULL, "", RW }, { "lrp-del-gateway-chassis", 2, 2, "PORT CHASSIS", nbctl_pre_lrp_del_gateway_chassis, nbctl_lrp_del_gateway_chassis, NULL, "", RW }, @@ -6972,12 +7087,13 @@ static const struct ctl_command_syntax nbctl_commands[] = { /* logical router route commands. */ { "lr-route-add", 3, 4, "ROUTER PREFIX NEXTHOP [PORT]", nbctl_pre_lr_route_add, nbctl_lr_route_add, NULL, - "--may-exist,--ecmp,--ecmp-symmetric-reply,--policy=,--bfd?", RW }, + "--may-exist,--ecmp,--ecmp-symmetric-reply,--policy=,--route-table=,--bfd?", + RW }, { "lr-route-del", 1, 4, "ROUTER [PREFIX [NEXTHOP [PORT]]]", nbctl_pre_lr_route_del, nbctl_lr_route_del, NULL, - "--if-exists,--policy=", RW }, + "--if-exists,--policy=,--route-table=", RW }, { "lr-route-list", 1, 1, "ROUTER", nbctl_pre_lr_route_list, - nbctl_lr_route_list, NULL, "", RO }, + nbctl_lr_route_list, NULL, "--route-table=", RO }, /* Policy commands */ { "lr-policy-add", 4, INT_MAX, -- 2.30.0 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev