---
controller/lflow.c | 204 ++++++++++++++++---
tests/ofproto-macros.at | 5 +-
tests/ovn.at | 516 +++++++++++++++++++++++++-----------------------
3 files changed, 455 insertions(+), 270 deletions(-)
diff --git a/controller/lflow.c b/controller/lflow.c
index 946c1e0..2b7d356 100644
--- a/controller/lflow.c
+++ b/controller/lflow.c
@@ -1171,6 +1171,178 @@ add_neighbor_flows(struct ovsdb_idl_index
*sbrec_port_binding_by_name,
}
}
+/* Builds the "learn()" action to be triggered by packets initiating a
+ * hairpin session.
+ *
+ * This will generate flows in table OFTABLE_CHK_LB_HAIRPIN_REPLY of the form:
+ * - match:
+ * metadata=<orig-pkt-metadata>,ip/ipv6,ip.src=<backend>,ip.dst=<vip>
+ * nw_proto='lb_proto',tp_src_port=<backend-port>
+ * - action:
+ * set MLF_LOOKUP_LB_HAIRPIN_BIT=1
+ */
+static void
+add_lb_vip_hairpin_reply_action(struct in6_addr *vip6, ovs_be32 vip,
+ uint8_t lb_proto, bool has_l4_port,
+ uint64_t cookie, struct ofpbuf *ofpacts)
+{
+ struct match match = MATCH_CATCHALL_INITIALIZER;
+ struct ofpact_learn *ol = ofpact_put_LEARN(ofpacts);
+ struct ofpact_learn_spec *ol_spec;
+ unsigned int imm_bytes;
+ uint8_t *src_imm;
+
+ /* Once learned, hairpin reply flows are permanent until the VIP/backend
+ * is removed.
+ */
+ ol->flags = NX_LEARN_F_DELETE_LEARNED;
+ ol->idle_timeout = OFP_FLOW_PERMANENT;
+ ol->hard_timeout = OFP_FLOW_PERMANENT;
+ ol->priority = OFP_DEFAULT_PRIORITY;
+ ol->table_id = OFTABLE_CHK_LB_HAIRPIN_REPLY;
+ ol->cookie = htonll(cookie);
+
+ /* Match on metadata of the packet that created the hairpin session. */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+
+ ol_spec->dst.field = mf_from_id(MFF_METADATA);
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
+ ol_spec->src.field = mf_from_id(MFF_METADATA);
+
+ /* Match on the same ETH type as the packet that created the hairpin
+ * session.
+ */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(MFF_ETH_TYPE);
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
+ union mf_value imm_eth_type = {
+ .be16 = !vip6 ? htons(ETH_TYPE_IP) : htons(ETH_TYPE_IPV6)
+ };
+ mf_write_subfield_value(&ol_spec->dst, &imm_eth_type, &match);
+
+ /* Push value last, as this may reallocate 'ol_spec'. */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_eth_type, imm_bytes);
+
+ /* Hairpin replies have ip.src == <backend-ip>. */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ if (!vip6) {
+ ol_spec->dst.field = mf_from_id(MFF_IPV4_SRC);
+ ol_spec->src.field = mf_from_id(MFF_IPV4_SRC);
+ } else {
+ ol_spec->dst.field = mf_from_id(MFF_IPV6_SRC);
+ ol_spec->src.field = mf_from_id(MFF_IPV6_SRC);
+ }
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
+
+ /* Hairpin replies have ip.dst == <vip>. */
+ union mf_value imm_ip;
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ if (!vip6) {
+ ol_spec->dst.field = mf_from_id(MFF_IPV4_DST);
+ imm_ip = (union mf_value) {
+ .be32 = vip
+ };
+ } else {
+ ol_spec->dst.field = mf_from_id(MFF_IPV6_DST);
+ imm_ip = (union mf_value) {
+ .ipv6 = *vip6
+ };
+ }
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
+ mf_write_subfield_value(&ol_spec->dst, &imm_ip, &match);
+
+ /* Push value last, as this may reallocate 'ol_spec' */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_ip, imm_bytes);
+
+ /* Hairpin replies have the same nw_proto as packets that created the
+ * session.
+ */
+ union mf_value imm_proto = {
+ .u8 = lb_proto,
+ };
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(MFF_IP_PROTO);
+ ol_spec->src.field = mf_from_id(MFF_IP_PROTO);
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
+ mf_write_subfield_value(&ol_spec->dst, &imm_proto, &match);
+
+ /* Push value last, as this may reallocate 'ol_spec' */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_proto, imm_bytes);
+
+ /* Hairpin replies have source port == <backend-port>. */
+ if (has_l4_port) {
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ switch (lb_proto) {
+ case IPPROTO_TCP:
+ ol_spec->dst.field = mf_from_id(MFF_TCP_SRC);
+ ol_spec->src.field = mf_from_id(MFF_TCP_DST);
+ break;
+ case IPPROTO_UDP:
+ ol_spec->dst.field = mf_from_id(MFF_UDP_SRC);
+ ol_spec->src.field = mf_from_id(MFF_UDP_DST);
+ break;
+ case IPPROTO_SCTP:
+ ol_spec->dst.field = mf_from_id(MFF_SCTP_SRC);
+ ol_spec->src.field = mf_from_id(MFF_SCTP_DST);
+ break;
+ default:
+ OVS_NOT_REACHED();
+ break;
+ }
+ ol_spec->dst.ofs = 0;
+ ol_spec->dst.n_bits = ol_spec->dst.field->n_bits;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_MATCH;
+ ol_spec->src_type = NX_LEARN_SRC_FIELD;
+ }
+
+ /* Set MLF_LOOKUP_LB_HAIRPIN_BIT for hairpin replies. */
+ ol_spec = ofpbuf_put_zeros(ofpacts, sizeof *ol_spec);
+ ol_spec->dst.field = mf_from_id(MFF_LOG_FLAGS);
+ ol_spec->dst.ofs = MLF_LOOKUP_LB_HAIRPIN_BIT;
+ ol_spec->dst.n_bits = 1;
+ ol_spec->n_bits = ol_spec->dst.n_bits;
+ ol_spec->dst_type = NX_LEARN_DST_LOAD;
+ ol_spec->src_type = NX_LEARN_SRC_IMMEDIATE;
+ union mf_value imm_reg_value = {
+ .u8 = 1
+ };
+ mf_write_subfield_value(&ol_spec->dst, &imm_reg_value, &match);
+
+ /* Push value last, as this may reallocate 'ol_spec' */
+ imm_bytes = DIV_ROUND_UP(ol_spec->dst.n_bits, 8);
+ src_imm = ofpbuf_put_zeros(ofpacts, OFPACT_ALIGN(imm_bytes));
+ memcpy(src_imm, &imm_reg_value, imm_bytes);
+
+ ofpact_finish_LEARN(ofpacts, &ol);