mirror of
https://github.com/openvswitch/ovs
synced 2025-10-29 15:28:56 +00:00
OpenFlow 1.0 has special reserved ports in the range 0xfff8 to 0xffff.
OpenFlow 1.1 and later has the same ports in the range 0xfffffff8 to
0xffffffff and allows the OF1.0 range to be used for ordinary ("physical")
switch ports. This means that, naively, the meaning of a port number in
the range 0xfff8 to 0xffff given on the ovs-ofctl command line depends on
the protocol in use. This commit implements something a little smarter:
- Accept keyword names (e.g. LOCAL) for special reserved ports
everywhere that such a port can plausibly be used (previously they
were only accepted in some places).
- Translate 0xfff8...0xffff to 0xfffffff8...0xffffffff for now, since
OF1.1+ isn't in widespread use and those particular ports aren't
likely to be in use in OF1.1+ anyway.
- Log warnings about those ports when they are specified by number, to
allow users to fix their invocations.
Also:
- Accept the OF1.1+ port numbers for these ports, without warning, for
compatibility with the upcoming OF1.1+ support.
- Stop accepting port number 0, which has never been a valid port
number in OpenFlow 1.0 and later. (This required fixing some tests
that inadvertently used this port number).
Signed-off-by: Ben Pfaff <blp@nicira.com>
Acked-by: Simon Horman <horms@verge.net.au>
963 lines
27 KiB
C
963 lines
27 KiB
C
/*
|
|
* Copyright (c) 2010, 2011, 2012 Nicira, Inc.
|
|
*
|
|
* 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 "ofp-parse.h"
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "autopath.h"
|
|
#include "bundle.h"
|
|
#include "byte-order.h"
|
|
#include "dynamic-string.h"
|
|
#include "learn.h"
|
|
#include "meta-flow.h"
|
|
#include "multipath.h"
|
|
#include "netdev.h"
|
|
#include "nx-match.h"
|
|
#include "ofp-actions.h"
|
|
#include "ofp-util.h"
|
|
#include "ofpbuf.h"
|
|
#include "openflow/openflow.h"
|
|
#include "packets.h"
|
|
#include "socket-util.h"
|
|
#include "vconn.h"
|
|
#include "vlog.h"
|
|
|
|
VLOG_DEFINE_THIS_MODULE(ofp_parse);
|
|
|
|
static void ofp_fatal(const char *flow, bool verbose, const char *format, ...)
|
|
NO_RETURN;
|
|
|
|
static uint8_t
|
|
str_to_table_id(const char *str)
|
|
{
|
|
int table_id;
|
|
|
|
if (!str_to_int(str, 10, &table_id) || table_id < 0 || table_id > 255) {
|
|
ovs_fatal(0, "invalid table \"%s\"", str);
|
|
}
|
|
return table_id;
|
|
}
|
|
|
|
static uint16_t
|
|
str_to_u16(const char *str, const char *name)
|
|
{
|
|
int value;
|
|
|
|
if (!str_to_int(str, 0, &value) || value < 0 || value > 65535) {
|
|
ovs_fatal(0, "invalid %s \"%s\"", name, str);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static uint32_t
|
|
str_to_u32(const char *str)
|
|
{
|
|
char *tail;
|
|
uint32_t value;
|
|
|
|
if (!str[0]) {
|
|
ovs_fatal(0, "missing required numeric argument");
|
|
}
|
|
|
|
errno = 0;
|
|
value = strtoul(str, &tail, 0);
|
|
if (errno == EINVAL || errno == ERANGE || *tail) {
|
|
ovs_fatal(0, "invalid numeric format %s", str);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static uint64_t
|
|
str_to_u64(const char *str)
|
|
{
|
|
char *tail;
|
|
uint64_t value;
|
|
|
|
if (!str[0]) {
|
|
ovs_fatal(0, "missing required numeric argument");
|
|
}
|
|
|
|
errno = 0;
|
|
value = strtoull(str, &tail, 0);
|
|
if (errno == EINVAL || errno == ERANGE || *tail) {
|
|
ovs_fatal(0, "invalid numeric format %s", str);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static void
|
|
str_to_mac(const char *str, uint8_t mac[6])
|
|
{
|
|
if (sscanf(str, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac))
|
|
!= ETH_ADDR_SCAN_COUNT) {
|
|
ovs_fatal(0, "invalid mac address %s", str);
|
|
}
|
|
}
|
|
|
|
static void
|
|
str_to_ip(const char *str, ovs_be32 *ip)
|
|
{
|
|
struct in_addr in_addr;
|
|
|
|
if (lookup_ip(str, &in_addr)) {
|
|
ovs_fatal(0, "%s: could not convert to IP address", str);
|
|
}
|
|
*ip = in_addr.s_addr;
|
|
}
|
|
|
|
static void
|
|
parse_enqueue(char *arg, struct ofpbuf *ofpacts)
|
|
{
|
|
char *sp = NULL;
|
|
char *port = strtok_r(arg, ":q", &sp);
|
|
char *queue = strtok_r(NULL, "", &sp);
|
|
struct ofpact_enqueue *enqueue;
|
|
|
|
if (port == NULL || queue == NULL) {
|
|
ovs_fatal(0, "\"enqueue\" syntax is \"enqueue:PORT:QUEUE\"");
|
|
}
|
|
|
|
enqueue = ofpact_put_ENQUEUE(ofpacts);
|
|
enqueue->port = str_to_u32(port);
|
|
enqueue->queue = str_to_u32(queue);
|
|
}
|
|
|
|
static void
|
|
parse_output(char *arg, struct ofpbuf *ofpacts)
|
|
{
|
|
if (strchr(arg, '[')) {
|
|
struct ofpact_output_reg *output_reg;
|
|
|
|
output_reg = ofpact_put_OUTPUT_REG(ofpacts);
|
|
mf_parse_subfield(&output_reg->src, arg);
|
|
output_reg->max_len = UINT16_MAX;
|
|
} else {
|
|
struct ofpact_output *output;
|
|
|
|
output = ofpact_put_OUTPUT(ofpacts);
|
|
output->port = str_to_u32(arg);
|
|
output->max_len = output->port == OFPP_CONTROLLER ? UINT16_MAX : 0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_resubmit(char *arg, struct ofpbuf *ofpacts)
|
|
{
|
|
struct ofpact_resubmit *resubmit;
|
|
char *in_port_s, *table_s;
|
|
|
|
resubmit = ofpact_put_RESUBMIT(ofpacts);
|
|
|
|
in_port_s = strsep(&arg, ",");
|
|
if (in_port_s && in_port_s[0]) {
|
|
resubmit->in_port = ofputil_port_from_string(in_port_s);
|
|
if (!resubmit->in_port) {
|
|
ovs_fatal(0, "%s: resubmit to unknown port", in_port_s);
|
|
}
|
|
} else {
|
|
resubmit->in_port = OFPP_IN_PORT;
|
|
}
|
|
|
|
table_s = strsep(&arg, ",");
|
|
resubmit->table_id = table_s && table_s[0] ? str_to_u32(table_s) : 255;
|
|
|
|
if (resubmit->in_port == OFPP_IN_PORT && resubmit->table_id == 255) {
|
|
ovs_fatal(0, "at least one \"in_port\" or \"table\" must be specified "
|
|
" on resubmit");
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_note(const char *arg, struct ofpbuf *ofpacts)
|
|
{
|
|
struct ofpact_note *note;
|
|
|
|
note = ofpact_put_NOTE(ofpacts);
|
|
while (*arg != '\0') {
|
|
uint8_t byte;
|
|
bool ok;
|
|
|
|
if (*arg == '.') {
|
|
arg++;
|
|
}
|
|
if (*arg == '\0') {
|
|
break;
|
|
}
|
|
|
|
byte = hexits_value(arg, 2, &ok);
|
|
if (!ok) {
|
|
ovs_fatal(0, "bad hex digit in `note' argument");
|
|
}
|
|
ofpbuf_put(ofpacts, &byte, 1);
|
|
|
|
note = ofpacts->l2;
|
|
note->length++;
|
|
|
|
arg += 2;
|
|
}
|
|
ofpact_update_len(ofpacts, ¬e->ofpact);
|
|
}
|
|
|
|
static void
|
|
parse_fin_timeout(struct ofpbuf *b, char *arg)
|
|
{
|
|
struct ofpact_fin_timeout *oft = ofpact_put_FIN_TIMEOUT(b);
|
|
char *key, *value;
|
|
|
|
while (ofputil_parse_key_value(&arg, &key, &value)) {
|
|
if (!strcmp(key, "idle_timeout")) {
|
|
oft->fin_idle_timeout = str_to_u16(value, key);
|
|
} else if (!strcmp(key, "hard_timeout")) {
|
|
oft->fin_hard_timeout = str_to_u16(value, key);
|
|
} else {
|
|
ovs_fatal(0, "invalid key '%s' in 'fin_timeout' argument", key);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_controller(struct ofpbuf *b, char *arg)
|
|
{
|
|
enum ofp_packet_in_reason reason = OFPR_ACTION;
|
|
uint16_t controller_id = 0;
|
|
uint16_t max_len = UINT16_MAX;
|
|
|
|
if (!arg[0]) {
|
|
/* Use defaults. */
|
|
} else if (strspn(arg, "0123456789") == strlen(arg)) {
|
|
max_len = str_to_u16(arg, "max_len");
|
|
} else {
|
|
char *name, *value;
|
|
|
|
while (ofputil_parse_key_value(&arg, &name, &value)) {
|
|
if (!strcmp(name, "reason")) {
|
|
if (!ofputil_packet_in_reason_from_string(value, &reason)) {
|
|
ovs_fatal(0, "unknown reason \"%s\"", value);
|
|
}
|
|
} else if (!strcmp(name, "max_len")) {
|
|
max_len = str_to_u16(value, "max_len");
|
|
} else if (!strcmp(name, "id")) {
|
|
controller_id = str_to_u16(value, "id");
|
|
} else {
|
|
ovs_fatal(0, "unknown key \"%s\" parsing controller action",
|
|
name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reason == OFPR_ACTION && controller_id == 0) {
|
|
struct ofpact_output *output;
|
|
|
|
output = ofpact_put_OUTPUT(b);
|
|
output->port = OFPP_CONTROLLER;
|
|
output->max_len = max_len;
|
|
} else {
|
|
struct ofpact_controller *controller;
|
|
|
|
controller = ofpact_put_CONTROLLER(b);
|
|
controller->max_len = max_len;
|
|
controller->reason = reason;
|
|
controller->controller_id = controller_id;
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_dec_ttl(struct ofpbuf *b, char *arg)
|
|
{
|
|
struct ofpact_cnt_ids *ids;
|
|
|
|
ids = ofpact_put_DEC_TTL(b);
|
|
|
|
if (*arg == '\0') {
|
|
uint16_t id = 0;
|
|
|
|
ids->ofpact.compat = OFPUTIL_NXAST_DEC_TTL;
|
|
ofpbuf_put(b, &id, sizeof id);
|
|
ids = b->l2;
|
|
ids->n_controllers++;
|
|
} else {
|
|
char *cntr;
|
|
|
|
ids->ofpact.compat = OFPUTIL_NXAST_DEC_TTL_CNT_IDS;
|
|
for (cntr = strtok_r(arg, ", ", &arg); cntr != NULL;
|
|
cntr = strtok_r(NULL, ", ", &arg)) {
|
|
uint16_t id = atoi(cntr);
|
|
|
|
ofpbuf_put(b, &id, sizeof id);
|
|
ids = b->l2;
|
|
ids->n_controllers++;
|
|
}
|
|
if (!ids->n_controllers) {
|
|
ovs_fatal(0, "dec_ttl_cnt_ids: expected at least one controller "
|
|
"id.");
|
|
}
|
|
|
|
}
|
|
ofpact_update_len(b, &ids->ofpact);
|
|
}
|
|
|
|
static void
|
|
parse_named_action(enum ofputil_action_code code, const struct flow *flow,
|
|
char *arg, struct ofpbuf *ofpacts)
|
|
{
|
|
struct ofpact_tunnel *tunnel;
|
|
uint16_t vid;
|
|
ovs_be32 ip;
|
|
uint8_t pcp;
|
|
uint8_t tos;
|
|
|
|
switch (code) {
|
|
case OFPUTIL_ACTION_INVALID:
|
|
NOT_REACHED();
|
|
|
|
case OFPUTIL_OFPAT10_OUTPUT:
|
|
case OFPUTIL_OFPAT11_OUTPUT:
|
|
parse_output(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_VLAN_VID:
|
|
case OFPUTIL_OFPAT11_SET_VLAN_VID:
|
|
vid = str_to_u32(arg);
|
|
if (vid & ~VLAN_VID_MASK) {
|
|
ovs_fatal(0, "%s: not a valid VLAN VID", arg);
|
|
}
|
|
ofpact_put_SET_VLAN_VID(ofpacts)->vlan_vid = vid;
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_VLAN_PCP:
|
|
case OFPUTIL_OFPAT11_SET_VLAN_PCP:
|
|
pcp = str_to_u32(arg);
|
|
if (pcp & ~7) {
|
|
ovs_fatal(0, "%s: not a valid VLAN PCP", arg);
|
|
}
|
|
ofpact_put_SET_VLAN_PCP(ofpacts)->vlan_pcp = pcp;
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT12_SET_FIELD:
|
|
NOT_REACHED(); /* This will be implemented by later patch and
|
|
* enabled using a non-NULL name in
|
|
* OFPAT12_ACTION(OFPAT12_SET_FIELD, ...) */
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_STRIP_VLAN:
|
|
ofpact_put_STRIP_VLAN(ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_DL_SRC:
|
|
case OFPUTIL_OFPAT11_SET_DL_SRC:
|
|
str_to_mac(arg, ofpact_put_SET_ETH_SRC(ofpacts)->mac);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_DL_DST:
|
|
case OFPUTIL_OFPAT11_SET_DL_DST:
|
|
str_to_mac(arg, ofpact_put_SET_ETH_DST(ofpacts)->mac);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_NW_SRC:
|
|
case OFPUTIL_OFPAT11_SET_NW_SRC:
|
|
str_to_ip(arg, &ip);
|
|
ofpact_put_SET_IPV4_SRC(ofpacts)->ipv4 = ip;
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_NW_DST:
|
|
case OFPUTIL_OFPAT11_SET_NW_DST:
|
|
str_to_ip(arg, &ip);
|
|
ofpact_put_SET_IPV4_DST(ofpacts)->ipv4 = ip;
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_NW_TOS:
|
|
case OFPUTIL_OFPAT11_SET_NW_TOS:
|
|
tos = str_to_u32(arg);
|
|
if (tos & ~IP_DSCP_MASK) {
|
|
ovs_fatal(0, "%s: not a valid TOS", arg);
|
|
}
|
|
ofpact_put_SET_IPV4_DSCP(ofpacts)->dscp = tos;
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_TP_SRC:
|
|
case OFPUTIL_OFPAT11_SET_TP_SRC:
|
|
ofpact_put_SET_L4_SRC_PORT(ofpacts)->port = str_to_u32(arg);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_SET_TP_DST:
|
|
case OFPUTIL_OFPAT11_SET_TP_DST:
|
|
ofpact_put_SET_L4_DST_PORT(ofpacts)->port = str_to_u32(arg);
|
|
break;
|
|
|
|
case OFPUTIL_OFPAT10_ENQUEUE:
|
|
parse_enqueue(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_RESUBMIT:
|
|
parse_resubmit(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_SET_TUNNEL:
|
|
case OFPUTIL_NXAST_SET_TUNNEL64:
|
|
tunnel = ofpact_put_SET_TUNNEL(ofpacts);
|
|
tunnel->ofpact.compat = code;
|
|
tunnel->tun_id = str_to_u64(arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_SET_QUEUE:
|
|
ofpact_put_SET_QUEUE(ofpacts)->queue_id = str_to_u32(arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_POP_QUEUE:
|
|
ofpact_put_POP_QUEUE(ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_REG_MOVE:
|
|
nxm_parse_reg_move(ofpact_put_REG_MOVE(ofpacts), arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_REG_LOAD:
|
|
nxm_parse_reg_load(ofpact_put_REG_LOAD(ofpacts), arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_NOTE:
|
|
parse_note(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_MULTIPATH:
|
|
multipath_parse(ofpact_put_MULTIPATH(ofpacts), arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_AUTOPATH__DEPRECATED:
|
|
autopath_parse(ofpact_put_AUTOPATH(ofpacts), arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_BUNDLE:
|
|
bundle_parse(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_BUNDLE_LOAD:
|
|
bundle_parse_load(arg, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_RESUBMIT_TABLE:
|
|
case OFPUTIL_NXAST_OUTPUT_REG:
|
|
case OFPUTIL_NXAST_DEC_TTL_CNT_IDS:
|
|
NOT_REACHED();
|
|
|
|
case OFPUTIL_NXAST_LEARN:
|
|
learn_parse(arg, flow, ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_EXIT:
|
|
ofpact_put_EXIT(ofpacts);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_DEC_TTL:
|
|
parse_dec_ttl(ofpacts, arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_FIN_TIMEOUT:
|
|
parse_fin_timeout(ofpacts, arg);
|
|
break;
|
|
|
|
case OFPUTIL_NXAST_CONTROLLER:
|
|
parse_controller(ofpacts, arg);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
str_to_ofpacts(const struct flow *flow, char *str, struct ofpbuf *ofpacts)
|
|
{
|
|
char *pos, *act, *arg;
|
|
int n_actions;
|
|
|
|
pos = str;
|
|
n_actions = 0;
|
|
while (ofputil_parse_key_value(&pos, &act, &arg)) {
|
|
int code = ofputil_action_code_from_name(act);
|
|
if (code >= 0) {
|
|
parse_named_action(code, flow, arg, ofpacts);
|
|
} else if (!strcasecmp(act, "drop")) {
|
|
if (n_actions) {
|
|
ovs_fatal(0, "Drop actions must not be preceded by other "
|
|
"actions");
|
|
} else if (ofputil_parse_key_value(&pos, &act, &arg)) {
|
|
ovs_fatal(0, "Drop actions must not be followed by other "
|
|
"actions");
|
|
}
|
|
break;
|
|
} else {
|
|
uint16_t port = ofputil_port_from_string(act);
|
|
if (port) {
|
|
ofpact_put_OUTPUT(ofpacts)->port = port;
|
|
} else {
|
|
ovs_fatal(0, "Unknown action: %s", act);
|
|
}
|
|
}
|
|
n_actions++;
|
|
}
|
|
ofpact_pad(ofpacts);
|
|
}
|
|
|
|
struct protocol {
|
|
const char *name;
|
|
uint16_t dl_type;
|
|
uint8_t nw_proto;
|
|
};
|
|
|
|
static bool
|
|
parse_protocol(const char *name, const struct protocol **p_out)
|
|
{
|
|
static const struct protocol protocols[] = {
|
|
{ "ip", ETH_TYPE_IP, 0 },
|
|
{ "arp", ETH_TYPE_ARP, 0 },
|
|
{ "icmp", ETH_TYPE_IP, IPPROTO_ICMP },
|
|
{ "tcp", ETH_TYPE_IP, IPPROTO_TCP },
|
|
{ "udp", ETH_TYPE_IP, IPPROTO_UDP },
|
|
{ "ipv6", ETH_TYPE_IPV6, 0 },
|
|
{ "ip6", ETH_TYPE_IPV6, 0 },
|
|
{ "icmp6", ETH_TYPE_IPV6, IPPROTO_ICMPV6 },
|
|
{ "tcp6", ETH_TYPE_IPV6, IPPROTO_TCP },
|
|
{ "udp6", ETH_TYPE_IPV6, IPPROTO_UDP },
|
|
};
|
|
const struct protocol *p;
|
|
|
|
for (p = protocols; p < &protocols[ARRAY_SIZE(protocols)]; p++) {
|
|
if (!strcmp(p->name, name)) {
|
|
*p_out = p;
|
|
return true;
|
|
}
|
|
}
|
|
*p_out = NULL;
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
ofp_fatal(const char *flow, bool verbose, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "%s:\n", flow);
|
|
}
|
|
|
|
va_start(args, format);
|
|
ovs_fatal_valist(0, format, args);
|
|
}
|
|
|
|
static void
|
|
parse_field(const struct mf_field *mf, const char *s, struct match *match)
|
|
{
|
|
union mf_value value, mask;
|
|
char *error;
|
|
|
|
error = mf_parse(mf, s, &value, &mask);
|
|
if (error) {
|
|
ovs_fatal(0, "%s", error);
|
|
}
|
|
|
|
mf_set(mf, &value, &mask, match);
|
|
}
|
|
|
|
/* Convert 'str_' (as described in the Flow Syntax section of the ovs-ofctl man
|
|
* page) into 'fm' for sending the specified flow_mod 'command' to a switch.
|
|
* If 'actions' is specified, an action must be in 'string' and may be expanded
|
|
* or reallocated.
|
|
*
|
|
* To parse syntax for an OFPT_FLOW_MOD (or NXT_FLOW_MOD), use an OFPFC_*
|
|
* constant for 'command'. To parse syntax for an OFPST_FLOW or
|
|
* OFPST_AGGREGATE (or NXST_FLOW or NXST_AGGREGATE), use -1 for 'command'. */
|
|
void
|
|
parse_ofp_str(struct ofputil_flow_mod *fm, int command, const char *str_,
|
|
bool verbose)
|
|
{
|
|
enum {
|
|
F_OUT_PORT = 1 << 0,
|
|
F_ACTIONS = 1 << 1,
|
|
F_TIMEOUT = 1 << 3,
|
|
F_PRIORITY = 1 << 4,
|
|
F_FLAGS = 1 << 5,
|
|
} fields;
|
|
char *string = xstrdup(str_);
|
|
char *save_ptr = NULL;
|
|
char *act_str = NULL;
|
|
char *name;
|
|
|
|
switch (command) {
|
|
case -1:
|
|
fields = F_OUT_PORT;
|
|
break;
|
|
|
|
case OFPFC_ADD:
|
|
fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS;
|
|
break;
|
|
|
|
case OFPFC_DELETE:
|
|
fields = F_OUT_PORT;
|
|
break;
|
|
|
|
case OFPFC_DELETE_STRICT:
|
|
fields = F_OUT_PORT | F_PRIORITY;
|
|
break;
|
|
|
|
case OFPFC_MODIFY:
|
|
fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS;
|
|
break;
|
|
|
|
case OFPFC_MODIFY_STRICT:
|
|
fields = F_ACTIONS | F_TIMEOUT | F_PRIORITY | F_FLAGS;
|
|
break;
|
|
|
|
default:
|
|
NOT_REACHED();
|
|
}
|
|
|
|
match_init_catchall(&fm->match);
|
|
fm->priority = OFP_DEFAULT_PRIORITY;
|
|
fm->cookie = htonll(0);
|
|
fm->cookie_mask = htonll(0);
|
|
if (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT) {
|
|
/* For modify, by default, don't update the cookie. */
|
|
fm->new_cookie = htonll(UINT64_MAX);
|
|
} else{
|
|
fm->new_cookie = htonll(0);
|
|
}
|
|
fm->table_id = 0xff;
|
|
fm->command = command;
|
|
fm->idle_timeout = OFP_FLOW_PERMANENT;
|
|
fm->hard_timeout = OFP_FLOW_PERMANENT;
|
|
fm->buffer_id = UINT32_MAX;
|
|
fm->out_port = OFPP_NONE;
|
|
fm->flags = 0;
|
|
if (fields & F_ACTIONS) {
|
|
act_str = strstr(string, "action");
|
|
if (!act_str) {
|
|
ofp_fatal(str_, verbose, "must specify an action");
|
|
}
|
|
*act_str = '\0';
|
|
|
|
act_str = strchr(act_str + 1, '=');
|
|
if (!act_str) {
|
|
ofp_fatal(str_, verbose, "must specify an action");
|
|
}
|
|
|
|
act_str++;
|
|
}
|
|
for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
|
|
name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
|
|
const struct protocol *p;
|
|
|
|
if (parse_protocol(name, &p)) {
|
|
match_set_dl_type(&fm->match, htons(p->dl_type));
|
|
if (p->nw_proto) {
|
|
match_set_nw_proto(&fm->match, p->nw_proto);
|
|
}
|
|
} else if (fields & F_FLAGS && !strcmp(name, "send_flow_rem")) {
|
|
fm->flags |= OFPFF_SEND_FLOW_REM;
|
|
} else if (fields & F_FLAGS && !strcmp(name, "check_overlap")) {
|
|
fm->flags |= OFPFF_CHECK_OVERLAP;
|
|
} else {
|
|
char *value;
|
|
|
|
value = strtok_r(NULL, ", \t\r\n", &save_ptr);
|
|
if (!value) {
|
|
ofp_fatal(str_, verbose, "field %s missing value", name);
|
|
}
|
|
|
|
if (!strcmp(name, "table")) {
|
|
fm->table_id = str_to_table_id(value);
|
|
} else if (!strcmp(name, "out_port")) {
|
|
fm->out_port = ofputil_port_from_string(name);
|
|
if (!fm->out_port) {
|
|
ofp_fatal(str_, verbose, "%s is not a valid OpenFlow port",
|
|
name);
|
|
}
|
|
} else if (fields & F_PRIORITY && !strcmp(name, "priority")) {
|
|
fm->priority = str_to_u16(value, name);
|
|
} else if (fields & F_TIMEOUT && !strcmp(name, "idle_timeout")) {
|
|
fm->idle_timeout = str_to_u16(value, name);
|
|
} else if (fields & F_TIMEOUT && !strcmp(name, "hard_timeout")) {
|
|
fm->hard_timeout = str_to_u16(value, name);
|
|
} else if (!strcmp(name, "cookie")) {
|
|
char *mask = strchr(value, '/');
|
|
|
|
if (mask) {
|
|
/* A mask means we're searching for a cookie. */
|
|
if (command == OFPFC_ADD) {
|
|
ofp_fatal(str_, verbose, "flow additions cannot use "
|
|
"a cookie mask");
|
|
}
|
|
*mask = '\0';
|
|
fm->cookie = htonll(str_to_u64(value));
|
|
fm->cookie_mask = htonll(str_to_u64(mask+1));
|
|
} else {
|
|
/* No mask means that the cookie is being set. */
|
|
if (command != OFPFC_ADD && command != OFPFC_MODIFY
|
|
&& command != OFPFC_MODIFY_STRICT) {
|
|
ofp_fatal(str_, verbose, "cannot set cookie");
|
|
}
|
|
fm->new_cookie = htonll(str_to_u64(value));
|
|
}
|
|
} else if (mf_from_name(name)) {
|
|
parse_field(mf_from_name(name), value, &fm->match);
|
|
} else if (!strcmp(name, "duration")
|
|
|| !strcmp(name, "n_packets")
|
|
|| !strcmp(name, "n_bytes")) {
|
|
/* Ignore these, so that users can feed the output of
|
|
* "ovs-ofctl dump-flows" back into commands that parse
|
|
* flows. */
|
|
} else {
|
|
ofp_fatal(str_, verbose, "unknown keyword %s", name);
|
|
}
|
|
}
|
|
}
|
|
if (!fm->cookie_mask && fm->new_cookie == htonll(UINT64_MAX)
|
|
&& (command == OFPFC_MODIFY || command == OFPFC_MODIFY_STRICT)) {
|
|
/* On modifies without a mask, we are supposed to add a flow if
|
|
* one does not exist. If a cookie wasn't been specified, use a
|
|
* default of zero. */
|
|
fm->new_cookie = htonll(0);
|
|
}
|
|
if (fields & F_ACTIONS) {
|
|
struct ofpbuf ofpacts;
|
|
|
|
ofpbuf_init(&ofpacts, 32);
|
|
str_to_ofpacts(&fm->match.flow, act_str, &ofpacts);
|
|
fm->ofpacts_len = ofpacts.size;
|
|
fm->ofpacts = ofpbuf_steal_data(&ofpacts);
|
|
} else {
|
|
fm->ofpacts_len = 0;
|
|
fm->ofpacts = NULL;
|
|
}
|
|
|
|
free(string);
|
|
}
|
|
|
|
/* Convert 'str_' (as described in the documentation for the "monitor" command
|
|
* in the ovs-ofctl man page) into 'fmr'. */
|
|
void
|
|
parse_flow_monitor_request(struct ofputil_flow_monitor_request *fmr,
|
|
const char *str_)
|
|
{
|
|
static uint32_t id;
|
|
|
|
char *string = xstrdup(str_);
|
|
char *save_ptr = NULL;
|
|
char *name;
|
|
|
|
fmr->id = id++;
|
|
fmr->flags = (NXFMF_INITIAL | NXFMF_ADD | NXFMF_DELETE | NXFMF_MODIFY
|
|
| NXFMF_OWN | NXFMF_ACTIONS);
|
|
fmr->out_port = OFPP_NONE;
|
|
fmr->table_id = 0xff;
|
|
match_init_catchall(&fmr->match);
|
|
|
|
for (name = strtok_r(string, "=, \t\r\n", &save_ptr); name;
|
|
name = strtok_r(NULL, "=, \t\r\n", &save_ptr)) {
|
|
const struct protocol *p;
|
|
|
|
if (!strcmp(name, "!initial")) {
|
|
fmr->flags &= ~NXFMF_INITIAL;
|
|
} else if (!strcmp(name, "!add")) {
|
|
fmr->flags &= ~NXFMF_ADD;
|
|
} else if (!strcmp(name, "!delete")) {
|
|
fmr->flags &= ~NXFMF_DELETE;
|
|
} else if (!strcmp(name, "!modify")) {
|
|
fmr->flags &= ~NXFMF_MODIFY;
|
|
} else if (!strcmp(name, "!actions")) {
|
|
fmr->flags &= ~NXFMF_ACTIONS;
|
|
} else if (!strcmp(name, "!own")) {
|
|
fmr->flags &= ~NXFMF_OWN;
|
|
} else if (parse_protocol(name, &p)) {
|
|
match_set_dl_type(&fmr->match, htons(p->dl_type));
|
|
if (p->nw_proto) {
|
|
match_set_nw_proto(&fmr->match, p->nw_proto);
|
|
}
|
|
} else {
|
|
char *value;
|
|
|
|
value = strtok_r(NULL, ", \t\r\n", &save_ptr);
|
|
if (!value) {
|
|
ovs_fatal(0, "%s: field %s missing value", str_, name);
|
|
}
|
|
|
|
if (!strcmp(name, "table")) {
|
|
fmr->table_id = str_to_table_id(value);
|
|
} else if (!strcmp(name, "out_port")) {
|
|
fmr->out_port = atoi(value);
|
|
} else if (mf_from_name(name)) {
|
|
parse_field(mf_from_name(name), value, &fmr->match);
|
|
} else {
|
|
ovs_fatal(0, "%s: unknown keyword %s", str_, name);
|
|
}
|
|
}
|
|
}
|
|
free(string);
|
|
}
|
|
|
|
/* Parses 's' as a set of OpenFlow actions and appends the actions to
|
|
* 'actions'.
|
|
*
|
|
* Prints an error on stderr and aborts the program if 's' syntax is
|
|
* invalid. */
|
|
void
|
|
parse_ofpacts(const char *s_, struct ofpbuf *ofpacts)
|
|
{
|
|
char *s = xstrdup(s_);
|
|
str_to_ofpacts(NULL, s, ofpacts);
|
|
free(s);
|
|
}
|
|
|
|
/* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command'
|
|
* (one of OFPFC_*) into 'fm'. */
|
|
void
|
|
parse_ofp_flow_mod_str(struct ofputil_flow_mod *fm, const char *string,
|
|
uint16_t command, bool verbose)
|
|
{
|
|
struct match match_copy;
|
|
|
|
parse_ofp_str(fm, command, string, verbose);
|
|
|
|
/* Normalize a copy of the match. This ensures that non-normalized flows
|
|
* get logged but doesn't affect what gets sent to the switch, so that the
|
|
* switch can do whatever it likes with the flow. */
|
|
match_copy = fm->match;
|
|
ofputil_normalize_match(&match_copy);
|
|
}
|
|
|
|
void
|
|
parse_ofp_flow_mod_file(const char *file_name, uint16_t command,
|
|
struct ofputil_flow_mod **fms, size_t *n_fms)
|
|
{
|
|
size_t allocated_fms;
|
|
FILE *stream;
|
|
struct ds s;
|
|
|
|
stream = !strcmp(file_name, "-") ? stdin : fopen(file_name, "r");
|
|
if (stream == NULL) {
|
|
ovs_fatal(errno, "%s: open", file_name);
|
|
}
|
|
|
|
allocated_fms = *n_fms;
|
|
ds_init(&s);
|
|
while (!ds_get_preprocessed_line(&s, stream)) {
|
|
if (*n_fms >= allocated_fms) {
|
|
*fms = x2nrealloc(*fms, &allocated_fms, sizeof **fms);
|
|
}
|
|
parse_ofp_flow_mod_str(&(*fms)[*n_fms], ds_cstr(&s), command, false);
|
|
*n_fms += 1;
|
|
}
|
|
ds_destroy(&s);
|
|
|
|
if (stream != stdin) {
|
|
fclose(stream);
|
|
}
|
|
}
|
|
|
|
void
|
|
parse_ofp_flow_stats_request_str(struct ofputil_flow_stats_request *fsr,
|
|
bool aggregate, const char *string)
|
|
{
|
|
struct ofputil_flow_mod fm;
|
|
|
|
parse_ofp_str(&fm, -1, string, false);
|
|
fsr->aggregate = aggregate;
|
|
fsr->cookie = fm.cookie;
|
|
fsr->cookie_mask = fm.cookie_mask;
|
|
fsr->match = fm.match;
|
|
fsr->out_port = fm.out_port;
|
|
fsr->table_id = fm.table_id;
|
|
}
|
|
|
|
/* Parses a specification of a flow from 's' into 'flow'. 's' must take the
|
|
* form FIELD=VALUE[,FIELD=VALUE]... where each FIELD is the name of a
|
|
* mf_field. Fields must be specified in a natural order for satisfying
|
|
* prerequisites.
|
|
*
|
|
* Returns NULL on success, otherwise a malloc()'d string that explains the
|
|
* problem. */
|
|
char *
|
|
parse_ofp_exact_flow(struct flow *flow, const char *s)
|
|
{
|
|
char *pos, *key, *value_s;
|
|
char *error = NULL;
|
|
char *copy;
|
|
|
|
memset(flow, 0, sizeof *flow);
|
|
|
|
pos = copy = xstrdup(s);
|
|
while (ofputil_parse_key_value(&pos, &key, &value_s)) {
|
|
const struct protocol *p;
|
|
if (parse_protocol(key, &p)) {
|
|
if (flow->dl_type) {
|
|
error = xasprintf("%s: Ethernet type set multiple times", s);
|
|
goto exit;
|
|
}
|
|
flow->dl_type = htons(p->dl_type);
|
|
|
|
if (p->nw_proto) {
|
|
if (flow->nw_proto) {
|
|
error = xasprintf("%s: network protocol set "
|
|
"multiple times", s);
|
|
goto exit;
|
|
}
|
|
flow->nw_proto = p->nw_proto;
|
|
}
|
|
} else {
|
|
const struct mf_field *mf;
|
|
union mf_value value;
|
|
char *field_error;
|
|
|
|
mf = mf_from_name(key);
|
|
if (!mf) {
|
|
error = xasprintf("%s: unknown field %s", s, key);
|
|
goto exit;
|
|
}
|
|
|
|
if (!mf_are_prereqs_ok(mf, flow)) {
|
|
error = xasprintf("%s: prerequisites not met for setting %s",
|
|
s, key);
|
|
goto exit;
|
|
}
|
|
|
|
if (!mf_is_zero(mf, flow)) {
|
|
error = xasprintf("%s: field %s set multiple times", s, key);
|
|
goto exit;
|
|
}
|
|
|
|
field_error = mf_parse_value(mf, value_s, &value);
|
|
if (field_error) {
|
|
error = xasprintf("%s: bad value for %s (%s)",
|
|
s, key, field_error);
|
|
free(field_error);
|
|
goto exit;
|
|
}
|
|
|
|
mf_set_flow_value(mf, &value, flow);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
free(copy);
|
|
|
|
if (error) {
|
|
memset(flow, 0, sizeof *flow);
|
|
}
|
|
return error;
|
|
}
|