diff --git a/ovn/lib/ovn-util.c b/ovn/lib/ovn-util.c
index f732b851ae9..40299176151 100644
--- a/ovn/lib/ovn-util.c
+++ b/ovn/lib/ovn-util.c
@@ -15,20 +15,59 @@
#include
-ip4.dst = ip4.src; -ip4.src = S; +ip4.dst <-> ip4.src; ip.ttl = 255; icmp4.type = 0; inport = ""; /* Allow sending out inport. */ @@ -891,6 +889,7 @@ reg0 = G; reg1 = A; eth.src = E; outport = P; +inport = ""; /* Allow sending out inport. */ next;@@ -956,7 +955,7 @@ icmp4 { from the
addresses
column in the
Logical_Switch_Port
table. For router ports
connected to other logical routers, MAC bindings can be known
- statically from the mac
and network
+ statically from the mac
and networks
column in the Logical_Router_Port
table.
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 26a95269283..788d614bb52 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -494,16 +494,8 @@ struct ovn_port {
/* Logical router port data. */
const struct nbrec_logical_router_port *nbr; /* May be NULL. */
- char *ip_s; /* "192.168.10.123" */
- char *network_s; /* "192.168.10.0" */
- char *bcast_s; /* "192.168.10.255" */
- int plen; /* CIDR prefix: 24 */
+ struct lport_addresses lrp_networks;
- ovs_be32 ip; /* 192.168.10.123 */
- ovs_be32 mask; /* 255.255.255.0 */
- ovs_be32 network; /* 192.168.10.255 */
-
- struct eth_addr mac;
struct ovn_port *peer;
struct ovn_datapath *od;
@@ -550,9 +542,7 @@ ovn_port_destroy(struct hmap *ports, struct ovn_port *port)
}
free(port->ps_addrs);
- free(port->bcast_s);
- free(port->network_s);
- free(port->ip_s);
+ destroy_lport_addresses(&port->lrp_networks);
free(port->json_key);
free(port->key);
free(port);
@@ -661,24 +651,17 @@ join_logical_ports(struct northd_context *ctx,
}
} else {
for (size_t i = 0; i < od->nbr->n_ports; i++) {
- const struct nbrec_logical_router_port *nbr
- = od->nbr->ports[i];
+ const struct nbrec_logical_router_port *nbr = od->nbr->ports[i];
- struct eth_addr mac;
- if (!eth_addr_from_string(nbr->mac, &mac)) {
+ struct lport_addresses lrp_networks;
+ if (!extract_lrp_networks(nbr, &lrp_networks)) {
static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(5, 1);
VLOG_WARN_RL(&rl, "bad 'mac' %s", nbr->mac);
continue;
}
- ovs_be32 ip, mask;
- char *error = ip_parse_masked(nbr->network, &ip, &mask);
- if (error || mask == OVS_BE32_MAX || !ip_is_cidr(mask)) {
- static struct vlog_rate_limit rl
- = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "bad 'network' %s", nbr->network);
- free(error);
+ if (!lrp_networks.n_ipv4_addrs && !lrp_networks.n_ipv6_addrs) {
continue;
}
@@ -694,21 +677,17 @@ join_logical_ports(struct northd_context *ctx,
op->nbr = nbr;
ovs_list_remove(&op->list);
ovs_list_push_back(both, &op->list);
+
+ /* This port exists but should not have been
+ * initialized fully. */
+ ovs_assert(!op->lrp_networks.n_ipv4_addrs
+ && !op->lrp_networks.n_ipv6_addrs);
} else {
op = ovn_port_create(ports, nbr->name, NULL, nbr, NULL);
ovs_list_push_back(nb_only, &op->list);
}
- op->ip = ip;
- op->mask = mask;
- op->network = ip & mask;
- op->plen = ip_count_cidr_bits(mask);
-
- op->ip_s = xasprintf(IP_FMT, IP_ARGS(ip));
- op->network_s = xasprintf(IP_FMT, IP_ARGS(op->network));
- op->bcast_s = xasprintf(IP_FMT, IP_ARGS(ip | ~mask));
-
- op->mac = mac;
+ op->lrp_networks = lrp_networks;
op->od = od;
}
}
@@ -2048,9 +2027,40 @@ lrport_is_enabled(const struct nbrec_logical_router_port *lrport)
return !lrport->enabled || *lrport->enabled;
}
+/* Returns a string of the IP address of the router port 'op' that
+ * overlaps with 'ip_s". If one is not found, returns NULL.
+ *
+ * The caller must not free the returned string. */
+static const char *
+find_lrp_member_ip(const struct ovn_port *op, const char *ip_s)
+{
+ uint32_t ip;
+
+ if (!ip_parse(ip_s, &ip)) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "bad ip address %s", ip_s);
+ return NULL;
+ }
+
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ const struct ipv4_netaddr *na = &op->lrp_networks.ipv4_addrs[i];
+
+ if (!((na->network ^ ip) & na->mask)) {
+ /* There should be only 1 interface that matches the
+ * next hop. Otherwise, it's a configuration error,
+ * because subnets of router's interfaces should NOT
+ * overlap. */
+ return na->addr_s;
+ }
+ }
+
+ return NULL;
+}
+
static void
add_route(struct hmap *lflows, const struct ovn_port *op,
- const char *network_s, int plen, const char *gateway)
+ const char *lrp_addr_s, const char *network_s, int plen,
+ const char *gateway)
{
char *match = xasprintf("ip4.dst == %s/%d", network_s, plen);
@@ -2061,13 +2071,15 @@ add_route(struct hmap *lflows, const struct ovn_port *op,
} else {
ds_put_cstr(&actions, "ip4.dst");
}
- ds_put_format(&actions,
- "; "
+ ds_put_format(&actions, "; "
"reg1 = %s; "
- "eth.src = "ETH_ADDR_FMT"; "
+ "eth.src = %s; "
"outport = %s; "
+ "inport = \"\"; /* Allow sending out inport. */ "
"next;",
- op->ip_s, ETH_ADDR_ARGS(op->mac), op->json_key);
+ lrp_addr_s,
+ op->lrp_networks.ea_s,
+ op->json_key);
/* The priority here is calculated to implement longest-prefix-match
* routing. */
@@ -2082,10 +2094,11 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
struct hmap *ports,
const struct nbrec_logical_router_static_route *route)
{
- ovs_be32 prefix, next_hop, mask;
+ ovs_be32 prefix, nexthop, mask;
+ const char *lrp_addr_s;
/* Verify that next hop is an IP address with 32 bits mask. */
- char *error = ip_parse_masked(route->nexthop, &next_hop, &mask);
+ char *error = ip_parse_masked(route->nexthop, &nexthop, &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);
@@ -2113,6 +2126,7 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
route->output_port, route->ip_prefix);
return;
}
+ lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
} else {
/* output_port is not specified, find the
* router port matching the next hop. */
@@ -2125,29 +2139,47 @@ build_static_route_flow(struct hmap *lflows, struct ovn_datapath *od,
continue;
}
- if (out_port->network
- && !((out_port->network ^ next_hop) & out_port->mask)) {
- /* There should be only 1 interface that matches the next hop.
- * Otherwise, it's a configuration error, because subnets of
- * router's interfaces should NOT overlap. */
+ lrp_addr_s = find_lrp_member_ip(out_port, route->nexthop);
+ if (lrp_addr_s) {
break;
}
}
- if (i == od->nbr->n_ports) {
- /* There is no matched out port. */
- static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
- VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s",
- route->ip_prefix, route->nexthop);
- return;
- }
}
- char *prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix));
- add_route(lflows, out_port, prefix_s, ip_count_cidr_bits(mask),
- route->nexthop);
+ if (!lrp_addr_s) {
+ /* There is no matched out port. */
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
+ VLOG_WARN_RL(&rl, "No path for static route %s; next hop %s",
+ route->ip_prefix, route->nexthop);
+ return;
+ }
+
+ char *prefix_s = xasprintf(IP_FMT, IP_ARGS(prefix & mask));
+ add_route(lflows, out_port, lrp_addr_s, prefix_s,
+ ip_count_cidr_bits(mask), route->nexthop);
free(prefix_s);
}
+static void
+op_put_networks(struct ds *ds, const struct ovn_port *op, bool add_bcast)
+{
+ if (!add_bcast && op->lrp_networks.n_ipv4_addrs == 1) {
+ ds_put_format(ds, "%s", op->lrp_networks.ipv4_addrs[0].addr_s);
+ return;
+ }
+
+ ds_put_cstr(ds, "{");
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].addr_s);
+ if (add_bcast) {
+ ds_put_format(ds, "%s, ", op->lrp_networks.ipv4_addrs[i].bcast_s);
+ }
+ }
+ ds_chomp(ds, ' ');
+ ds_chomp(ds, ',');
+ ds_put_cstr(ds, "}");
+}
+
static void
build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
struct hmap *lflows)
@@ -2185,9 +2217,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
}
ds_clear(&match);
- ds_put_format(&match,
- "(eth.mcast || eth.dst == "ETH_ADDR_FMT") && inport == %s",
- ETH_ADDR_ARGS(op->mac), op->json_key);
+ ds_put_format(&match, "(eth.mcast || eth.dst == %s) && inport == %s",
+ op->lrp_networks.ea_s, op->json_key);
ovn_lflow_add(lflows, op->od, S_ROUTER_IN_ADMISSION, 50,
ds_cstr(&match), "next;");
}
@@ -2246,7 +2277,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
* owned by the router or a broadcast address known to the router
* (priority 100). */
ds_clear(&match);
- ds_put_format(&match, "ip4.src == {%s, %s}", op->ip_s, op->bcast_s);
+ ds_put_cstr(&match, "ip4.src == ");
+ op_put_networks(&match, op, true);
ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 100,
ds_cstr(&match), "drop;");
@@ -2256,44 +2288,47 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
* (i.e. the incoming locally attached net) does not matter.
* The ip.ttl also does not matter (RFC1812 section 4.2.2.9) */
ds_clear(&match);
- ds_put_format(&match,
- "ip4.dst == %s && icmp4.type == 8 && icmp4.code == 0",
- op->ip_s);
+ ds_put_cstr(&match, "ip4.dst == ");
+ op_put_networks(&match, op, false);
+ ds_put_cstr(&match, " && icmp4.type == 8 && icmp4.code == 0");
+
ds_clear(&actions);
ds_put_format(&actions,
- "ip4.dst = ip4.src; "
- "ip4.src = %s; "
+ "ip4.dst <-> ip4.src; "
"ip.ttl = 255; "
"icmp4.type = 0; "
"inport = \"\"; /* Allow sending out inport. */ "
- "next; ",
- op->ip_s);
+ "next; ");
ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
ds_cstr(&match), ds_cstr(&actions));
/* ARP reply. These flows reply to ARP requests for the router's own
* IP address. */
- ds_clear(&match);
- ds_put_format(&match, "inport == %s && arp.tpa == %s && arp.op == 1",
- op->json_key, op->ip_s);
- ds_clear(&actions);
- ds_put_format(&actions,
- "eth.dst = eth.src; "
- "eth.src = "ETH_ADDR_FMT"; "
- "arp.op = 2; /* ARP reply */ "
- "arp.tha = arp.sha; "
- "arp.sha = "ETH_ADDR_FMT"; "
- "arp.tpa = arp.spa; "
- "arp.spa = %s; "
- "outport = %s; "
- "inport = \"\"; /* Allow sending out inport. */ "
- "output;",
- ETH_ADDR_ARGS(op->mac),
- ETH_ADDR_ARGS(op->mac),
- op->ip_s,
- op->json_key);
- ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
- ds_cstr(&match), ds_cstr(&actions));
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ ds_clear(&match);
+ ds_put_format(&match,
+ "inport == %s && arp.tpa == %s && arp.op == 1",
+ op->json_key, op->lrp_networks.ipv4_addrs[i].addr_s);
+
+ ds_clear(&actions);
+ ds_put_format(&actions,
+ "eth.dst = eth.src; "
+ "eth.src = %s; "
+ "arp.op = 2; /* ARP reply */ "
+ "arp.tha = arp.sha; "
+ "arp.sha = %s; "
+ "arp.tpa = arp.spa; "
+ "arp.spa = %s; "
+ "outport = %s; "
+ "inport = \"\"; /* Allow sending out inport. */ "
+ "output;",
+ op->lrp_networks.ea_s,
+ op->lrp_networks.ea_s,
+ op->lrp_networks.ipv4_addrs[i].addr_s,
+ op->json_key);
+ ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
+ ds_cstr(&match), ds_cstr(&actions));
+ }
/* ARP handling for external IP addresses.
*
@@ -2320,20 +2355,21 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
ds_put_format(&match,
"inport == %s && arp.tpa == "IP_FMT" && arp.op == 1",
op->json_key, IP_ARGS(ip));
+
ds_clear(&actions);
ds_put_format(&actions,
"eth.dst = eth.src; "
- "eth.src = "ETH_ADDR_FMT"; "
+ "eth.src = %s; "
"arp.op = 2; /* ARP reply */ "
"arp.tha = arp.sha; "
- "arp.sha = "ETH_ADDR_FMT"; "
+ "arp.sha = %s; "
"arp.tpa = arp.spa; "
"arp.spa = "IP_FMT"; "
"outport = %s; "
"inport = \"\"; /* Allow sending out inport. */ "
"output;",
- ETH_ADDR_ARGS(op->mac),
- ETH_ADDR_ARGS(op->mac),
+ op->lrp_networks.ea_s,
+ op->lrp_networks.ea_s,
IP_ARGS(ip),
op->json_key);
ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90,
@@ -2342,7 +2378,8 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
/* Drop IP traffic to this router, unless the router ip is used as
* SNAT ip. */
- bool snat_ip_is_router_ip = false;
+ ovs_be32 *nat_ips = xmalloc(sizeof *nat_ips * op->od->nbr->n_nat);
+ size_t n_nat_ips = 0;
for (int i = 0; i < op->od->nbr->n_nat; i++) {
const struct nbrec_nat *nat;
ovs_be32 ip;
@@ -2359,18 +2396,33 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- if (ip == op->ip) {
- snat_ip_is_router_ip = true;
- break;
+ nat_ips[n_nat_ips++] = ip;
+ }
+
+ ds_clear(&match);
+ ds_put_cstr(&match, "ip4.dst == {");
+ bool has_drop_ips = false;
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ for (int j = 0; j < n_nat_ips; j++) {
+ if (op->lrp_networks.ipv4_addrs[i].addr == nat_ips[j]) {
+ continue;
+ }
}
+ ds_put_format(&match, "%s, ",
+ op->lrp_networks.ipv4_addrs[i].addr_s);
+ has_drop_ips = true;
}
+ ds_chomp(&match, ' ');
+ ds_chomp(&match, ',');
+ ds_put_cstr(&match, "}");
- if (!snat_ip_is_router_ip) {
- ds_clear(&match);
- ds_put_format(&match, "ip4.dst == %s", op->ip_s);
+ if (has_drop_ips) {
+ /* Drop IP traffic to this router. */
ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 60,
ds_cstr(&match), "drop;");
}
+
+ free(nat_ips);
}
/* NAT in Gateway routers. */
@@ -2512,8 +2564,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- add_route(lflows, op, op->network_s, op->plen, NULL);
+ for (int i = 0; i < op->lrp_networks.n_ipv4_addrs; i++) {
+ add_route(lflows, 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);
+ }
}
+
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbr) {
continue;
@@ -2550,15 +2607,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- if (!peer->ip || !op->ip) {
- continue;
- }
ds_clear(&match);
- ds_put_format(&match, "outport == %s && reg0 == %s",
- peer->json_key, op->ip_s);
+ ds_put_format(&match, "outport == %s && reg0 == ",
+ peer->json_key);
+ op_put_networks(&match, op, false);
+
ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = "ETH_ADDR_FMT"; next;",
- ETH_ADDR_ARGS(op->mac));
+ ds_put_format(&actions, "eth.dst = %s; next;",
+ op->lrp_networks.ea_s);
ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
100, ds_cstr(&match), ds_cstr(&actions));
}
@@ -2570,8 +2626,9 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
* router port, add an ARP entry in that router's pipeline. */
for (size_t i = 0; i < op->n_lsp_addrs; i++) {
+ const char *ea_s = op->lsp_addrs[i].ea_s;
for (size_t j = 0; j < op->lsp_addrs[i].n_ipv4_addrs; j++) {
- ovs_be32 ip = op->lsp_addrs[i].ipv4_addrs[j].addr;
+ const char *ip_s = op->lsp_addrs[i].ipv4_addrs[j].addr_s;
for (size_t k = 0; k < op->od->n_router_ports; k++) {
/* Get the Logical_Router_Port that the
* Logical_Switch_Port is connected to, as
@@ -2588,22 +2645,19 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- /* Make sure that 'ip' is in 'peer''s network. */
- if ((ip ^ peer->network) & peer->mask) {
+ if (!find_lrp_member_ip(peer, ip_s)) {
continue;
}
ds_clear(&match);
ds_put_format(&match, "outport == %s && reg0 == %s",
- peer->json_key,
- op->lsp_addrs[i].ipv4_addrs[j].addr_s);
+ peer->json_key, ip_s);
+
ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = %s; next;",
- op->lsp_addrs[i].ea_s);
+ ds_put_format(&actions, "eth.dst = %s; next;", ea_s);
ovn_lflow_add(lflows, peer->od,
S_ROUTER_IN_ARP_RESOLVE, 100,
ds_cstr(&match), ds_cstr(&actions));
- break;
}
}
}
@@ -2622,17 +2676,17 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
}
struct ovn_port *peer = ovn_port_find(ports, peer_name);
- if (!peer || !peer->nbr || !peer->ip) {
+ if (!peer || !peer->nbr) {
continue;
}
- for (size_t j = 0; j < op->od->n_router_ports; j++) {
+ for (size_t i = 0; i < op->od->n_router_ports; i++) {
const char *router_port_name = smap_get(
- &op->od->router_ports[j]->nbs->options,
+ &op->od->router_ports[i]->nbs->options,
"router-port");
struct ovn_port *router_port = ovn_port_find(ports,
router_port_name);
- if (!router_port || !router_port->nbr || !router_port->ip) {
+ if (!router_port || !router_port->nbr) {
continue;
}
@@ -2641,15 +2695,14 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports,
continue;
}
- if (!router_port->ip) {
- continue;
- }
ds_clear(&match);
- ds_put_format(&match, "outport == %s && reg0 == %s",
- peer->json_key, router_port->ip_s);
+ ds_put_format(&match, "outport == %s && reg0 == ",
+ peer->json_key);
+ op_put_networks(&match, router_port, false);
+
ds_clear(&actions);
- ds_put_format(&actions, "eth.dst = "ETH_ADDR_FMT"; next;",
- ETH_ADDR_ARGS(router_port->mac));
+ ds_put_format(&actions, "eth.dst = %s; next;",
+ router_port->lrp_networks.ea_s);
ovn_lflow_add(lflows, peer->od, S_ROUTER_IN_ARP_RESOLVE,
100, ds_cstr(&match), ds_cstr(&actions));
}
diff --git a/ovn/ovn-nb.ovsschema b/ovn/ovn-nb.ovsschema
index 5102948b7df..460d5bdbfc9 100644
--- a/ovn/ovn-nb.ovsschema
+++ b/ovn/ovn-nb.ovsschema
@@ -1,7 +1,7 @@
{
"name": "OVN_Northbound",
- "version": "4.0.0",
- "cksum": "2156178478 7460",
+ "version": "5.0.0",
+ "cksum": "849073644 7576",
"tables": {
"Logical_Switch": {
"columns": {
@@ -123,7 +123,9 @@
"Logical_Router_Port": {
"columns": {
"name": {"type": "string"},
- "network": {"type": "string"},
+ "networks": {"type": {"key": "string",
+ "min": 1,
+ "max": "unlimited"}},
"mac": {"type": "string"},
"peer": {"type": {"key": "string", "min": 0, "max": 1}},
"enabled": {"type": {"key": "boolean", "min": 0, "max": 1}},
diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml
index 1eeec22a9e8..e571eeb6bd4 100644
--- a/ovn/ovn-nb.xml
+++ b/ovn/ovn-nb.xml
@@ -794,11 +794,13 @@
- 192.168.0.1/24
indicates that the router's IP address is
- 192.168.0.1 and that packets destined to 192.168.0.x should be
- routed to this port.
+
+ The IP addresses and netmasks of the router. For example,
+ 192.168.0.1/24
indicates that the router's IP
+ address is 192.168.0.1 and that packets destined to
+ 192.168.0.x should be routed to this port.
+
--may-exist
] lrp-add
router port mac network [peer]--may-exist
] lrp-add
router port mac network... [peer=
peer]Creates on router a new logical router port named - port with Ethernet address mac and IP - address/netmask network. If peer is - specified, it identifies a logical router port that connects - to this one. + port with Ethernet address mac and one + or more IP address/netmask for each network. +
+ +
+ The optional argument peer
identifies a logical
+ router port that connects to this one. The following example
+ adds a router port with an IPv4 and IPv6 address with peer
+ lr1
:
+
+ lrp-add lr0 lrp0 00:11:22:33:44:55 192.168.0.1/24 2001:db8::1/64 peer=lr1
diff --git a/ovn/utilities/ovn-nbctl.c b/ovn/utilities/ovn-nbctl.c index ad70a05ec95..375e4ccc95b 100644 --- a/ovn/utilities/ovn-nbctl.c +++ b/ovn/utilities/ovn-nbctl.c @@ -29,6 +29,7 @@ #include "poll-loop.h" #include "process.h" #include "smap.h" +#include "sset.h" #include "stream.h" #include "stream-ssl.h" #include "svec.h" @@ -340,7 +341,7 @@ Logical router commands:\n\ lr-list print the names of all logical routers\n\ \n\ Logical router port commands:\n\ - lrp-add ROUTER PORT MAC NETWORK [PEER]\n\ + lrp-add ROUTER PORT MAC NETWORK... [peer=PEER]\n\ add logical port PORT on ROUTER\n\ lrp-del PORT delete PORT from its attached router\n\ lrp-list ROUTER print the names of all ports on ROUTER\n\ @@ -1527,8 +1528,22 @@ nbctl_lrp_add(struct ctl_context *ctx) const char *lrp_name = ctx->argv[2]; const char *mac = ctx->argv[3]; - const char *network = ctx->argv[4]; - const char *peer = (ctx->argc == 6) ? ctx->argv[5] : NULL; + const char **networks = (const char **) &ctx->argv[4]; + + int n_networks = ctx->argc - 4; + for (int i = 4; i < ctx->argc; i++) { + if (strchr(ctx->argv[i], '=')) { + n_networks = i - 4; + break; + } + } + + if (!n_networks) { + ctl_fatal("%s: router port requires specifying a network", lrp_name); + } + + char **settings = (char **) &ctx->argv[n_networks + 4]; + int n_settings = ctx->argc - 4 - n_networks; const struct nbrec_logical_router_port *lrp; lrp = lrp_by_name_or_uuid(ctx, lrp_name, false); @@ -1551,9 +1566,29 @@ nbctl_lrp_add(struct ctl_context *ctx) lrp->mac); } - if (strcmp(network, lrp->network)) { - ctl_fatal("%s: port already exists with network %s", lrp_name, - lrp->network); + struct sset new_networks = SSET_INITIALIZER(&new_networks); + for (int i = 0; i < n_networks; i++) { + sset_add(&new_networks, networks[i]); + } + + struct sset orig_networks = SSET_INITIALIZER(&orig_networks); + sset_add_array(&orig_networks, lrp->networks, lrp->n_networks); + + if (!sset_equals(&orig_networks, &new_networks)) { + ctl_fatal("%s: port already exists with different network", + lrp_name); + } + + sset_destroy(&orig_networks); + sset_destroy(&new_networks); + + /* Special-case sanity-check of peer ports. */ + const char *peer = NULL; + for (int i = 0; i < n_settings; i++) { + if (!strncmp(settings[i], "peer=", 5)) { + peer = settings[i] + 5; + break; + } } if ((!peer != !lrp->peer) || @@ -1567,19 +1602,22 @@ nbctl_lrp_add(struct ctl_context *ctx) struct eth_addr ea; if (!ovs_scan(mac, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(ea))) { - ctl_fatal("%s: invalid mac address.", mac); + ctl_fatal("%s: invalid mac address %s", lrp_name, mac); } - ovs_be32 ipv4; - unsigned int plen; - char *error = ip_parse_cidr(network, &ipv4, &plen); - if (error) { - free(error); - struct in6_addr ipv6; - error = ipv6_parse_cidr(network, &ipv6, &plen); + for (int i = 0; i < n_networks; i++) { + ovs_be32 ipv4; + unsigned int plen; + char *error = ip_parse_cidr(networks[i], &ipv4, &plen); if (error) { free(error); - ctl_fatal("%s: invalid network address.", network); + struct in6_addr ipv6; + error = ipv6_parse_cidr(networks[i], &ipv6, &plen); + if (error) { + free(error); + ctl_fatal("%s: invalid network address: %s", lrp_name, + networks[i]); + } } } @@ -1587,9 +1625,11 @@ nbctl_lrp_add(struct ctl_context *ctx) lrp = nbrec_logical_router_port_insert(ctx->txn); nbrec_logical_router_port_set_name(lrp, lrp_name); nbrec_logical_router_port_set_mac(lrp, mac); - nbrec_logical_router_port_set_network(lrp, network); - if (peer) { - nbrec_logical_router_port_set_peer(lrp, peer); + nbrec_logical_router_port_set_networks(lrp, networks, n_networks); + + for (int i = 0; i < n_settings; i++) { + ctl_set_column("Logical_Router_Port", &lrp->header_, settings[i], + ctx->symtab); } /* Insert the logical port into the logical router. */ @@ -2133,9 +2173,10 @@ static const struct ctl_command_syntax nbctl_commands[] = { { "lr-list", 0, 0, "", NULL, nbctl_lr_list, NULL, "", RO }, /* logical router port commands. */ - { "lrp-add", 4, 5, "ROUTER PORT MAC NETWORK [PEER]", NULL, nbctl_lrp_add, - NULL, "--may-exist", RW }, - { "lrp-del", 1, 1, "LPORT", NULL, nbctl_lrp_del, NULL, "--if-exists", RW }, + { "lrp-add", 4, INT_MAX, + "ROUTER PORT MAC NETWORK... [COLUMN[:KEY]=VALUE]...", + NULL, nbctl_lrp_add, NULL, "--may-exist", RW }, + { "lrp-del", 1, 1, "PORT", NULL, nbctl_lrp_del, NULL, "--if-exists", RW }, { "lrp-list", 1, 1, "ROUTER", NULL, nbctl_lrp_list, NULL, "", RO }, { "lrp-set-enabled", 2, 2, "PORT STATE", NULL, nbctl_lrp_set_enabled, NULL, "", RW }, diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at index 0c756ed41a9..c666022a0ee 100644 --- a/tests/ovn-nbctl.at +++ b/tests/ovn-nbctl.at @@ -305,7 +305,7 @@ AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl <0> (lrp0) ]) -AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 lrp1-peer]) +AT_CHECK([ovn-nbctl lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer]) AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl <0> (lrp0) <1> (lrp1) @@ -328,13 +328,25 @@ AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/2 [ovn-nbctl: lrp1: port already exists with mismatching peer ]) -AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 lrp1-peer]) +AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 peer=lrp1-peer], [1], [], + [ovn-nbctl: lrp1: port already exists with different network +]) + +AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 peer=lrp1-peer]) AT_CHECK([ovn-nbctl lrp-del lrp1]) AT_CHECK([ovn-nbctl lrp-list lr0 | ${PERL} $srcdir/uuidfilt.pl], [0], [dnl <0> (lrp0) ]) +AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 10.0.0.1/24 peer=lrp1-peer]) + +AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 192.168.1.1/24 172.16.0.1/24 peer=lrp1-peer], [1], [], + [ovn-nbctl: lrp1: port already exists with different network +]) + +AT_CHECK([ovn-nbctl --may-exist lrp-add lr0 lrp1 00:00:00:01:02:03 10.0.0.1/24 192.168.1.1/24 peer=lrp1-peer]) + OVN_NBCTL_TEST_STOP AT_CLEANUP diff --git a/tests/ovn.at b/tests/ovn.at index fdac600e02a..12de125f3e1 100644 --- a/tests/ovn.at +++ b/tests/ovn.at @@ -1508,8 +1508,8 @@ done ovn-nbctl lr-add lr0 for i in 1 2 3; do for j in 1 2 3; do - ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j \ - 192.168.$i$j.254/24 lrp$i$j-attachment + ovn-nbctl lrp-add lr0 lrp$i$j 00:00:00:00:ff:$i$j 192.168.$i$j.254/24 \ + peer=lrp$i$j-attachment ovn-nbctl \ -- lsp-add ls$i lrp$i$j-attachment \ -- set Logical_Switch_Port lrp$i$j-attachment type=router \ @@ -2305,20 +2305,20 @@ ovn-nbctl ls-add ls1 ovn-nbctl ls-add ls2 # Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 rp-ls1 +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 peer=rp-ls1 ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ options:router-port=ls1 addresses=\"00:00:00:01:02:03\" # Connect ls2 to R2 -ovn-nbctl lrp-add R2 ls2 00:00:00:01:02:04 172.16.1.1/24 rp-ls2 +ovn-nbctl lrp-add R2 ls2 00:00:00:01:02:04 172.16.1.1/24 peer=rp-ls2 ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \ options:router-port=ls2 addresses=\"00:00:00:01:02:04\" # Connect R1 to R2 -ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 R2_R1 -ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 R1_R2 +ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1 +ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2 ovn-nbctl lr-route-add R1 "0.0.0.0/0" 20.0.0.2 ovn-nbctl lr-route-add R2 "0.0.0.0/0" 20.0.0.1 @@ -2416,7 +2416,138 @@ OVN_CLEANUP([hv1],[hv2]) AT_CLEANUP -AT_SETUP([ovn -- 1 HVs, 2 LSs, 1 lport/LS, 1 LR]) +AT_SETUP([ovn -- 1 HV, 1 LS, 2 lport/LS, 1 LR]) +AT_KEYWORDS([router-admin-state]) +AT_SKIP_IF([test $HAVE_PYTHON = no]) +ovn_start + +# Logical network: +# One LR - R1 has switch ls1 with two subnets attached to it (191.168.1.0/24 +# and 172.16.1.0/24) connected to it. + +ovn-nbctl lr-add R1 + +ovn-nbctl ls-add ls1 + +# Connect ls1 to R1 +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 172.16.1.1/24 \ + peer=rp-ls1 +ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ + options:router-port=ls1 addresses=\"00:00:00:01:02:03\" + +# Create logical port ls1-lp1 in ls1 +ovn-nbctl lsp-add ls1 ls1-lp1 \ + -- lsp-set-addresses ls1-lp1 "f0:00:00:01:02:03 192.168.1.2" + +# Create logical port ls1-lp2 in ls1 +ovn-nbctl lsp-add ls1 ls1-lp2 \ + -- lsp-set-addresses ls1-lp2 "f0:00:00:01:02:04 172.16.1.2" + +# Create one 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 vif1 -- \ + set interface vif1 external-ids:iface-id=ls1-lp1 \ + options:tx_pcap=hv1/vif1-tx.pcap \ + options:rxq_pcap=hv1/vif1-rx.pcap \ + ofport-request=1 + +ovs-vsctl -- add-port br-int vif2 -- \ + set interface vif2 external-ids:iface-id=ls1-lp2 \ + options:tx_pcap=hv1/vif2-tx.pcap \ + options:rxq_pcap=hv1/vif2-rx.pcap \ + ofport-request=1 + + +# Allow some time for ovn-northd and ovn-controller to catch up. +# XXX This should be more systematic. +sleep 1 + +# Send ip packets between the two ports. +ip_to_hex() { + printf "%02x%02x%02x%02x" "$@" +} +trim_zeros() { + sed 's/\(00\)\{1,\}$//' +} + +# Packet to send. +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 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 logical_flow +echo "---------------------" + +echo "------ hv1 dump ----------" +as hv1 ovs-ofctl dump-flows br-int + + +#Disable router R1 +ovn-nbctl set Logical_Router R1 enabled=false + +echo "---------SB dump-----" +ovn-sbctl list datapath_binding +echo "---------------------" +ovn-sbctl list logical_flow +echo "---------------------" + +echo "------ hv1 dump ----------" +as hv1 ovs-ofctl dump-flows br-int + +as hv1 ovs-appctl netdev-dummy/receive vif1 $packet + +# Packet to Expect +expect_src_mac="000000010203" +expect_dst_mac="f00000010204" +expected=${expect_dst_mac}${expect_src_mac}08004500001c000000003f110100${src_ip}${dst_ip}0035111100080000 + +$PYTHON "$top_srcdir/utilities/ovs-pcap.in" hv1/vif2-tx.pcap | trim_zeros > received.packets +echo $expected | trim_zeros > expout +AT_CHECK([cat received.packets], [0], [expout]) + + +as hv1 +OVS_APP_EXIT_AND_WAIT([ovn-controller]) +OVS_APP_EXIT_AND_WAIT([ovs-vswitchd]) +OVS_APP_EXIT_AND_WAIT([ovsdb-server]) + +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 + + +AT_SETUP([ovn -- 1 HV, 2 LSs, 1 lport/LS, 1 LR]) AT_KEYWORDS([router-admin-state]) AT_SKIP_IF([test $HAVE_PYTHON = no]) ovn_start @@ -2431,12 +2562,12 @@ ovn-nbctl ls-add ls1 ovn-nbctl ls-add ls2 # Connect ls1 to R1 -ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 rp-ls1 +ovn-nbctl lrp-add R1 ls1 00:00:00:01:02:03 192.168.1.1/24 peer=rp-ls1 ovn-nbctl lsp-add ls1 rp-ls1 -- set Logical_Switch_Port rp-ls1 type=router \ options:router-port=ls1 addresses=\"00:00:00:01:02:03\" # Connect ls2 to R1 -ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24 rp-ls2 +ovn-nbctl lrp-add R1 ls2 00:00:00:01:02:04 172.16.1.1/24 peer=rp-ls2 ovn-nbctl lsp-add ls2 rp-ls2 -- set Logical_Switch_Port rp-ls2 type=router \ options:router-port=ls2 addresses=\"00:00:00:01:02:04\" @@ -2506,7 +2637,6 @@ echo "---------------------" echo "------ hv1 dump ----------" as hv1 ovs-ofctl dump-flows br-int - #Disable router R1 ovn-nbctl set Logical_Router R1 enabled=false @@ -2558,23 +2688,23 @@ ovn-nbctl ls-add alice ovn-nbctl ls-add bob # Connect foo to R1 -ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 rp-foo +ovn-nbctl lrp-add R1 foo 00:00:00:01:02:03 192.168.1.1/24 peer=rp-foo ovn-nbctl lsp-add foo rp-foo -- set Logical_Switch_Port rp-foo type=router \ options:router-port=foo addresses=\"00:00:00:01:02:03\" # Connect alice to R2 -ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24 rp-alice +ovn-nbctl lrp-add R2 alice 00:00:00:01:02:04 172.16.1.1/24 peer=rp-alice ovn-nbctl lsp-add alice rp-alice -- set Logical_Switch_Port rp-alice \ type=router options:router-port=alice addresses=\"00:00:00:01:02:04\" # Connect bob to R2 -ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24 rp-bob +ovn-nbctl lrp-add R2 bob 00:00:00:01:02:05 172.16.2.1/24 peer=rp-bob ovn-nbctl lsp-add bob rp-bob -- set Logical_Switch_Port rp-bob type=router \ options:router-port=bob addresses=\"00:00:00:01:02:05\" # Connect R1 to R2 -ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 R2_R1 -ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 R1_R2 +ovn-nbctl lrp-add R1 R1_R2 00:00:00:02:03:04 20.0.0.1/24 peer=R2_R1 +ovn-nbctl lrp-add R2 R2_R1 00:00:00:02:03:05 20.0.0.2/24 peer=R1_R2 #install static routes ovn-nbctl lr-route-add R1 172.16.1.0/24 20.0.0.2