2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 01:51:26 +00:00

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
This commit is contained in:
Ben Pfaff 2011-09-12 16:19:57 -07:00
parent 308881afb6
commit 75a7504356
19 changed files with 1246 additions and 83 deletions

1
NEWS
View File

@ -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:

View File

@ -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:

View File

@ -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 \

662
lib/learn.c Normal file
View File

@ -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 <config.h>
#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, ')');
}

38
lib/learn.h Normal file
View File

@ -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 */

View File

@ -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)

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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:

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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;
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));
}
update_learning_table(ofproto, &facet->flow, vlan, in_bundle);
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);
}

View File

@ -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 *);

View File

@ -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");
}

View File

@ -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 \

88
tests/learn.at Normal file
View File

@ -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

View File

@ -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])

View File

@ -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