From: Ben Pfaff Date: Mon, 12 Sep 2011 23:19:57 +0000 (-0700) Subject: Implement new "learn" action. X-Git-Tag: v1.3.0~307 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=75a75043564dc9b002fffa6c6ad71e0d4d5c892e;p=sliver-openvswitch.git Implement new "learn" action. There are a few loose ends here. First, learning actions cause too much flow revalidation. Upcoming commits will fix that problem. The following additional issues have not yet been addressed: * Resource limits: nothing yet limits the maximum number of flows that can be learned. It is possible to exhaust all system memory. * Age reporting: there is no way to find out how soon a learned table entry is due to be evicted. To try this action out, here's a recipe for a very simple-minded MAC learning switch. It uses a 10-second MAC expiration time to make it easier to see what's going on: ovs-vsctl del-controller br0 ovs-ofctl del-flows br0 ovs-ofctl add-flow br0 "table=0 actions=learn(table=1, hard_timeout=10, \ NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], \ output:NXM_OF_IN_PORT[]), resubmit(,1)" ovs-ofctl add-flow br0 "table=1 priority=0 actions=flood" You can then dump the MAC learning table with: ovs-ofctl dump-flows br0 table=1 --- diff --git a/NEWS b/NEWS index 49ec0b15f..186b2f624 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,7 @@ Post-v1.2.0 - OpenFlow: - Added an OpenFlow extension which allows the "output" action to accept NXM fields. + - Added an OpenFlow extension for flexible learning. - ovs-appctl: - New "version" command to determine version of running daemon - ovs-vswitchd: diff --git a/include/openflow/nicira-ext.h b/include/openflow/nicira-ext.h index 4fab6f17c..6ce87a253 100644 --- a/include/openflow/nicira-ext.h +++ b/include/openflow/nicira-ext.h @@ -281,7 +281,8 @@ enum nx_action_subtype { NXAST_BUNDLE, /* struct nx_action_bundle */ NXAST_BUNDLE_LOAD, /* struct nx_action_bundle */ NXAST_RESUBMIT_TABLE, /* struct nx_action_resubmit */ - NXAST_OUTPUT_REG /* struct nx_action_output_reg */ + NXAST_OUTPUT_REG, /* struct nx_action_output_reg */ + NXAST_LEARN /* struct nx_action_learn */ }; /* Header for Nicira-defined actions. */ @@ -656,6 +657,219 @@ enum nx_mp_algorithm { NX_MP_ALG_ITER_HASH /* Iterative Hash. */ }; +/* Action structure for NXAST_LEARN. + * + * This action adds or modifies a flow in an OpenFlow table, similar to + * OFPT_FLOW_MOD with OFPFC_MODIFY_STRICT as 'command'. The new flow has the + * specified idle timeout, hard timeout, priority, cookie, and flags. The new + * flow's match criteria and actions are built by applying each of the series + * of flow_mod_spec elements included as part of the action. + * + * A flow_mod_spec starts with a 16-bit header. A header that is all-bits-0 is + * a no-op used for padding the action as a whole to a multiple of 8 bytes in + * length. Otherwise, the flow_mod_spec can be thought of as copying 'n_bits' + * bits from a source to a destination. In this case, the header contains + * multiple fields: + * + * 15 14 13 12 11 10 0 + * +------+---+------+---------------------------------+ + * | 0 |src| dst | n_bits | + * +------+---+------+---------------------------------+ + * + * The meaning and format of a flow_mod_spec depends on 'src' and 'dst'. The + * following table summarizes the meaning of each possible combination. + * Details follow the table: + * + * src dst meaning + * --- --- ---------------------------------------------------------- + * 0 0 Add match criteria based on value in a field. + * 1 0 Add match criteria based on an immediate value. + * 0 1 Add NXAST_REG_LOAD action to copy field into a different field. + * 1 1 Add NXAST_REG_LOAD action to load immediate value into a field. + * 0 2 Add OFPAT_OUTPUT action to output to port from specified field. + * All other combinations are undefined and not allowed. + * + * The flow_mod_spec header is followed by a source specification and a + * destination specification. The format and meaning of the source + * specification depends on 'src': + * + * - If 'src' is 0, the source bits are taken from a field in the flow to + * which this action is attached. (This should be a wildcarded field. If + * its value is fully specified then the source bits being copied have + * constant values.) + * + * The source specification is an ovs_be32 'field' and an ovs_be16 'ofs'. + * 'field' is an nxm_header with nxm_hasmask=0, and 'ofs' the starting bit + * offset within that field. The source bits are field[ofs:ofs+n_bits-1]. + * 'field' and 'ofs' are subject to the same restrictions as the source + * field in NXAST_REG_MOVE. + * + * - If 'src' is 1, the source bits are a constant value. The source + * specification is (n_bits+15)/16*2 bytes long. Taking those bytes as a + * number in network order, the source bits are the 'n_bits' + * least-significant bits. The switch will report an error if other bits + * in the constant are nonzero. + * + * The flow_mod_spec destination specification, for 'dst' of 0 or 1, is an + * ovs_be32 'field' and an ovs_be16 'ofs'. 'field' is an nxm_header with + * nxm_hasmask=0 and 'ofs' is a starting bit offset within that field. The + * meaning of the flow_mod_spec depends on 'dst': + * + * - If 'dst' is 0, the flow_mod_spec specifies match criteria for the new + * flow. The new flow matches only if bits field[ofs:ofs+n_bits-1] in a + * packet equal the source bits. 'field' may be any nxm_header with + * nxm_hasmask=0 that is allowed in NXT_FLOW_MOD. + * + * Order is significant. Earlier flow_mod_specs must satisfy any + * prerequisites for matching fields specified later, by copying constant + * values into prerequisite fields. + * + * The switch will reject flow_mod_specs that do not satisfy NXM masking + * restrictions. + * + * - If 'dst' is 1, the flow_mod_spec specifies an NXAST_REG_LOAD action for + * the new flow. The new flow copies the source bits into + * field[ofs:ofs+n_bits-1]. Actions are executed in the same order as the + * flow_mod_specs. + * + * The flow_mod_spec destination spec for 'dst' of 2 (when 'src' is 0) is + * empty. It has the following meaning: + * + * - The flow_mod_spec specifies an OFPAT_OUTPUT action for the new flow. + * The new flow outputs to the OpenFlow port specified by the source field. + * Of the special output ports with value OFPP_MAX or larger, OFPP_IN_PORT, + * OFPP_FLOOD, OFPP_LOCAL, and OFPP_ALL are supported. Other special ports + * may not be used. + * + * Resource Management + * ------------------- + * + * A switch has a finite amount of flow table space available for learning. + * When this space is exhausted, no new learning table entries will be learned + * until some existing flow table entries expire. The controller should be + * prepared to handle this by flooding (which can be implemented as a + * low-priority flow). + * + * Examples + * -------- + * + * The following examples give a prose description of the flow_mod_specs along + * with informal notation for how those would be represented and a hex dump of + * the bytes that would be required. + * + * These examples could work with various nx_action_learn parameters. Typical + * values would be idle_timeout=OFP_FLOW_PERMANENT, hard_timeout=60, + * priority=OFP_DEFAULT_PRIORITY, flags=0, table_id=10. + * + * 1. Learn input port based on the source MAC, with lookup into + * NXM_NX_REG1[16:31] by resubmit to in_port=99: + * + * Match on in_port=99: + * ovs_be16(src=1, dst=0, n_bits=16), 20 10 + * ovs_be16(99), 00 63 + * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00 + * + * Match Ethernet destination on Ethernet source from packet: + * ovs_be16(src=0, dst=0, n_bits=48), 00 30 + * ovs_be32(NXM_OF_ETH_SRC), ovs_be16(0) 00 00 04 06 00 00 + * ovs_be32(NXM_OF_ETH_DST), ovs_be16(0) 00 00 02 06 00 00 + * + * Set NXM_NX_REG1[16:31] to the packet's input port: + * ovs_be16(src=0, dst=1, n_bits=16), 08 10 + * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00 + * ovs_be32(NXM_NX_REG1), ovs_be16(16) 00 01 02 04 00 10 + * + * Given a packet that arrived on port A with Ethernet source address B, + * this would set up the flow "in_port=99, dl_dst=B, + * actions=load:A->NXM_NX_REG1[16..31]". + * + * In syntax accepted by ovs-ofctl, this action is: learn(in_port=99, + * NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) + * + * 2. Output to input port based on the source MAC and VLAN VID, with lookup + * into NXM_NX_REG1[16:31]: + * + * Match on same VLAN ID as packet: + * ovs_be16(src=0, dst=0, n_bits=12), 00 0c + * ovs_be32(NXM_OF_VLAN_TCI), ovs_be16(0) 00 00 08 02 00 00 + * ovs_be32(NXM_OF_VLAN_TCI), ovs_be16(0) 00 00 08 02 00 00 + * + * Match Ethernet destination on Ethernet source from packet: + * ovs_be16(src=0, dst=0, n_bits=48), 00 30 + * ovs_be32(NXM_OF_ETH_SRC), ovs_be16(0) 00 00 04 06 00 00 + * ovs_be32(NXM_OF_ETH_DST), ovs_be16(0) 00 00 02 06 00 00 + * + * Output to the packet's input port: + * ovs_be16(src=0, dst=2, n_bits=16), 10 10 + * ovs_be32(NXM_OF_IN_PORT), ovs_be16(0) 00 00 00 02 00 00 + * + * Given a packet that arrived on port A with Ethernet source address B in + * VLAN C, this would set up the flow "dl_dst=B, vlan_vid=C, + * actions=output:A". + * + * In syntax accepted by ovs-ofctl, this action is: + * learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], + * output:NXM_OF_IN_PORT[]) + * + * 3. Here's a recipe for a very simple-minded MAC learning switch. It uses a + * 10-second MAC expiration time to make it easier to see what's going on + * + * ovs-vsctl del-controller br0 + * ovs-ofctl del-flows br0 + * ovs-ofctl add-flow br0 "table=0 actions=learn(table=1, \ + hard_timeout=10, NXM_OF_VLAN_TCI[0..11], \ + NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], \ + output:NXM_OF_IN_PORT[]), resubmit(,1)" + * ovs-ofctl add-flow br0 "table=1 priority=0 actions=flood" + * + * You can then dump the MAC learning table with: + * + * ovs-ofctl dump-flows br0 table=1 + * + * Usage Advice + * ------------ + * + * For best performance, segregate learned flows into a table that is not used + * for any other flows except possibly for a lowest-priority "catch-all" flow + * (a flow with no match criteria). If different learning actions specify + * different match criteria, use different tables for the learned flows. + * + * The meaning of 'hard_timeout' and 'idle_timeout' can be counterintuitive. + * These timeouts apply to the flow that is added, which means that a flow with + * an idle timeout will expire when no traffic has been sent *to* the learned + * address. This is not usually the intent in MAC learning; instead, we want + * the MAC learn entry to expire when no traffic has been sent *from* the + * learned address. Use a hard timeout for that. + */ +struct nx_action_learn { + ovs_be16 type; /* OFPAT_VENDOR. */ + ovs_be16 len; /* At least 24. */ + ovs_be32 vendor; /* NX_VENDOR_ID. */ + ovs_be16 subtype; /* NXAST_LEARN. */ + ovs_be16 idle_timeout; /* Idle time before discarding (seconds). */ + ovs_be16 hard_timeout; /* Max time before discarding (seconds). */ + ovs_be16 priority; /* Priority level of flow entry. */ + ovs_be64 cookie; /* Cookie for new flow. */ + ovs_be16 flags; /* Either 0 or OFPFF_SEND_FLOW_REM. */ + uint8_t table_id; /* Table to insert flow entry. */ + uint8_t pad[5]; /* Must be zero. */ + /* Followed by a sequence of flow_mod_spec elements, as described above, + * until the end of the action is reached. */ +}; +OFP_ASSERT(sizeof(struct nx_action_learn) == 32); + +#define NX_LEARN_N_BITS_MASK 0x3ff + +#define NX_LEARN_SRC_FIELD (0 << 13) /* Copy from field. */ +#define NX_LEARN_SRC_IMMEDIATE (1 << 13) /* Copy from immediate value. */ +#define NX_LEARN_SRC_MASK (1 << 13) + +#define NX_LEARN_DST_MATCH (0 << 11) /* Add match criterion. */ +#define NX_LEARN_DST_LOAD (1 << 11) /* Add NXAST_REG_LOAD action. */ +#define NX_LEARN_DST_OUTPUT (2 << 11) /* Add OFPAT_OUTPUT action. */ +#define NX_LEARN_DST_RESERVED (3 << 11) /* Not yet defined. */ +#define NX_LEARN_DST_MASK (3 << 11) + /* Action structure for NXAST_AUTOPATH. * * This action performs the following steps in sequence: diff --git a/lib/automake.mk b/lib/automake.mk index 7cc0dbe34..df3071129 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -67,6 +67,8 @@ lib_libopenvswitch_a_SOURCES = \ lib/lacp.h \ lib/leak-checker.c \ lib/leak-checker.h \ + lib/learn.c \ + lib/learn.h \ lib/learning-switch.c \ lib/learning-switch.h \ lib/list.c \ diff --git a/lib/learn.c b/lib/learn.c new file mode 100644 index 000000000..8927ff899 --- /dev/null +++ b/lib/learn.c @@ -0,0 +1,662 @@ +/* + * Copyright (c) 2011 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "learn.h" + +#include "byte-order.h" +#include "dynamic-string.h" +#include "meta-flow.h" +#include "nx-match.h" +#include "ofp-util.h" +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "unaligned.h" + +static ovs_be16 +get_be16(const void **pp) +{ + const ovs_be16 *p = *pp; + ovs_be16 value = *p; + *pp = p + 1; + return value; +} + +static ovs_be32 +get_be32(const void **pp) +{ + const ovs_be32 *p = *pp; + ovs_be32 value = get_unaligned_be32(p); + *pp = p + 1; + return value; +} + +static uint64_t +get_bits(int n_bits, const void **p) +{ + int n_segs = DIV_ROUND_UP(n_bits, 16); + uint64_t value; + + value = 0; + while (n_segs-- > 0) { + value = (value << 16) | ntohs(get_be16(p)); + } + return value; +} + +static unsigned int +learn_min_len(uint16_t header) +{ + int n_bits = header & NX_LEARN_N_BITS_MASK; + int src_type = header & NX_LEARN_SRC_MASK; + int dst_type = header & NX_LEARN_DST_MASK; + unsigned int min_len; + + min_len = 0; + if (src_type == NX_LEARN_SRC_FIELD) { + min_len += sizeof(ovs_be32); /* src_field */ + min_len += sizeof(ovs_be16); /* src_ofs */ + } else { + min_len += DIV_ROUND_UP(n_bits, 16); + } + if (dst_type == NX_LEARN_DST_MATCH || + dst_type == NX_LEARN_DST_LOAD) { + min_len += sizeof(ovs_be32); /* dst_field */ + min_len += sizeof(ovs_be16); /* dst_ofs */ + } + return min_len; +} + +static int +learn_check_header(uint16_t header, size_t len) +{ + int src_type = header & NX_LEARN_SRC_MASK; + int dst_type = header & NX_LEARN_DST_MASK; + + /* Check for valid src and dst type combination. */ + if (dst_type == NX_LEARN_DST_MATCH || + dst_type == NX_LEARN_DST_LOAD || + (dst_type == NX_LEARN_DST_OUTPUT && + src_type == NX_LEARN_SRC_FIELD)) { + /* OK. */ + } else { + return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT); + } + + /* Check that the arguments don't overrun the end of the action. */ + if (len < learn_min_len(header)) { + return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN); + } + + return 0; +} + +/* Checks that 'learn' (which must be at least 'sizeof *learn' bytes long) is a + * valid action on 'flow'. */ +int +learn_check(const struct nx_action_learn *learn, const struct flow *flow) +{ + struct cls_rule rule; + const void *p, *end; + + cls_rule_init_catchall(&rule, 0); + + if (learn->flags & ~htons(OFPFF_SEND_FLOW_REM) + || !is_all_zeros(learn->pad, sizeof learn->pad) + || learn->table_id == 0xff) { + return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT); + } + + end = (char *) learn + ntohs(learn->len); + for (p = learn + 1; p != end; ) { + uint16_t header = ntohs(get_be16(&p)); + int n_bits = header & NX_LEARN_N_BITS_MASK; + int src_type = header & NX_LEARN_SRC_MASK; + int dst_type = header & NX_LEARN_DST_MASK; + + uint64_t value; + int error; + + if (!header) { + break; + } + + error = learn_check_header(header, (char *) end - (char *) p); + if (error) { + return error; + } + + /* Check the source. */ + if (src_type == NX_LEARN_SRC_FIELD) { + ovs_be32 src_field = get_be32(&p); + int src_ofs = ntohs(get_be16(&p)); + + error = nxm_src_check(src_field, src_ofs, n_bits, flow); + if (error) { + return error; + } + value = 0; + } else { + value = get_bits(n_bits, &p); + } + + /* Check the destination. */ + if (dst_type == NX_LEARN_DST_MATCH || dst_type == NX_LEARN_DST_LOAD) { + ovs_be32 dst_field = get_be32(&p); + int dst_ofs = ntohs(get_be16(&p)); + int error; + + error = nxm_dst_check(dst_field, dst_ofs, n_bits, &rule.flow); + if (error) { + return error; + } + + if (dst_type == NX_LEARN_DST_MATCH + && src_type == NX_LEARN_SRC_IMMEDIATE) { + mf_set_subfield(nxm_field_to_mf_field(ntohl(dst_field)), value, + dst_ofs, n_bits, &rule); + } + } + } + if (!is_all_zeros(p, (char *) end - (char *) p)) { + return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT); + } + + return 0; +} + +void +learn_execute(const struct nx_action_learn *learn, const struct flow *flow, + struct ofputil_flow_mod *fm) +{ + const void *p, *end; + struct ofpbuf actions; + + cls_rule_init_catchall(&fm->cr, ntohs(learn->priority)); + fm->cookie = learn->cookie; + fm->table_id = learn->table_id; + fm->command = OFPFC_MODIFY_STRICT; + fm->idle_timeout = ntohs(learn->idle_timeout); + fm->hard_timeout = ntohs(learn->hard_timeout); + fm->buffer_id = UINT32_MAX; + fm->out_port = OFPP_NONE; + fm->flags = ntohs(learn->flags) & OFPFF_SEND_FLOW_REM; + fm->actions = NULL; + fm->n_actions = 0; + + ofpbuf_init(&actions, 64); + + for (p = learn + 1, end = (char *) learn + ntohs(learn->len); p != end; ) { + uint16_t header = ntohs(get_be16(&p)); + int n_bits = header & NX_LEARN_N_BITS_MASK; + int src_type = header & NX_LEARN_SRC_MASK; + int dst_type = header & NX_LEARN_DST_MASK; + uint64_t value; + + struct nx_action_reg_load *load; + ovs_be32 dst_field; + int dst_ofs; + + if (!header) { + break; + } + + if (src_type == NX_LEARN_SRC_FIELD) { + ovs_be32 src_field = get_be32(&p); + int src_ofs = ntohs(get_be16(&p)); + + value = nxm_read_field_bits(src_field, + nxm_encode_ofs_nbits(src_ofs, n_bits), + flow); + } else { + value = get_bits(n_bits, &p); + } + + switch (dst_type) { + case NX_LEARN_DST_MATCH: + dst_field = get_be32(&p); + dst_ofs = ntohs(get_be16(&p)); + mf_set_subfield(nxm_field_to_mf_field(ntohl(dst_field)), value, + dst_ofs, n_bits, &fm->cr); + break; + + case NX_LEARN_DST_LOAD: + dst_field = get_be32(&p); + dst_ofs = ntohs(get_be16(&p)); + load = ofputil_put_NXAST_REG_LOAD(&actions); + load->ofs_nbits = nxm_encode_ofs_nbits(dst_ofs, n_bits); + load->dst = dst_field; + load->value = htonll(value); + break; + + case NX_LEARN_DST_OUTPUT: + ofputil_put_OFPAT_OUTPUT(&actions)->port = htons(value); + break; + } + } + + fm->actions = ofpbuf_steal_data(&actions); + fm->n_actions = actions.size / sizeof(struct ofp_action_header); +} + +static void +put_be16(struct ofpbuf *b, ovs_be16 x) +{ + ofpbuf_put(b, &x, sizeof x); +} + +static void +put_be32(struct ofpbuf *b, ovs_be32 x) +{ + ofpbuf_put(b, &x, sizeof x); +} + +static void +put_u16(struct ofpbuf *b, uint16_t x) +{ + put_be16(b, htons(x)); +} + +static void +put_u32(struct ofpbuf *b, uint32_t x) +{ + put_be32(b, htonl(x)); +} + +struct learn_spec { + int n_bits; + + int src_type; + const struct mf_field *src; + int src_ofs; + uint8_t src_imm[sizeof(union mf_value)]; + + int dst_type; + const struct mf_field *dst; + int dst_ofs; +}; + +static void +learn_parse_spec(const char *orig, char *name, char *value, + struct learn_spec *spec) +{ + if (mf_from_name(name)) { + const struct mf_field *dst = mf_from_name(name); + union mf_value imm; + char *error; + + error = mf_parse_value(dst, value, &imm); + if (error) { + ovs_fatal(0, "%s", error); + } + + spec->n_bits = dst->n_bits; + spec->src_type = NX_LEARN_SRC_IMMEDIATE; + spec->src = NULL; + spec->src_ofs = 0; + memcpy(spec->src_imm, &imm, dst->n_bytes); + spec->dst_type = NX_LEARN_DST_MATCH; + spec->dst = dst; + spec->dst_ofs = 0; + } else if (strchr(name, '[')) { + uint32_t src_header, dst_header; + int src_ofs, dst_ofs; + int n_bits; + + /* Parse destination and check prerequisites. */ + if (nxm_parse_field_bits(name, &dst_header, &dst_ofs, + &n_bits)[0] != '\0') { + ovs_fatal(0, "%s: syntax error after NXM field name `%s'", + orig, name); + } + + /* Parse source and check prerequisites. */ + if (value[0] != '\0') { + int src_nbits; + + if (nxm_parse_field_bits(value, &src_header, &src_ofs, + &src_nbits)[0] != '\0') { + ovs_fatal(0, "%s: syntax error after NXM field name `%s'", + orig, value); + } + if (src_nbits != n_bits) { + ovs_fatal(0, "%s: bit widths of %s (%d) and %s (%d) differ", + orig, name, dst_header, value, dst_header); + } + } else { + src_header = dst_header; + src_ofs = dst_ofs; + } + + spec->n_bits = n_bits; + spec->src_type = NX_LEARN_SRC_FIELD; + spec->src = nxm_field_to_mf_field(src_header); + spec->src_ofs = src_ofs; + spec->dst_type = NX_LEARN_DST_MATCH; + spec->dst = nxm_field_to_mf_field(dst_header); + spec->dst_ofs = 0; + } else if (!strcmp(name, "load")) { + if (value[strcspn(value, "[-")] == '-') { + struct nx_action_reg_load load; + int nbits, imm_bytes; + uint64_t imm; + int i; + + nxm_parse_reg_load(&load, value); + nbits = nxm_decode_n_bits(load.ofs_nbits); + imm_bytes = DIV_ROUND_UP(nbits, 8); + imm = ntohll(load.value); + + spec->n_bits = nbits; + spec->src_type = NX_LEARN_SRC_IMMEDIATE; + spec->src = NULL; + spec->src_ofs = 0; + for (i = 0; i < imm_bytes; i++) { + spec->src_imm[i] = imm >> ((imm_bytes - i - 1) * 8); + } + spec->dst_type = NX_LEARN_DST_LOAD; + spec->dst = nxm_field_to_mf_field(ntohl(load.dst)); + spec->dst_ofs = nxm_decode_ofs(load.ofs_nbits); + } else { + struct nx_action_reg_move move; + + nxm_parse_reg_move(&move, value); + + spec->n_bits = ntohs(move.n_bits); + spec->src_type = NX_LEARN_SRC_FIELD; + spec->src = nxm_field_to_mf_field(ntohl(move.src)); + spec->src_ofs = ntohs(move.src_ofs); + spec->dst_type = NX_LEARN_DST_LOAD; + spec->dst = nxm_field_to_mf_field(ntohl(move.dst)); + spec->dst_ofs = ntohs(move.dst_ofs); + } + } else if (!strcmp(name, "output")) { + uint32_t header; + int ofs, n_bits; + + if (nxm_parse_field_bits(value, &header, &ofs, &n_bits)[0] != '\0') { + ovs_fatal(0, "%s: syntax error after NXM field name `%s'", + orig, name); + } + + spec->n_bits = n_bits; + spec->src_type = NX_LEARN_SRC_FIELD; + spec->src = nxm_field_to_mf_field(header); + spec->src_ofs = ofs; + spec->dst_type = NX_LEARN_DST_OUTPUT; + spec->dst = NULL; + spec->dst_ofs = 0; + } else { + ovs_fatal(0, "%s: unknown keyword %s", orig, name); + } +} + +void +learn_parse(struct ofpbuf *b, char *arg, const struct flow *flow) +{ + char *orig = xstrdup(arg); + char *name, *value; + size_t learn_ofs; + size_t len; + + struct nx_action_learn *learn; + struct cls_rule rule; + + learn_ofs = b->size; + learn = ofputil_put_NXAST_LEARN(b); + learn->idle_timeout = htons(OFP_FLOW_PERMANENT); + learn->hard_timeout = htons(OFP_FLOW_PERMANENT); + learn->priority = htons(OFP_DEFAULT_PRIORITY); + learn->cookie = htonll(0); + learn->flags = htons(0); + learn->table_id = 1; + + cls_rule_init_catchall(&rule, 0); + while (ofputil_parse_key_value(&arg, &name, &value)) { + learn = ofpbuf_at_assert(b, learn_ofs, sizeof *learn); + if (!strcmp(name, "table")) { + learn->table_id = atoi(value); + if (learn->table_id == 255) { + ovs_fatal(0, "%s: table id 255 not valid for `learn' action", + orig); + } + } else if (!strcmp(name, "priority")) { + learn->priority = htons(atoi(value)); + } else if (!strcmp(name, "idle_timeout")) { + learn->idle_timeout = htons(atoi(value)); + } else if (!strcmp(name, "hard_timeout")) { + learn->hard_timeout = htons(atoi(value)); + } else if (!strcmp(name, "cookie")) { + learn->cookie = htonll(strtoull(value, NULL, 0)); + } else { + struct learn_spec spec; + + learn_parse_spec(orig, name, value, &spec); + + /* Check prerequisites. */ + if (spec.src_type == NX_LEARN_SRC_FIELD + && !mf_are_prereqs_ok(spec.src, flow)) { + ovs_fatal(0, "%s: cannot specify source field %s because " + "prerequisites are not satisfied", + orig, spec.src->name); + } + if ((spec.dst_type == NX_LEARN_DST_MATCH + || spec.dst_type == NX_LEARN_DST_LOAD) + && !mf_are_prereqs_ok(spec.dst, &rule.flow)) { + ovs_fatal(0, "%s: cannot specify destination field %s because " + "prerequisites are not satisfied", + orig, spec.dst->name); + } + + /* Update 'rule' to allow for satisfying destination + * prerequisites. */ + if (spec.src_type == NX_LEARN_SRC_IMMEDIATE + && spec.dst_type == NX_LEARN_DST_MATCH + && spec.dst_ofs == 0 + && spec.n_bits == spec.dst->n_bytes * 8) { + union mf_value imm; + + memcpy(&imm, spec.src_imm, spec.dst->n_bytes); + mf_set_value(spec.dst, &imm, &rule); + } + + /* Output the flow_mod_spec. */ + put_u16(b, spec.n_bits | spec.src_type | spec.dst_type); + if (spec.src_type == NX_LEARN_SRC_IMMEDIATE) { + int n_bytes = DIV_ROUND_UP(spec.n_bits, 8); + if (n_bytes % 2) { + ofpbuf_put_zeros(b, 1); + } + ofpbuf_put(b, spec.src_imm, n_bytes); + } else { + put_u32(b, spec.src->nxm_header); + put_u16(b, spec.src_ofs); + } + if (spec.dst_type == NX_LEARN_DST_MATCH || + spec.dst_type == NX_LEARN_DST_LOAD) { + put_u32(b, spec.dst->nxm_header); + put_u16(b, spec.dst_ofs); + } else { + assert(spec.dst_type == NX_LEARN_DST_OUTPUT); + } + } + } + free(orig); + + put_u16(b, 0); + + len = b->size - learn_ofs; + if (len % 8) { + ofpbuf_put_zeros(b, 8 - len % 8); + } + + learn = ofpbuf_at_assert(b, learn_ofs, sizeof *learn); + learn->len = htons(b->size - learn_ofs); +} + +void +learn_format(const struct nx_action_learn *learn, struct ds *s) +{ + struct cls_rule rule; + const void *p, *end; + + cls_rule_init_catchall(&rule, 0); + + ds_put_format(s, "learn(table=%"PRIu8, learn->table_id); + if (learn->idle_timeout != htons(OFP_FLOW_PERMANENT)) { + ds_put_format(s, ",idle_timeout=%"PRIu16, ntohs(learn->idle_timeout)); + } + if (learn->hard_timeout != htons(OFP_FLOW_PERMANENT)) { + ds_put_format(s, ",hard_timeout=%"PRIu16, ntohs(learn->hard_timeout)); + } + if (learn->priority != htons(OFP_DEFAULT_PRIORITY)) { + ds_put_format(s, ",priority=%"PRIu16, ntohs(learn->priority)); + } + if (learn->flags & htons(OFPFF_SEND_FLOW_REM)) { + ds_put_cstr(s, ",OFPFF_SEND_FLOW_REM"); + } + if (learn->flags & htons(~OFPFF_SEND_FLOW_REM)) { + ds_put_format(s, ",***flags=%"PRIu16"***", + ntohs(learn->flags) & ~OFPFF_SEND_FLOW_REM); + } + if (learn->cookie != htonll(0)) { + ds_put_format(s, ",cookie=0x%"PRIx64, ntohll(learn->cookie)); + } + if (!is_all_zeros(learn->pad, sizeof learn->pad)) { + ds_put_cstr(s, ",***nonzero pad***"); + } + + end = (char *) learn + ntohs(learn->len); + for (p = learn + 1; p != end; ) { + uint16_t header = ntohs(get_be16(&p)); + int n_bits = header & NX_LEARN_N_BITS_MASK; + + int src_type = header & NX_LEARN_SRC_MASK; + uint32_t src_header; + int src_ofs; + const uint8_t *src_value; + int src_value_bytes; + + int dst_type = header & NX_LEARN_DST_MASK; + uint32_t dst_header; + int dst_ofs; + const struct mf_field *dst_field; + + int error; + int i; + + if (!header) { + break; + } + + error = learn_check_header(header, (char *) end - (char *) p); + if (error == ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_ARGUMENT)) { + ds_put_format(s, ",***bad flow_mod_spec header %"PRIx16"***)", + header); + return; + } else if (error == ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_LEN)) { + ds_put_format(s, ",***flow_mod_spec at offset %td is %u bytes " + "long but only %td bytes are left***)", + (char *) p - (char *) (learn + 1) - 2, + learn_min_len(header) + 2, + (char *) end - (char *) p + 2); + return; + } + assert(!error); + + /* Get the source. */ + if (src_type == NX_LEARN_SRC_FIELD) { + src_header = ntohl(get_be32(&p)); + src_ofs = ntohs(get_be16(&p)); + src_value_bytes = 0; + src_value = NULL; + } else { + src_header = 0; + src_ofs = 0; + src_value_bytes = 2 * DIV_ROUND_UP(n_bits, 16); + src_value = p; + p = (const void *) ((const uint8_t *) p + src_value_bytes); + } + + /* Get the destination. */ + if (dst_type == NX_LEARN_DST_MATCH || dst_type == NX_LEARN_DST_LOAD) { + dst_header = ntohl(get_be32(&p)); + dst_field = nxm_field_to_mf_field(dst_header); + dst_ofs = ntohs(get_be16(&p)); + } else { + dst_header = 0; + dst_field = NULL; + dst_ofs = 0; + } + + ds_put_char(s, ','); + + switch (src_type | dst_type) { + case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_MATCH: + if (dst_field && dst_ofs == 0 && n_bits == dst_field->n_bits) { + union mf_value value; + uint8_t *bytes = (uint8_t *) &value; + + memset(&value, 0, sizeof value); + memcpy(&bytes[dst_field->n_bytes - src_value_bytes], + src_value, src_value_bytes); + ds_put_format(s, "%s=", dst_field->name); + mf_format(dst_field, &value, NULL, s); + } else { + nxm_format_field_bits(s, dst_header, dst_ofs, n_bits); + ds_put_cstr(s, "=0x"); + for (i = 0; i < src_value_bytes; i++) { + ds_put_format(s, "%02"PRIx8, src_value[i]); + } + } + break; + + case NX_LEARN_SRC_FIELD | NX_LEARN_DST_MATCH: + nxm_format_field_bits(s, dst_header, dst_ofs, n_bits); + if (src_header != dst_header || src_ofs != dst_ofs) { + ds_put_char(s, '='); + nxm_format_field_bits(s, src_header, src_ofs, n_bits); + } + break; + + case NX_LEARN_SRC_IMMEDIATE | NX_LEARN_DST_LOAD: + ds_put_cstr(s, "load:0x"); + for (i = 0; i < src_value_bytes; i++) { + ds_put_format(s, "%02"PRIx8, src_value[i]); + } + ds_put_cstr(s, "->"); + nxm_format_field_bits(s, dst_header, dst_ofs, n_bits); + break; + + case NX_LEARN_SRC_FIELD | NX_LEARN_DST_LOAD: + ds_put_cstr(s, "load:"); + nxm_format_field_bits(s, src_header, src_ofs, n_bits); + ds_put_cstr(s, "->"); + nxm_format_field_bits(s, dst_header, dst_ofs, n_bits); + break; + + case NX_LEARN_SRC_FIELD | NX_LEARN_DST_OUTPUT: + ds_put_cstr(s, "output:"); + nxm_format_field_bits(s, src_header, src_ofs, n_bits); + break; + } + } + if (!is_all_zeros(p, (char *) end - (char *) p)) { + ds_put_cstr(s, ",***nonzero trailer***"); + } + ds_put_char(s, ')'); +} diff --git a/lib/learn.h b/lib/learn.h new file mode 100644 index 000000000..19a9089c2 --- /dev/null +++ b/lib/learn.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LEARN_H +#define LEARN_H 1 + +struct ds; +struct flow; +struct ofpbuf; +struct ofputil_flow_mod; +struct nx_action_learn; + +/* NXAST_LEARN helper functions. + * + * See include/openflow/nicira-ext.h for NXAST_LEARN specification. + */ + +int learn_check(const struct nx_action_learn *, const struct flow *); +void learn_execute(const struct nx_action_learn *, const struct flow *, + struct ofputil_flow_mod *); + +void learn_parse(struct ofpbuf *, char *, const struct flow *); +void learn_format(const struct nx_action_learn *, struct ds *); + +#endif /* learn.h */ diff --git a/lib/meta-flow.c b/lib/meta-flow.c index 9727253ef..140f90224 100644 --- a/lib/meta-flow.c +++ b/lib/meta-flow.c @@ -302,32 +302,6 @@ static const struct mf_field mf_fields[MFF_N_IDS] = { } }; -static bool -is_all_zeros(const uint8_t *field, size_t length) -{ - size_t i; - - for (i = 0; i < length; i++) { - if (field[i] != 0x00) { - return false; - } - } - return true; -} - -static bool -is_all_ones(const uint8_t *field, size_t length) -{ - size_t i; - - for (i = 0; i < length; i++) { - if (field[i] != 0xff) { - return false; - } - } - return true; -} - /* Returns the field with the given 'id'. */ const struct mf_field * mf_from_id(enum mf_field_id id) diff --git a/lib/ofp-parse.c b/lib/ofp-parse.c index a80d45fe6..fd0a72e02 100644 --- a/lib/ofp-parse.c +++ b/lib/ofp-parse.c @@ -26,6 +26,7 @@ #include "bundle.h" #include "byte-order.h" #include "dynamic-string.h" +#include "learn.h" #include "meta-flow.h" #include "netdev.h" #include "multipath.h" @@ -228,7 +229,8 @@ parse_note(struct ofpbuf *b, const char *arg) } static void -parse_named_action(enum ofputil_action_code code, struct ofpbuf *b, char *arg) +parse_named_action(enum ofputil_action_code code, const struct flow *flow, + struct ofpbuf *b, char *arg) { struct ofp_action_dl_addr *oada; struct ofp_action_vlan_pcp *oavp; @@ -332,11 +334,15 @@ parse_named_action(enum ofputil_action_code code, struct ofpbuf *b, char *arg) case OFPUTIL_NXAST_RESUBMIT_TABLE: case OFPUTIL_NXAST_OUTPUT_REG: NOT_REACHED(); + + case OFPUTIL_NXAST_LEARN: + learn_parse(b, arg, flow); + break; } } static void -str_to_action(char *str, struct ofpbuf *b) +str_to_action(const struct flow *flow, char *str, struct ofpbuf *b) { char *pos, *act, *arg; int n_actions; @@ -349,7 +355,7 @@ str_to_action(char *str, struct ofpbuf *b) code = ofputil_action_code_from_name(act); if (code >= 0) { - parse_named_action(code, b, arg); + parse_named_action(code, flow, b, arg); } else if (!strcasecmp(act, "drop")) { /* A drop action in OpenFlow occurs by just not setting * an action. */ @@ -462,6 +468,7 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, } fields; char *string = xstrdup(str_); char *save_ptr = NULL; + char *act_str = NULL; char *name; switch (command) { @@ -503,9 +510,6 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, fm->out_port = OFPP_NONE; fm->flags = 0; if (fields & F_ACTIONS) { - struct ofpbuf actions; - char *act_str; - act_str = strstr(string, "action"); if (!act_str) { ofp_fatal(str_, verbose, "must specify an action"); @@ -518,14 +522,6 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, } act_str++; - - ofpbuf_init(&actions, sizeof(union ofp_action)); - str_to_action(act_str, &actions); - fm->actions = ofpbuf_steal_data(&actions); - fm->n_actions = actions.size / sizeof(union ofp_action); - } else { - fm->actions = NULL; - fm->n_actions = 0; } for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name; name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) { @@ -569,6 +565,17 @@ parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_, } } } + if (fields & F_ACTIONS) { + struct ofpbuf actions; + + ofpbuf_init(&actions, sizeof(union ofp_action)); + str_to_action(&fm->cr.flow, act_str, &actions); + fm->actions = ofpbuf_steal_data(&actions); + fm->n_actions = actions.size / sizeof(union ofp_action); + } else { + fm->actions = NULL; + fm->n_actions = 0; + } free(string); } diff --git a/lib/ofp-print.c b/lib/ofp-print.c index 3b9c58263..23110920b 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -31,6 +31,7 @@ #include "compiler.h" #include "dynamic-string.h" #include "flow.h" +#include "learn.h" #include "multipath.h" #include "nx-match.h" #include "ofp-util.h" @@ -333,6 +334,10 @@ ofp_print_action(struct ds *s, const union ofp_action *a, nxm_decode_n_bits(naor->ofs_nbits)); break; + case OFPUTIL_NXAST_LEARN: + learn_format((const struct nx_action_learn *) a, s); + break; + default: break; } diff --git a/lib/ofp-util.c b/lib/ofp-util.c index 6887217da..00d1af76f 100644 --- a/lib/ofp-util.c +++ b/lib/ofp-util.c @@ -25,6 +25,7 @@ #include "byte-order.h" #include "classifier.h" #include "dynamic-string.h" +#include "learn.h" #include "multipath.h" #include "nx-match.h" #include "ofp-errors.h" @@ -2132,6 +2133,10 @@ validate_actions(const union ofp_action *actions, size_t n_actions, (const struct nx_action_resubmit *) a); break; + case OFPUTIL_NXAST_LEARN: + error = learn_check((const struct nx_action_learn *) a, flow); + break; + case OFPUTIL_OFPAT_STRIP_VLAN: case OFPUTIL_OFPAT_SET_NW_SRC: case OFPUTIL_OFPAT_SET_NW_DST: diff --git a/lib/ofp-util.def b/lib/ofp-util.def index c5d883d67..7868faa4b 100644 --- a/lib/ofp-util.def +++ b/lib/ofp-util.def @@ -34,4 +34,5 @@ NXAST_ACTION(NXAST_BUNDLE, nx_action_bundle, 1, "bundle") NXAST_ACTION(NXAST_BUNDLE_LOAD, nx_action_bundle, 1, "bundle_load") NXAST_ACTION(NXAST_RESUBMIT_TABLE, nx_action_resubmit, 0, NULL) NXAST_ACTION(NXAST_OUTPUT_REG, nx_action_output_reg, 0, NULL) +NXAST_ACTION(NXAST_LEARN, nx_action_learn, 1, "learn") #undef NXAST_ACTION diff --git a/lib/util.c b/lib/util.c index 5e90ecb50..e02f59fb0 100644 --- a/lib/util.c +++ b/lib/util.c @@ -696,3 +696,32 @@ ctz(uint32_t n) #endif } } + +/* Returns true if the 'n' bytes starting at 'p' are zeros. */ +bool +is_all_zeros(const uint8_t *p, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + if (p[i] != 0x00) { + return false; + } + } + return true; +} + +/* Returns true if the 'n' bytes starting at 'p' are 0xff. */ +bool +is_all_ones(const uint8_t *p, size_t n) +{ + size_t i; + + for (i = 0; i < n; i++) { + if (p[i] != 0xff) { + return false; + } + } + return true; +} + diff --git a/lib/util.h b/lib/util.h index 5c8618d33..5ae0775f1 100644 --- a/lib/util.h +++ b/lib/util.h @@ -197,6 +197,9 @@ void ignore(bool x OVS_UNUSED); int log_2_floor(uint32_t); int ctz(uint32_t); +bool is_all_zeros(const uint8_t *, size_t); +bool is_all_ones(const uint8_t *, size_t); + #ifdef __cplusplus } #endif diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c index 93920452a..ac196eeb6 100644 --- a/ofproto/ofproto-dpif.c +++ b/ofproto/ofproto-dpif.c @@ -32,6 +32,7 @@ #include "fail-open.h" #include "hmapx.h" #include "lacp.h" +#include "learn.h" #include "mac-learning.h" #include "multipath.h" #include "netdev.h" @@ -166,6 +167,12 @@ struct action_xlate_ctx { * revalidating without a packet to refer to. */ const struct ofpbuf *packet; + /* Should OFPP_NORMAL MAC learning and NXAST_LEARN actions execute? We + * want to execute them if we are actually processing a packet, or if we + * are accounting for packets that the datapath has processed, but not if + * we are just revalidating. */ + bool may_learn; + /* If nonnull, called just before executing a resubmit action. * * This is normally null so the client has to set it manually after @@ -176,9 +183,11 @@ struct action_xlate_ctx { * to look at them after it returns. */ struct ofpbuf *odp_actions; /* Datapath actions. */ - tag_type tags; /* Tags associated with OFPP_NORMAL actions. */ + tag_type tags; /* Tags associated with actions. */ bool may_set_up_flow; /* True ordinarily; false if the actions must * be reassessed for every packet. */ + bool has_learn; /* Actions include NXAST_LEARN? */ + bool has_normal; /* Actions output to OFPP_NORMAL? */ uint16_t nf_output_iface; /* Output interface index for NetFlow. */ /* xlate_actions() initializes and uses these members, but the client has no @@ -229,6 +238,8 @@ struct facet { bool installed; /* Installed in datapath? */ bool may_install; /* True ordinarily; false if actions must * be reassessed for every packet. */ + bool has_learn; /* Actions include NXAST_LEARN? */ + bool has_normal; /* Actions output to OFPP_NORMAL? */ size_t actions_len; /* Number of bytes in actions[]. */ struct nlattr *actions; /* Datapath actions. */ tag_type tags; /* Tags. */ @@ -2168,6 +2179,8 @@ facet_make_actions(struct ofproto_dpif *p, struct facet *facet, odp_actions = xlate_actions(&ctx, rule->up.actions, rule->up.n_actions); facet->tags = ctx.tags; facet->may_install = ctx.may_set_up_flow; + facet->has_learn = ctx.has_learn; + facet->has_normal = ctx.has_normal; facet->nf_flow.output_iface = ctx.nf_output_iface; if (facet->actions_len != odp_actions->size @@ -2239,12 +2252,9 @@ static void facet_account(struct ofproto_dpif *ofproto, struct facet *facet) { uint64_t n_bytes; - struct ofbundle *in_bundle; const struct nlattr *a; - tag_type dummy = 0; unsigned int left; ovs_be16 vlan_tci; - int vlan; if (facet->byte_count <= facet->accounted_bytes) { return; @@ -2252,22 +2262,19 @@ facet_account(struct ofproto_dpif *ofproto, struct facet *facet) n_bytes = facet->byte_count - facet->accounted_bytes; facet->accounted_bytes = facet->byte_count; - /* Test that 'tags' is nonzero to ensure that only flows that include an - * OFPP_NORMAL action are used for learning and bond slave rebalancing. - * This works because OFPP_NORMAL always sets a nonzero tag value. - * - * Feed information from the active flows back into the learning table to + /* Feed information from the active flows back into the learning table to * ensure that table is always in sync with what is actually flowing * through the datapath. */ - if (!facet->tags - || !is_admissible(ofproto, &facet->flow, false, &dummy, - &vlan, &in_bundle)) { - return; - } + if (facet->has_learn || facet->has_normal) { + struct action_xlate_ctx ctx; - update_learning_table(ofproto, &facet->flow, vlan, in_bundle); + action_xlate_ctx_init(&ctx, ofproto, &facet->flow, NULL); + ctx.may_learn = true; + ofpbuf_delete(xlate_actions(&ctx, facet->rule->up.actions, + facet->rule->up.n_actions)); + } - if (!ofproto->has_bonded_bundles) { + if (!facet->has_normal || !ofproto->has_bonded_bundles) { return; } @@ -2491,6 +2498,8 @@ facet_revalidate(struct ofproto_dpif *ofproto, struct facet *facet) facet->tags = ctx.tags; facet->nf_flow.output_iface = ctx.nf_output_iface; facet->may_install = ctx.may_set_up_flow; + facet->has_learn = ctx.has_learn; + facet->has_normal = ctx.has_normal; if (actions_changed) { free(facet->actions); facet->actions_len = odp_actions->size; @@ -3168,6 +3177,26 @@ slave_enabled_cb(uint16_t ofp_port, void *ofproto_) } } +static void +xlate_learn_action(struct action_xlate_ctx *ctx, + const struct nx_action_learn *learn) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1); + struct ofputil_flow_mod fm; + int error; + + learn_execute(learn, &ctx->flow, &fm); + + error = ofproto_flow_mod(&ctx->ofproto->up, &fm); + if (error && !VLOG_DROP_WARN(&rl)) { + char *msg = ofputil_error_to_string(error); + VLOG_WARN("learning action failed to modify flow table (%s)", msg); + free(msg); + } + + free(fm.actions); +} + static void do_xlate_actions(const union ofp_action *in, size_t n_in, struct action_xlate_ctx *ctx) @@ -3325,6 +3354,13 @@ do_xlate_actions(const union ofp_action *in, size_t n_in, naor = (const struct nx_action_output_reg *) ia; xlate_output_reg_action(ctx, naor); break; + + case OFPUTIL_NXAST_LEARN: + ctx->has_learn = true; + if (ctx->may_learn) { + xlate_learn_action(ctx, (const struct nx_action_learn *) ia); + } + break; } } } @@ -3337,6 +3373,7 @@ action_xlate_ctx_init(struct action_xlate_ctx *ctx, ctx->ofproto = ofproto; ctx->flow = *flow; ctx->packet = packet; + ctx->may_learn = packet != NULL; ctx->resubmit_hook = NULL; } @@ -3349,6 +3386,8 @@ xlate_actions(struct action_xlate_ctx *ctx, ctx->odp_actions = ofpbuf_new(512); ctx->tags = 0; ctx->may_set_up_flow = true; + ctx->has_learn = false; + ctx->has_normal = false; ctx->nf_output_iface = NF_OUT_DROP; ctx->recurse = 0; ctx->priority = 0; @@ -3821,6 +3860,7 @@ is_admissible(struct ofproto_dpif *ofproto, const struct flow *flow, "port %"PRIu16, ofproto->up.name, flow->in_port); } + *vlanp = -1; return false; } *vlanp = vlan = flow_get_vlan(ofproto, flow, in_bundle, have_packet); @@ -3879,6 +3919,8 @@ xlate_normal(struct action_xlate_ctx *ctx) struct mac_entry *mac; int vlan; + ctx->has_normal = true; + /* Check whether we should drop packets in this flow. */ if (!is_admissible(ctx->ofproto, &ctx->flow, ctx->packet != NULL, &ctx->tags, &vlan, &in_bundle)) { @@ -3886,8 +3928,8 @@ xlate_normal(struct action_xlate_ctx *ctx) goto done; } - /* Learn source MAC (but don't try to learn from revalidation). */ - if (ctx->packet) { + /* Learn source MAC. */ + if (ctx->may_learn) { update_learning_table(ctx->ofproto, &ctx->flow, vlan, in_bundle); } diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h index b1c697223..3037bb2e9 100644 --- a/ofproto/ofproto-provider.h +++ b/ofproto/ofproto-provider.h @@ -26,6 +26,8 @@ #include "shash.h" #include "timeval.h" +struct ofputil_flow_mod; + /* An OpenFlow switch. * * With few exceptions, ofproto implementations may look at these fields but @@ -957,6 +959,18 @@ extern const struct ofproto_class ofproto_dpif_class; int ofproto_class_register(const struct ofproto_class *); int ofproto_class_unregister(const struct ofproto_class *); +/* ofproto_flow_mod() returns this value if the flow_mod could not be processed + * because it overlaps with an ongoing flow table operation that has not yet + * completed. The caller should retry the operation later. + * + * ofproto.c also uses this value internally for additional (similar) purposes. + * + * This particular value is a good choice because it is negative (so it won't + * collide with any errno value or any value returned by ofp_mkerr()) and large + * (so it won't accidentally collide with EOF or a negative errno value). */ +enum { OFPROTO_POSTPONE = -100000 }; + +int ofproto_flow_mod(struct ofproto *, const struct ofputil_flow_mod *); void ofproto_add_flow(struct ofproto *, const struct cls_rule *, const union ofp_action *, size_t n_actions); bool ofproto_delete_flow(struct ofproto *, const struct cls_rule *); diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c index b96fa6e67..979335543 100644 --- a/ofproto/ofproto.c +++ b/ofproto/ofproto.c @@ -139,16 +139,10 @@ static int add_flow(struct ofproto *, struct ofconn *, const struct ofputil_flow_mod *, const struct ofp_header *); -/* This return value tells handle_openflow() that processing of the current - * OpenFlow message must be postponed until some ongoing operations have - * completed. - * - * This particular value is a good choice because it is negative (so it won't - * collide with any errno value or any value returned by ofp_mkerr()) and large - * (so it won't accidentally collide with EOF or a negative errno value). */ -enum { OFPROTO_POSTPONE = -100000 }; - static bool handle_openflow(struct ofconn *, struct ofpbuf *); +static int handle_flow_mod__(struct ofproto *, struct ofconn *, + const struct ofputil_flow_mod *, + const struct ofp_header *); static void update_port(struct ofproto *, const char *devname); static int init_ports(struct ofproto *); @@ -1062,6 +1056,18 @@ ofproto_add_flow(struct ofproto *ofproto, const struct cls_rule *cls_rule, } } +/* Executes the flow modification specified in 'fm'. Returns 0 on success, an + * OpenFlow error code as encoded by ofp_mkerr() on failure, or + * OFPROTO_POSTPONE if the operation cannot be initiated now but may be retried + * later. + * + * This is a helper function for in-band control and fail-open. */ +int +ofproto_flow_mod(struct ofproto *ofproto, const struct ofputil_flow_mod *fm) +{ + return handle_flow_mod__(ofproto, NULL, fm, NULL); +} + /* Searches for a rule with matching criteria exactly equal to 'target' in * ofproto's table 0 and, if it finds one, deletes it. * @@ -2190,8 +2196,9 @@ is_flow_deletion_pending(const struct ofproto *ofproto, * in which no matching flow already exists in the flow table. * * Adds the flow specified by 'ofm', which is followed by 'n_actions' - * ofp_actions, to the ofproto's flow table. Returns 0 on success or an - * OpenFlow error code as encoded by ofp_mkerr() on failure. + * ofp_actions, to the ofproto's flow table. Returns 0 on success, an OpenFlow + * error code as encoded by ofp_mkerr() on failure, or OFPROTO_POSTPONE if the + * operation cannot be initiated now but may be retried later. * * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id, * if any. */ @@ -2472,7 +2479,6 @@ ofproto_rule_expire(struct rule *rule, uint8_t reason) static int handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh) { - struct ofproto *ofproto = ofconn_get_ofproto(ofconn); struct ofputil_flow_mod fm; int error; @@ -2481,11 +2487,6 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh) return error; } - if (ofproto->n_pending >= 50) { - assert(!list_is_empty(&ofproto->pending)); - return OFPROTO_POSTPONE; - } - error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_flow_mod_table_id(ofconn)); if (error) { @@ -2500,24 +2501,37 @@ handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh) return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_ALL_TABLES_FULL); } - switch (fm.command) { + return handle_flow_mod__(ofconn_get_ofproto(ofconn), ofconn, &fm, oh); +} + +static int +handle_flow_mod__(struct ofproto *ofproto, struct ofconn *ofconn, + const struct ofputil_flow_mod *fm, + const struct ofp_header *oh) +{ + if (ofproto->n_pending >= 50) { + assert(!list_is_empty(&ofproto->pending)); + return OFPROTO_POSTPONE; + } + + switch (fm->command) { case OFPFC_ADD: - return add_flow(ofproto, ofconn, &fm, oh); + return add_flow(ofproto, ofconn, fm, oh); case OFPFC_MODIFY: - return modify_flows_loose(ofproto, ofconn, &fm, oh); + return modify_flows_loose(ofproto, ofconn, fm, oh); case OFPFC_MODIFY_STRICT: - return modify_flow_strict(ofproto, ofconn, &fm, oh); + return modify_flow_strict(ofproto, ofconn, fm, oh); case OFPFC_DELETE: - return delete_flows_loose(ofproto, ofconn, &fm, oh); + return delete_flows_loose(ofproto, ofconn, fm, oh); case OFPFC_DELETE_STRICT: - return delete_flow_strict(ofproto, ofconn, &fm, oh); + return delete_flow_strict(ofproto, ofconn, fm, oh); default: - if (fm.command > 0xff) { + if (fm->command > 0xff) { VLOG_WARN_RL(&rl, "flow_mod has explicit table_id but " "flow_mod_table_id extension is not enabled"); } diff --git a/tests/automake.mk b/tests/automake.mk index 28b4e1d0e..0e135f0bb 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -18,6 +18,7 @@ TESTSUITE_AT = \ tests/odp.at \ tests/multipath.at \ tests/autopath.at \ + tests/learn.at \ tests/vconn.at \ tests/file_name.at \ tests/aes128.at \ diff --git a/tests/learn.at b/tests/learn.at new file mode 100644 index 000000000..20a012de0 --- /dev/null +++ b/tests/learn.at @@ -0,0 +1,88 @@ +AT_BANNER([learning action]) + +AT_SETUP([learning action - parsing and formatting]) +AT_DATA([flows.txt], [[ +actions=learn() +actions=learn(NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[], load:10->NXM_NX_REG0[5..10]) +actions=learn(table=1,idle_timeout=1, hard_timeout=2, priority=10, cookie=0xfedcba9876543210, in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) +]]) +AT_CHECK([ovs-ofctl parse-flows flows.txt], [0], +[[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1) +OFPT_FLOW_MOD (xid=0x2): ADD actions=learn(table=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[],load:0x000a->NXM_NX_REG0[5..10]) +OFPT_FLOW_MOD (xid=0x3): ADD actions=learn(table=1,idle_timeout=1,hard_timeout=2,priority=10,cookie=0xfedcba9876543210,in_port=99,NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:NXM_OF_IN_PORT[]->NXM_NX_REG1[16..31]) +]]) +AT_CLEANUP + +AT_SETUP([learning action - satisfied prerequisites]) +AT_DATA([flows.txt], +[[actions=learn(eth_type=0x800,load:5->NXM_OF_IP_DST[]) +ip,actions=learn(load:NXM_OF_IP_DST[]->NXM_NX_REG1[]) +ip,actions=learn(eth_type=0x800,NXM_OF_IP_DST[]) +]]) +AT_CHECK([ovs-ofctl parse-flows flows.txt], [0], +[[OFPT_FLOW_MOD (xid=0x1): ADD actions=learn(table=1,eth_type=0x800,load:0x00000005->NXM_OF_IP_DST[]) +OFPT_FLOW_MOD (xid=0x2): ADD ip actions=learn(table=1,load:NXM_OF_IP_DST[]->NXM_NX_REG1[]) +OFPT_FLOW_MOD (xid=0x3): ADD ip actions=learn(table=1,eth_type=0x800,NXM_OF_IP_DST[]) +]]) +AT_CLEANUP + +AT_SETUP([learning action - invalid prerequisites]) +AT_CHECK([[ovs-ofctl parse-flow 'actions=learn(load:5->NXM_OF_IP_DST[])']], + [1], [], + [[ovs-ofctl: load:5->NXM_OF_IP_DST[]: cannot specify destination field ip_dst because prerequisites are not satisfied +]]) +AT_CHECK([[ovs-ofctl parse-flow 'actions=learn(load:NXM_OF_IP_DST[]->NXM_NX_REG1[])']], + [1], [], + [[ovs-ofctl: load:NXM_OF_IP_DST[]->NXM_NX_REG1[]: cannot specify source field ip_dst because prerequisites are not satisfied +]]) +AT_CLEANUP + +AT_SETUP([learning action - standard VLAN+MAC learning]) +OFPROTO_START([--ports=dummy@eth0,dummy@eth1,dummy@eth2]) +# Set up flow table for VLAN+MAC learning. +AT_DATA([flows.txt], [[ +table=0 actions=learn(table=1, hard_timeout=60, NXM_OF_VLAN_TCI[0..11], NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[]), resubmit(,1) +table=1 priority=0 actions=flood +]]) +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) + +# Trace an ARP packet arriving on port 3, to create a MAC learning entry. +AT_CHECK([ovs-appctl -t test-openflowd ofproto/trace br0 'in_port(3),eth(src=50:54:00:00:00:05,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=192.168.0.1,tip=192.168.0.2,op=1,sha=50:54:00:00:00:05,tha=00:00:00:00:00:00)' -generate], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 2,0,1 +]) + +# Check for the MAC learning entry. +AT_CHECK([ovs-ofctl dump-flows br0 table=1 | STRIP_XIDS | STRIP_DURATION | sort], [0], [dnl + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, hard_timeout=60,vlan_tci=0x0000/0x0fff,dl_dst=50:54:00:00:00:05 actions=output:3 + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, priority=0 actions=FLOOD +NXST_FLOW reply: +]) + +# Trace a packet arrival destined for the learned MAC. +# (This will also learn a MAC.) +AT_CHECK([ovs-appctl -t test-openflowd ofproto/trace br0 'in_port(1),eth(src=50:54:00:00:00:06,dst=50:54:00:00:00:05),eth_type(0x0806),arp(sip=192.168.0.2,tip=192.168.0.1,op=2,sha=50:54:00:00:00:06,tha=50:54:00:00:00:05)' -generate], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 3 +]) + +# Check for both MAC learning entries. +AT_CHECK([ovs-ofctl dump-flows br0 table=1 | STRIP_XIDS | STRIP_DURATION | sort], [0], [dnl + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, hard_timeout=60,vlan_tci=0x0000/0x0fff,dl_dst=50:54:00:00:00:05 actions=output:3 + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, hard_timeout=60,vlan_tci=0x0000/0x0fff,dl_dst=50:54:00:00:00:06 actions=output:1 + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, priority=0 actions=FLOOD +NXST_FLOW reply: +]) + +# Trace a packet arrival that updates the first learned MAC entry. +AT_CHECK([ovs-appctl -t test-openflowd ofproto/trace br0 'in_port(2),eth(src=50:54:00:00:00:05,dst=ff:ff:ff:ff:ff:ff),eth_type(0x0806),arp(sip=192.168.0.1,tip=192.168.0.2,op=1,sha=50:54:00:00:00:05,tha=00:00:00:00:00:00)' -generate], [0], [stdout]) +AT_CHECK([tail -1 stdout], [0], [Datapath actions: 3,0,1 +]) + +# Check that the MAC learning entry was updated. +AT_CHECK([ovs-ofctl dump-flows br0 table=1 | STRIP_XIDS | STRIP_DURATION | sort], [0], [dnl + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, hard_timeout=60,vlan_tci=0x0000/0x0fff,dl_dst=50:54:00:00:00:05 actions=output:2 + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, hard_timeout=60,vlan_tci=0x0000/0x0fff,dl_dst=50:54:00:00:00:06 actions=output:1 + cookie=0x0, duration=?s, table=1, n_packets=0, n_bytes=0, priority=0 actions=FLOOD +NXST_FLOW reply: +]) +OFPROTO_STOP +AT_CLEANUP diff --git a/tests/testsuite.at b/tests/testsuite.at index b394cb6ff..ea5208f44 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -49,6 +49,7 @@ m4_include([tests/ovs-ofctl.at]) m4_include([tests/odp.at]) m4_include([tests/multipath.at]) m4_include([tests/autopath.at]) +m4_include([tests/learn.at]) m4_include([tests/vconn.at]) m4_include([tests/file_name.at]) m4_include([tests/aes128.at]) diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index cddde1f5c..100faf023 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -775,6 +775,68 @@ between OpenFlow ports 4 and 8 using the Highest Random Weight algorithm, and writes the selection to \fBNXM_NX_REG0[]\fR. .IP Refer to \fBnicira\-ext.h\fR for more details. +. +.IP "\fBlearn(\fIargument\fR[\fB,\fIargument\fR]...\fB)\fR" +This action adds or modifies a flow in an OpenFlow table, similar to +\fBovs\-ofctl \-\-strict mod\-flows\fR. The arguments specify the +flow's match fields, actions, and other properties, as follows. At +least one match criterion and one action argument should ordinarily be +specified. +.RS +.IP \fBidle_timeout=\fIseconds\fR +.IQ \fBhard_timeout=\fIseconds\fR +.IQ \fBpriority=\fIvalue\fR +These key-value pairs have the same meaning as in the usual +\fBovs\-ofctl\fR flow syntax. +. +.IP \fBtable=\fInumber\fR +The table in which the new flow should be inserted. Specify a decimal +number between 0 and 254. The default, if \fBtable\fR is unspecified, +is table 1. +. +.IP \fIfield\fB=\fIvalue\fR +.IQ \fIfield\fB[\fIstart\fB..\fIend\fB]=\fIsrc\fB[\fIstart\fB..\fIend\fB]\fR +.IQ \fIfield\fB[\fIstart\fB..\fIend\fB]\fR +Adds a match criterion to the new flow. +.IP +The first form specifies that \fIfield\fR must match the literal +\fIvalue\fR, e.g. \fBdl_type=0x0800\fR. All of the fields and values +for \fBovs\-ofctl\fR flow syntax are available with their usual +meanings. +.IP +The second form specifies that \fIfield\fB[\fIstart\fB..\fIend\fB]\fR +in the new flow must match \fIsrc\fB[\fIstart\fB..\fIend\fB]\fR taken +from the flow currently being processed. +.IP +The third form is a shorthand for the second form. It specifies that +\fIfield\fB[\fIstart\fB..\fIend\fB]\fR in the new flow must match +\fIfield\fB[\fIstart\fB..\fIend\fB]\fR taken from the flow currently +being processed. +. +.IP \fBload:\fIvalue\fB\->\fIdst\fB[\fIstart\fB..\fIend\fB] +.IQ \fBload:\fIsrc\fB[\fIstart\fB..\fIend\fB]\->\fIdst\fB[\fIstart\fB..\fIend\fB] +. +Adds a \fBload\fR action to the new flow. +.IP +The first form loads the literal \fIvalue\fR into bits \fIstart\fR +through \fIend\fR, inclusive, in field \fIdst\fR. Its syntax is the +same as the \fBload\fR action described earlier in this section. +.IP +The second form loads \fIsrc\fB[\fIstart\fB..\fIend\fB]\fR, a value +from the flow currently being processed, into bits \fIstart\fR +through \fIend\fR, inclusive, in field \fIdst\fR. +. +.IP \fBoutput:\fIfield\fB[\fIstart\fB..\fIend\fB]\fR +Add an \fBoutput\fR action to the new flow's actions, that outputs to +the OpenFlow port taken from \fIfield\fB[\fIstart\fB..\fIend\fB]\fR, +which must be an NXM field as described above. +.RE +.IP +For best performance, segregate learned flows into a table (using +\fBtable=\fInumber\fR) that is not used for any other flows except +possibly for a lowest-priority ``catch-all'' flow, that is, a flow +with no match criteria. (This is why the default \fBtable\fR is 1, to +keep the learned flows separate from the primary flow table 0.) .RE . .PP