diff options
author | Mark Michelson <mmichels@redhat.com> | 2017-11-02 11:13:58 -0500 |
---|---|---|
committer | Ben Pfaff <blp@ovn.org> | 2017-11-02 10:52:26 -0700 |
commit | 485d373bf6e934d1842f4542a1eaf163b733c983 (patch) | |
tree | 0585d24160d3caeec48869802bf598bba4baf444 /ovn | |
parent | 9d236afafb7817df8a4e9fe6a46f00109e3b86d9 (diff) | |
download | openvswitch-485d373bf6e934d1842f4542a1eaf163b733c983.tar.gz |
ovn: Allow northd to install IPv6 ct_lb logical flows.
For this commit, ovn-northd will now accept both IPv4 and IPv6 addresses
in the northbound database for a load balancer VIP or destination
addresses. For IPv4, the behavior remains the same. For IPv6, the
following logical flows will be added to the southbound database:
* An ND_NA response for incoming ND_NS requests for the load balancer
VIP.
* A ct_lb flow with the configured IPv6 addresses.
The ovn-northd manpage has been updated to indicate what flows are
added for load balancers with IPv6 VIPs.
Signed-off-by: Mark Michelson <mmichels@redhat.com>
Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'ovn')
-rw-r--r-- | ovn/northd/ovn-northd.8.xml | 68 | ||||
-rw-r--r-- | ovn/northd/ovn-northd.c | 182 | ||||
-rw-r--r-- | ovn/ovn-nb.xml | 22 |
3 files changed, 163 insertions, 109 deletions
diff --git a/ovn/northd/ovn-northd.8.xml b/ovn/northd/ovn-northd.8.xml index e9799e18a..b31106521 100644 --- a/ovn/northd/ovn-northd.8.xml +++ b/ovn/northd/ovn-northd.8.xml @@ -243,8 +243,10 @@ balancing rules with virtual IP addresses (and ports) are configured in <code>OVN_Northbound</code> database for a logical switch datapath, a priority-100 flow is added for each configured virtual IP address - <var>VIP</var> with a match <code>ip && ip4.dst == <var>VIP</var> - </code> that sets an action <code>reg0[0] = 1; next;</code> to act as a + <var>VIP</var>. For IPv4 <var>VIPs</var>, the match is <code>ip + && ip4.dst == <var>VIP</var></code>. For IPv6 <var>VIPs</var>, + the match is <code>ip && ip6.dst == <var>VIP</var></code>. The + flow sets an action <code>reg0[0] = 1; next;</code> to act as a hint for table <code>Pre-stateful</code> to send IP packets to the connection tracker for packet de-fragmentation before eventually advancing to ingress table <code>LB</code>. @@ -383,21 +385,29 @@ <li> For all the configured load balancing rules for a switch in <code>OVN_Northbound</code> database that includes a L4 port - <var>PORT</var> of protocol <var>P</var> and IPv4 address - <var>VIP</var>, a priority-120 flow that matches on - <code>ct.new && ip && ip4.dst == <var>VIP - </var>&& <var>P</var> && <var>P</var>.dst == <var>PORT - </var></code> with an action of <code>ct_lb(<var>args</var>)</code>, - where <var>args</var> contains comma separated IPv4 addresses (and - optional port numbers) to load balance to. + <var>PORT</var> of protocol <var>P</var> and IP address + <var>VIP</var>, a priority-120 flow is added. For IPv4 <var>VIPs + </var>, the flow matches <code>ct.new && ip && + ip4.dst == <var>VIP</var> && <var>P</var> && + <var>P</var>.dst == <var>PORT</var></code>. For IPv6 <var>VIPs</var>, + the flow matches <code>ct.new && ip && ip6.dst == <var> + VIP </var>&& <var>P</var> && <var>P</var>.dst == <var> + PORT</var></code>. The flow's action is <code>ct_lb(<var>args</var>) + </code>, where <var>args</var> contains comma separated IP addresses + (and optional port numbers) to load balance to. The address family of + the IP addresses of <var>args</var> is the same as the address family + of <var>VIP</var> </li> <li> For all the configured load balancing rules for a switch in <code>OVN_Northbound</code> database that includes just an IP address - <var>VIP</var> to match on, a priority-110 flow that matches on - <code>ct.new && ip && ip4.dst == <var>VIP</var></code> - with an action of <code>ct_lb(<var>args</var>)</code>, where - <var>args</var> contains comma separated IPv4 addresses. + <var>VIP</var> to match on, OVN adds a priority-110 flow. For IPv4 + <var>VIPs</var>, the flow matches <code>ct.new && ip && + ip4.dst == <var>VIP</var></code>. For IPv6 <var>VIPs</var>, + the flow matches <code>ct.new && ip && ip6.dst == <var> + VIP</var></code>. The action on this flow is <code> + ct_lb(<var>args</var>)</code>, where <var>args</var> contains comma + separated IP addresses of the same address family as <var>VIP</var>. </li> <li> A priority-100 flow commits packets to connection tracker using @@ -1113,7 +1123,7 @@ output; <p> These flows reply to ARP requests for the virtual IP addresses configured in the router for DNAT or load balancing. For a - configured DNAT IP address or a load balancer VIP <var>A</var>, + configured DNAT IP address or a load balancer IPv4 VIP <var>A</var>, for each router port <var>P</var> with Ethernet address <var>E</var>, a priority-90 flow matches <code>inport == <var>P</var> && arp.op == 1 && @@ -1190,13 +1200,13 @@ arp.sha = <var>external_mac</var>; <p> Reply to IPv6 Neighbor Solicitations. These flows reply to Neighbor Solicitation requests for the router's own IPv6 - address and populate the logical router's mac binding table. - For each router port <var>P</var> that owns IPv6 address - <var>A</var>, solicited node address <var>S</var>, and - Ethernet address <var>E</var>, a priority-90 flow matches - <code>inport == <var>P</var> && nd_ns && - ip6.dst == {<var>A</var>, <var>E</var>} && nd.target - == <var>A</var></code> with the following actions: + address and load balancing IPv6 VIPs and populate the logical + router's mac binding table. For each router port <var>P</var> that + owns IPv6 address or has load balancing VIP <var>A</var>, solicited + node address <var>S</var>, and Ethernet address <var>E</var>, a + priority-90 flow matches <code>inport == <var>P</var> && + nd_ns && ip6.dst == {<var>A</var>, <var>E</var>} && + nd.target == <var>A</var></code> with the following actions: </p> <pre> @@ -1364,10 +1374,12 @@ icmp4 { to the next table. If load balancing rules with virtual IP addresses (and ports) are configured in <code>OVN_Northbound</code> database for a Gateway router, a priority-100 flow is added for each configured virtual - IP address <var>VIP</var> with a match <code>ip && - ip4.dst == <var>VIP</var></code> that sets an action - <code>ct_next;</code> to send IP packets to the connection tracker for - packet de-fragmentation and tracking before sending it to the next table. + IP address <var>VIP</var>. For IPv4 <var>VIPs</var> the flow matches + <code>ip && ip4.dst == <var>VIP</var></code>. For IPv6 + <var>VIPs</var>, the flow matches <code>ip && ip6.dst == + <var>VIP</var></code>. The flow uses the action <code>ct_next;</code> + to send IP packets to the connection tracker for packet de-fragmentation + and tracking before sending it to the next table. </p> <h3>Ingress Table 3: UNSNAT</h3> @@ -1464,7 +1476,8 @@ icmp4 { <p> Following load balancing DNAT flows are added for Gateway router or Router with gateway port. These flows are programmed only on the - <code>redirect-chassis</code>. + <code>redirect-chassis</code>. These flows do not get programmed for + load balancers with IPv6 <var>VIPs</var>. </p> <ul> @@ -1910,7 +1923,8 @@ arp { router gateway port with an action <code>ct_dnat;</code>. If the backend IPv4 address <var>B</var> is also configured with L4 port <var>PORT</var> of protocol <var>P</var>, then the - match also includes <code>P.src</code> == <var>PORT</var>. + match also includes <code>P.src</code> == <var>PORT</var>. These + flows are not added for load balancers with IPv6 <var>VIPs</var>. </p> <p> diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c index 268bd60d6..6732ad003 100644 --- a/ovn/northd/ovn-northd.c +++ b/ovn/northd/ovn-northd.c @@ -1562,11 +1562,11 @@ join_logical_ports(struct northd_context *ctx, static void ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port); + uint16_t *port, int *addr_family); static void get_router_load_balancer_ips(const struct ovn_datapath *od, - struct sset *all_ips) + struct sset *all_ips, int *addr_family) { if (!od->nbr) { return; @@ -1582,7 +1582,8 @@ get_router_load_balancer_ips(const struct ovn_datapath *od, char *ip_address = NULL; uint16_t port; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port); + ip_address_and_port_from_lb_key(node->key, &ip_address, &port, + addr_family); if (!ip_address) { continue; } @@ -1659,7 +1660,8 @@ get_nat_addresses(const struct ovn_port *op, size_t *n) /* A set to hold all load-balancer vips. */ struct sset all_ips = SSET_INITIALIZER(&all_ips); - get_router_load_balancer_ips(op->od, &all_ips); + int addr_family; + get_router_load_balancer_ips(op->od, &all_ips, &addr_family); const char *ip_address; SSET_FOR_EACH (ip_address, &all_ips) { @@ -2902,44 +2904,33 @@ build_pre_acls(struct ovn_datapath *od, struct hmap *lflows) * 'ip_address'. */ static void ip_address_and_port_from_lb_key(const char *key, char **ip_address, - uint16_t *port) + uint16_t *port, int *addr_family) { - char *ip_str, *start, *next; - *ip_address = NULL; - *port = 0; + struct sockaddr_storage ss; + char ip_addr_buf[INET6_ADDRSTRLEN]; + char *error; - next = start = xstrdup(key); - ip_str = strsep(&next, ":"); - if (!ip_str || !ip_str[0]) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address for load balancer key %s", key); - free(start); - return; - } - - ovs_be32 ip, mask; - char *error = ip_parse_masked(ip_str, &ip, &mask); - if (error || mask != OVS_BE32_MAX) { + error = ipv46_parse(key, PORT_OPTIONAL, &ss); + if (error) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip address for load balancer key %s", key); - free(start); + VLOG_WARN_RL(&rl, "bad ip address or port for load balancer key %s", + key); free(error); return; } - int l4_port = 0; - if (next && next[0]) { - if (!str_to_int(next, 0, &l4_port) || l4_port < 0 || l4_port > 65535) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); - VLOG_WARN_RL(&rl, "bad ip port for load balancer key %s", key); - free(start); - return; - } + if (ss.ss_family == AF_INET) { + struct sockaddr_in *sin = ALIGNED_CAST(struct sockaddr_in *, &ss); + *port = sin->sin_port == 0 ? 0 : ntohs(sin->sin_port); + inet_ntop(AF_INET, &sin->sin_addr, ip_addr_buf, sizeof ip_addr_buf); + } else { + struct sockaddr_in6 *sin6 = ALIGNED_CAST(struct sockaddr_in6 *, &ss); + *port = sin6->sin6_port == 0 ? 0 : ntohs(sin6->sin6_port); + inet_ntop(AF_INET6, &sin6->sin6_addr, ip_addr_buf, sizeof ip_addr_buf); } - *port = l4_port; - *ip_address = strdup(ip_str); - free(start); + *ip_address = xstrdup(ip_addr_buf); + *addr_family = ss.ss_family; } /* @@ -2967,6 +2958,7 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) struct sset all_ips = SSET_INITIALIZER(&all_ips); bool vip_configured = false; + int addr_family = AF_INET; for (int i = 0; i < od->nbs->n_load_balancer; i++) { struct nbrec_load_balancer *lb = od->nbs->load_balancer[i]; struct smap *vips = &lb->vips; @@ -2978,7 +2970,8 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) /* node->key contains IP:port or just IP. */ char *ip_address = NULL; uint16_t port; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port); + ip_address_and_port_from_lb_key(node->key, &ip_address, &port, + &addr_family); if (!ip_address) { continue; } @@ -3000,7 +2993,13 @@ build_pre_lb(struct ovn_datapath *od, struct hmap *lflows) * packet to conntrack for defragmentation. */ const char *ip_address; SSET_FOR_EACH(ip_address, &all_ips) { - char *match = xasprintf("ip && ip4.dst == %s", ip_address); + char *match; + + if (addr_family == AF_INET) { + match = xasprintf("ip && ip4.dst == %s", ip_address); + } else { + match = xasprintf("ip && ip6.dst == %s", ip_address); + } ovn_lflow_add(lflows, od, S_SWITCH_IN_PRE_LB, 100, match, REGBIT_CONNTRACK_DEFRAG" = 1; next;"); free(match); @@ -3458,10 +3457,12 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows) SMAP_FOR_EACH (node, vips) { uint16_t port = 0; + int addr_family; /* node->key contains IP:port or just IP. */ char *ip_address = NULL; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port); + ip_address_and_port_from_lb_key(node->key, &ip_address, &port, + &addr_family); if (!ip_address) { continue; } @@ -3469,7 +3470,11 @@ build_stateful(struct ovn_datapath *od, struct hmap *lflows) /* New connections in Ingress table. */ char *action = xasprintf("ct_lb(%s);", node->value); struct ds match = DS_EMPTY_INITIALIZER; - ds_put_format(&match, "ct.new && ip4.dst == %s", ip_address); + if (addr_family == AF_INET) { + ds_put_format(&match, "ct.new && ip4.dst == %s", ip_address); + } else { + ds_put_format(&match, "ct.new && ip6.dst == %s", ip_address); + } if (port) { if (lb->protocol && !strcmp(lb->protocol, "udp")) { ds_put_format(&match, " && udp.dst == %d", port); @@ -4352,7 +4357,7 @@ static void add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, struct ds *match, struct ds *actions, int priority, const char *lb_force_snat_ip, char *backend_ips, - bool is_udp) + bool is_udp, int addr_family) { /* A match and actions for new connections. */ char *new_match = xasprintf("ct.new && %s", ds_cstr(match)); @@ -4380,7 +4385,8 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, free(new_match); free(est_match); - if (!od->l3dgw_port || !od->l3redirect_port || !backend_ips) { + if (!od->l3dgw_port || !od->l3redirect_port || !backend_ips + || addr_family != AF_INET) { return; } @@ -4397,7 +4403,9 @@ add_router_lb_flow(struct hmap *lflows, struct ovn_datapath *od, while (ip_str && ip_str[0]) { char *ip_address = NULL; uint16_t port = 0; - ip_address_and_port_from_lb_key(ip_str, &ip_address, &port); + int addr_family; + ip_address_and_port_from_lb_key(ip_str, &ip_address, &port, + &addr_family); if (!ip_address) { break; } @@ -4635,36 +4643,55 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, /* A set to hold all load-balancer vips that need ARP responses. */ struct sset all_ips = SSET_INITIALIZER(&all_ips); - get_router_load_balancer_ips(op->od, &all_ips); + int addr_family; + get_router_load_balancer_ips(op->od, &all_ips, &addr_family); const char *ip_address; SSET_FOR_EACH(ip_address, &all_ips) { - ovs_be32 ip; - if (!ip_parse(ip_address, &ip) || !ip) { - continue; - } - ds_clear(&match); - ds_put_format(&match, - "inport == %s && arp.tpa == "IP_FMT" && arp.op == 1", - op->json_key, IP_ARGS(ip)); + if (addr_family == AF_INET) { + ds_put_format(&match, + "inport == %s && arp.tpa == %s && arp.op == 1", + op->json_key, ip_address); + } else { + ds_put_format(&match, + "inport == %s && nd_ns && nd.target == %s", + op->json_key, ip_address); + } ds_clear(&actions); - ds_put_format(&actions, + if (addr_family == AF_INET) { + 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 = "IP_FMT"; " + "arp.spa = %s; " "outport = %s; " "flags.loopback = 1; " "output;", op->lrp_networks.ea_s, op->lrp_networks.ea_s, - IP_ARGS(ip), + ip_address, op->json_key); + } else { + ds_put_format(&actions, + "nd_na { " + "eth.src = %s; " + "ip6.src = %s; " + "nd.target = %s; " + "nd.tll = %s; " + "outport = inport; " + "flags.loopback = 1; " + "output; " + "};", + op->lrp_networks.ea_s, + ip_address, + ip_address, + op->lrp_networks.ea_s); + } ovn_lflow_add(lflows, op->od, S_ROUTER_IN_IP_INPUT, 90, ds_cstr(&match), ds_cstr(&actions)); } @@ -5328,16 +5355,36 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, SMAP_FOR_EACH (node, vips) { uint16_t port = 0; + int addr_family; /* node->key contains IP:port or just IP. */ char *ip_address = NULL; - ip_address_and_port_from_lb_key(node->key, &ip_address, &port); + ip_address_and_port_from_lb_key(node->key, &ip_address, &port, + &addr_family); if (!ip_address) { continue; } if (!sset_contains(&all_ips, ip_address)) { sset_add(&all_ips, ip_address); + /* If there are any load balancing rules, we should send + * the packet to conntrack for defragmentation and + * tracking. This helps with two things. + * + * 1. With tracking, we can send only new connections to + * pick a DNAT ip address from a group. + * 2. If there are L4 ports in load balancing rules, we + * need the defragmentation to match on L4 ports. */ + ds_clear(&match); + if (addr_family == AF_INET) { + ds_put_format(&match, "ip && ip4.dst == %s", + ip_address); + } else { + ds_put_format(&match, "ip && ip6.dst == %s", + ip_address); + } + ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, + 100, ds_cstr(&match), "ct_next;"); } /* Higher priority rules are added for load-balancing in DNAT @@ -5349,8 +5396,13 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, ds_put_format(&actions, "ct_lb(%s);", node->value); ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", - ip_address); + if (addr_family == AF_INET) { + ds_put_format(&match, "ip && ip4.dst == %s", + ip_address); + } else { + ds_put_format(&match, "ip && ip6.dst == %s", + ip_address); + } free(ip_address); int prio = 110; @@ -5372,26 +5424,10 @@ build_lrouter_flows(struct hmap *datapaths, struct hmap *ports, od->l3redirect_port->json_key); } add_router_lb_flow(lflows, od, &match, &actions, prio, - lb_force_snat_ip, node->value, is_udp); + lb_force_snat_ip, node->value, is_udp, + addr_family); } } - - /* If there are any load balancing rules, we should send the - * packet to conntrack for defragmentation and tracking. This helps - * with two things. - * - * 1. With tracking, we can send only new connections to pick a - * DNAT ip address from a group. - * 2. If there are L4 ports in load balancing rules, we need the - * defragmentation to match on L4 ports. */ - const char *ip_address; - SSET_FOR_EACH(ip_address, &all_ips) { - ds_clear(&match); - ds_put_format(&match, "ip && ip4.dst == %s", ip_address); - ovn_lflow_add(lflows, od, S_ROUTER_IN_DEFRAG, - 100, ds_cstr(&match), "ct_next;"); - } - sset_destroy(&all_ips); } diff --git a/ovn/ovn-nb.xml b/ovn/ovn-nb.xml index 9869d7ed7..a6afc9583 100644 --- a/ovn/ovn-nb.xml +++ b/ovn/ovn-nb.xml @@ -113,8 +113,8 @@ </column> <column name="load_balancer"> - Load balance a virtual ipv4 address to a set of logical port endpoint - ipv4 addresses. + Load balance a virtual ip address to a set of logical port endpoint + ip addresses. </column> <column name="acls"> @@ -925,16 +925,20 @@ <column name="vips"> <p> - A map of virtual IPv4 addresses (and an optional port number with + A map of virtual IP addresses (and an optional port number with <code>:</code> as a separator) associated with this load balancer and - their corresponding endpoint IPv4 addresses (and optional port numbers + their corresponding endpoint IP addresses (and optional port numbers with <code>:</code> as separators) separated by commas. If the destination IP address (and port number) of a packet leaving a - container or a VM matches the virtual IPv4 address (and port number) + container or a VM matches the virtual IP address (and port number) provided here as a key, then OVN will statefully replace the - destination IP address by one of the provided IPv4 address (and port - number) in this map as a value. Examples for keys are "192.168.1.4" - and "172.16.1.8:80". Examples for value are "10.0.0.1, 10.0.0.2" and + destination IP address by one of the provided IP address (and port + number) in this map as a value. IPv4 and IPv6 addresses are supported + for load balancing; however a VIP of one address family may not be + mapped to a destination IP address of a different family. If + specifying an IPv6 address with a port, the address portion must be + enclosed in square brackets. Examples for keys are "192.168.1.4" and + "[fd0f::1]:8800". Examples for value are "10.0.0.1, 10.0.0.2" and "20.0.0.10:8800, 20.0.0.11:8800". </p> </column> @@ -1116,7 +1120,7 @@ </column> <column name="load_balancer"> - Load balance a virtual ipv4 address to a set of logical port ipv4 + Load balance a virtual ip address to a set of logical port ip addresses. Load balancer rules only work on the Gateway routers. </column> |