mirror of
https://github.com/openvswitch/ovs
synced 2025-08-29 05:18:13 +00:00
Commit 0b3f27253 (ovs-ofctl: Warn about flows not in normal form) made ovs-ofctl warn about non-normalized flows, that is, flows some of whose specified fields will be ignored by the switch. This was convenient for users, who are understandably confused by flow normalization. However, later commit 8050b31d6 (ofp-parse: Refactor flow parsing) accidentally deleted the warning. This commit restores it and adds a test to ensure that it doesn't get deleted again later. Reported-by: Reid Price <reid@nicira.com> Bug #5029.
917 lines
28 KiB
C
917 lines
28 KiB
C
/*
|
|
* Copyright (c) 2010, 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 "ofp-parse.h"
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "autopath.h"
|
|
#include "byte-order.h"
|
|
#include "dynamic-string.h"
|
|
#include "netdev.h"
|
|
#include "multipath.h"
|
|
#include "nx-match.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 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, ovs_be32 *maskp)
|
|
{
|
|
char *str = xstrdup(str_);
|
|
char *save_ptr = NULL;
|
|
const char *name, *netmask;
|
|
struct in_addr in_addr;
|
|
ovs_be32 mask;
|
|
int retval;
|
|
|
|
name = strtok_r(str, "/", &save_ptr);
|
|
retval = name ? lookup_ip(name, &in_addr) : EINVAL;
|
|
if (retval) {
|
|
ovs_fatal(0, "%s: could not convert to IP address", str);
|
|
}
|
|
*ip = in_addr.s_addr;
|
|
|
|
netmask = strtok_r(NULL, "/", &save_ptr);
|
|
if (netmask) {
|
|
uint8_t o[4];
|
|
if (sscanf(netmask, "%"SCNu8".%"SCNu8".%"SCNu8".%"SCNu8,
|
|
&o[0], &o[1], &o[2], &o[3]) == 4) {
|
|
mask = htonl((o[0] << 24) | (o[1] << 16) | (o[2] << 8) | o[3]);
|
|
} else {
|
|
int prefix = atoi(netmask);
|
|
if (prefix <= 0 || prefix > 32) {
|
|
ovs_fatal(0, "%s: network prefix bits not between 1 and 32",
|
|
str);
|
|
} else if (prefix == 32) {
|
|
mask = htonl(UINT32_MAX);
|
|
} else {
|
|
mask = htonl(((1u << prefix) - 1) << (32 - prefix));
|
|
}
|
|
}
|
|
} else {
|
|
mask = htonl(UINT32_MAX);
|
|
}
|
|
*ip &= mask;
|
|
|
|
if (maskp) {
|
|
*maskp = mask;
|
|
} else {
|
|
if (mask != htonl(UINT32_MAX)) {
|
|
ovs_fatal(0, "%s: netmask not allowed here", str_);
|
|
}
|
|
}
|
|
|
|
free(str);
|
|
}
|
|
|
|
static void
|
|
str_to_tun_id(const char *str, ovs_be64 *tun_idp, ovs_be64 *maskp)
|
|
{
|
|
uint64_t tun_id, mask;
|
|
char *tail;
|
|
|
|
errno = 0;
|
|
tun_id = strtoull(str, &tail, 0);
|
|
if (errno || (*tail != '\0' && *tail != '/')) {
|
|
goto error;
|
|
}
|
|
|
|
if (*tail == '/') {
|
|
mask = strtoull(tail + 1, &tail, 0);
|
|
if (errno || *tail != '\0') {
|
|
goto error;
|
|
}
|
|
} else {
|
|
mask = UINT64_MAX;
|
|
}
|
|
|
|
*tun_idp = htonll(tun_id);
|
|
*maskp = htonll(mask);
|
|
return;
|
|
|
|
error:
|
|
ovs_fatal(0, "%s: bad syntax for tunnel id", str);
|
|
}
|
|
|
|
static void
|
|
str_to_ipv6(const char *str_, struct in6_addr *addrp, struct in6_addr *maskp)
|
|
{
|
|
char *str = xstrdup(str_);
|
|
char *save_ptr = NULL;
|
|
const char *name, *netmask;
|
|
struct in6_addr addr, mask;
|
|
int retval;
|
|
|
|
name = strtok_r(str, "/", &save_ptr);
|
|
retval = name ? lookup_ipv6(name, &addr) : EINVAL;
|
|
if (retval) {
|
|
ovs_fatal(0, "%s: could not convert to IPv6 address", str);
|
|
}
|
|
|
|
netmask = strtok_r(NULL, "/", &save_ptr);
|
|
if (netmask) {
|
|
int prefix = atoi(netmask);
|
|
if (prefix <= 0 || prefix > 128) {
|
|
ovs_fatal(0, "%s: network prefix bits not between 1 and 128",
|
|
str);
|
|
} else {
|
|
mask = ipv6_create_mask(prefix);
|
|
}
|
|
} else {
|
|
mask = in6addr_exact;
|
|
}
|
|
*addrp = ipv6_addr_bitand(&addr, &mask);
|
|
|
|
if (maskp) {
|
|
*maskp = mask;
|
|
} else {
|
|
if (!ipv6_mask_is_exact(&mask)) {
|
|
ovs_fatal(0, "%s: netmask not allowed here", str_);
|
|
}
|
|
}
|
|
|
|
free(str);
|
|
}
|
|
|
|
static void *
|
|
put_action(struct ofpbuf *b, size_t size, uint16_t type)
|
|
{
|
|
struct ofp_action_header *ah = ofpbuf_put_zeros(b, size);
|
|
ah->type = htons(type);
|
|
ah->len = htons(size);
|
|
return ah;
|
|
}
|
|
|
|
static struct ofp_action_output *
|
|
put_output_action(struct ofpbuf *b, uint16_t port)
|
|
{
|
|
struct ofp_action_output *oao = put_action(b, sizeof *oao, OFPAT_OUTPUT);
|
|
oao->port = htons(port);
|
|
return oao;
|
|
}
|
|
|
|
static void
|
|
put_enqueue_action(struct ofpbuf *b, uint16_t port, uint32_t queue)
|
|
{
|
|
struct ofp_action_enqueue *oae = put_action(b, sizeof *oae, OFPAT_ENQUEUE);
|
|
oae->port = htons(port);
|
|
oae->queue_id = htonl(queue);
|
|
}
|
|
|
|
static void
|
|
put_dl_addr_action(struct ofpbuf *b, uint16_t type, const char *addr)
|
|
{
|
|
struct ofp_action_dl_addr *oada = put_action(b, sizeof *oada, type);
|
|
str_to_mac(addr, oada->dl_addr);
|
|
}
|
|
|
|
|
|
static bool
|
|
parse_port_name(const char *name, uint16_t *port)
|
|
{
|
|
struct pair {
|
|
const char *name;
|
|
uint16_t value;
|
|
};
|
|
static const struct pair pairs[] = {
|
|
#define DEF_PAIR(NAME) {#NAME, OFPP_##NAME}
|
|
DEF_PAIR(IN_PORT),
|
|
DEF_PAIR(TABLE),
|
|
DEF_PAIR(NORMAL),
|
|
DEF_PAIR(FLOOD),
|
|
DEF_PAIR(ALL),
|
|
DEF_PAIR(CONTROLLER),
|
|
DEF_PAIR(LOCAL),
|
|
DEF_PAIR(NONE),
|
|
#undef DEF_PAIR
|
|
};
|
|
static const int n_pairs = ARRAY_SIZE(pairs);
|
|
size_t i;
|
|
|
|
for (i = 0; i < n_pairs; i++) {
|
|
if (!strcasecmp(name, pairs[i].name)) {
|
|
*port = pairs[i].value;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
str_to_action(char *str, struct ofpbuf *b)
|
|
{
|
|
bool drop = false;
|
|
int n_actions;
|
|
char *pos;
|
|
|
|
pos = str;
|
|
n_actions = 0;
|
|
for (;;) {
|
|
char empty_string[] = "";
|
|
char *act, *arg;
|
|
size_t actlen;
|
|
uint16_t port;
|
|
|
|
pos += strspn(pos, ", \t\r\n");
|
|
if (*pos == '\0') {
|
|
break;
|
|
}
|
|
|
|
if (drop) {
|
|
ovs_fatal(0, "Drop actions must not be followed by other actions");
|
|
}
|
|
|
|
act = pos;
|
|
actlen = strcspn(pos, ":(, \t\r\n");
|
|
if (act[actlen] == ':') {
|
|
/* The argument can be separated by a colon. */
|
|
size_t arglen;
|
|
|
|
arg = act + actlen + 1;
|
|
arglen = strcspn(arg, ", \t\r\n");
|
|
pos = arg + arglen + (arg[arglen] != '\0');
|
|
arg[arglen] = '\0';
|
|
} else if (act[actlen] == '(') {
|
|
/* The argument can be surrounded by balanced parentheses. The
|
|
* outermost set of parentheses is removed. */
|
|
int level = 1;
|
|
size_t arglen;
|
|
|
|
arg = act + actlen + 1;
|
|
for (arglen = 0; level > 0; arglen++) {
|
|
switch (arg[arglen]) {
|
|
case '\0':
|
|
ovs_fatal(0, "unbalanced parentheses in argument to %s "
|
|
"action", act);
|
|
|
|
case '(':
|
|
level++;
|
|
break;
|
|
|
|
case ')':
|
|
level--;
|
|
break;
|
|
}
|
|
}
|
|
arg[arglen - 1] = '\0';
|
|
pos = arg + arglen;
|
|
} else {
|
|
/* There might be no argument at all. */
|
|
arg = empty_string;
|
|
pos = act + actlen + (act[actlen] != '\0');
|
|
}
|
|
act[actlen] = '\0';
|
|
|
|
if (!strcasecmp(act, "mod_vlan_vid")) {
|
|
struct ofp_action_vlan_vid *va;
|
|
va = put_action(b, sizeof *va, OFPAT_SET_VLAN_VID);
|
|
va->vlan_vid = htons(str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "mod_vlan_pcp")) {
|
|
struct ofp_action_vlan_pcp *va;
|
|
va = put_action(b, sizeof *va, OFPAT_SET_VLAN_PCP);
|
|
va->vlan_pcp = str_to_u32(arg);
|
|
} else if (!strcasecmp(act, "strip_vlan")) {
|
|
struct ofp_action_header *ah;
|
|
ah = put_action(b, sizeof *ah, OFPAT_STRIP_VLAN);
|
|
ah->type = htons(OFPAT_STRIP_VLAN);
|
|
} else if (!strcasecmp(act, "mod_dl_src")) {
|
|
put_dl_addr_action(b, OFPAT_SET_DL_SRC, arg);
|
|
} else if (!strcasecmp(act, "mod_dl_dst")) {
|
|
put_dl_addr_action(b, OFPAT_SET_DL_DST, arg);
|
|
} else if (!strcasecmp(act, "mod_nw_src")) {
|
|
struct ofp_action_nw_addr *na;
|
|
na = put_action(b, sizeof *na, OFPAT_SET_NW_SRC);
|
|
str_to_ip(arg, &na->nw_addr, NULL);
|
|
} else if (!strcasecmp(act, "mod_nw_dst")) {
|
|
struct ofp_action_nw_addr *na;
|
|
na = put_action(b, sizeof *na, OFPAT_SET_NW_DST);
|
|
str_to_ip(arg, &na->nw_addr, NULL);
|
|
} else if (!strcasecmp(act, "mod_tp_src")) {
|
|
struct ofp_action_tp_port *ta;
|
|
ta = put_action(b, sizeof *ta, OFPAT_SET_TP_SRC);
|
|
ta->tp_port = htons(str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "mod_tp_dst")) {
|
|
struct ofp_action_tp_port *ta;
|
|
ta = put_action(b, sizeof *ta, OFPAT_SET_TP_DST);
|
|
ta->tp_port = htons(str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "mod_nw_tos")) {
|
|
struct ofp_action_nw_tos *nt;
|
|
nt = put_action(b, sizeof *nt, OFPAT_SET_NW_TOS);
|
|
nt->nw_tos = str_to_u32(arg);
|
|
} else if (!strcasecmp(act, "resubmit")) {
|
|
struct nx_action_resubmit *nar;
|
|
nar = put_action(b, sizeof *nar, OFPAT_VENDOR);
|
|
nar->vendor = htonl(NX_VENDOR_ID);
|
|
nar->subtype = htons(NXAST_RESUBMIT);
|
|
nar->in_port = htons(str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "set_tunnel")
|
|
|| !strcasecmp(act, "set_tunnel64")) {
|
|
uint64_t tun_id = str_to_u64(arg);
|
|
if (!strcasecmp(act, "set_tunnel64") || tun_id > UINT32_MAX) {
|
|
struct nx_action_set_tunnel64 *nast64;
|
|
nast64 = put_action(b, sizeof *nast64, OFPAT_VENDOR);
|
|
nast64->vendor = htonl(NX_VENDOR_ID);
|
|
nast64->subtype = htons(NXAST_SET_TUNNEL64);
|
|
nast64->tun_id = htonll(tun_id);
|
|
} else {
|
|
struct nx_action_set_tunnel *nast;
|
|
nast = put_action(b, sizeof *nast, OFPAT_VENDOR);
|
|
nast->vendor = htonl(NX_VENDOR_ID);
|
|
nast->subtype = htons(NXAST_SET_TUNNEL);
|
|
nast->tun_id = htonl(tun_id);
|
|
}
|
|
} else if (!strcasecmp(act, "drop_spoofed_arp")) {
|
|
struct nx_action_header *nah;
|
|
nah = put_action(b, sizeof *nah, OFPAT_VENDOR);
|
|
nah->vendor = htonl(NX_VENDOR_ID);
|
|
nah->subtype = htons(NXAST_DROP_SPOOFED_ARP);
|
|
} else if (!strcasecmp(act, "set_queue")) {
|
|
struct nx_action_set_queue *nasq;
|
|
nasq = put_action(b, sizeof *nasq, OFPAT_VENDOR);
|
|
nasq->vendor = htonl(NX_VENDOR_ID);
|
|
nasq->subtype = htons(NXAST_SET_QUEUE);
|
|
nasq->queue_id = htonl(str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "pop_queue")) {
|
|
struct nx_action_header *nah;
|
|
nah = put_action(b, sizeof *nah, OFPAT_VENDOR);
|
|
nah->vendor = htonl(NX_VENDOR_ID);
|
|
nah->subtype = htons(NXAST_POP_QUEUE);
|
|
} else if (!strcasecmp(act, "note")) {
|
|
size_t start_ofs = b->size;
|
|
struct nx_action_note *nan;
|
|
int remainder;
|
|
size_t len;
|
|
|
|
nan = put_action(b, sizeof *nan, OFPAT_VENDOR);
|
|
nan->vendor = htonl(NX_VENDOR_ID);
|
|
nan->subtype = htons(NXAST_NOTE);
|
|
|
|
b->size -= sizeof nan->note;
|
|
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(b, &byte, 1);
|
|
|
|
arg += 2;
|
|
}
|
|
|
|
len = b->size - start_ofs;
|
|
remainder = len % OFP_ACTION_ALIGN;
|
|
if (remainder) {
|
|
ofpbuf_put_zeros(b, OFP_ACTION_ALIGN - remainder);
|
|
}
|
|
nan->len = htons(b->size - start_ofs);
|
|
} else if (!strcasecmp(act, "move")) {
|
|
struct nx_action_reg_move *move;
|
|
move = ofpbuf_put_uninit(b, sizeof *move);
|
|
nxm_parse_reg_move(move, arg);
|
|
} else if (!strcasecmp(act, "load")) {
|
|
struct nx_action_reg_load *load;
|
|
load = ofpbuf_put_uninit(b, sizeof *load);
|
|
nxm_parse_reg_load(load, arg);
|
|
} else if (!strcasecmp(act, "multipath")) {
|
|
struct nx_action_multipath *nam;
|
|
nam = ofpbuf_put_uninit(b, sizeof *nam);
|
|
multipath_parse(nam, arg);
|
|
} else if (!strcasecmp(act, "autopath")) {
|
|
struct nx_action_autopath *naa;
|
|
naa = ofpbuf_put_uninit(b, sizeof *naa);
|
|
autopath_parse(naa, arg);
|
|
} else if (!strcasecmp(act, "output")) {
|
|
put_output_action(b, str_to_u32(arg));
|
|
} else if (!strcasecmp(act, "enqueue")) {
|
|
char *sp = NULL;
|
|
char *port_s = strtok_r(arg, ":q", &sp);
|
|
char *queue = strtok_r(NULL, "", &sp);
|
|
if (port_s == NULL || queue == NULL) {
|
|
ovs_fatal(0, "\"enqueue\" syntax is \"enqueue:PORT:QUEUE\"");
|
|
}
|
|
put_enqueue_action(b, str_to_u32(port_s), str_to_u32(queue));
|
|
} else if (!strcasecmp(act, "drop")) {
|
|
/* A drop action in OpenFlow occurs by just not setting
|
|
* an action. */
|
|
drop = true;
|
|
if (n_actions) {
|
|
ovs_fatal(0, "Drop actions must not be preceded by other "
|
|
"actions");
|
|
}
|
|
} else if (!strcasecmp(act, "CONTROLLER")) {
|
|
struct ofp_action_output *oao;
|
|
oao = put_output_action(b, OFPP_CONTROLLER);
|
|
|
|
/* Unless a numeric argument is specified, we send the whole
|
|
* packet to the controller. */
|
|
if (arg[0] && (strspn(arg, "0123456789") == strlen(arg))) {
|
|
oao->max_len = htons(str_to_u32(arg));
|
|
} else {
|
|
oao->max_len = htons(UINT16_MAX);
|
|
}
|
|
} else if (parse_port_name(act, &port)) {
|
|
put_output_action(b, port);
|
|
} else if (strspn(act, "0123456789") == strlen(act)) {
|
|
put_output_action(b, str_to_u32(act));
|
|
} else {
|
|
ovs_fatal(0, "Unknown action: %s", act);
|
|
}
|
|
n_actions++;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#define FIELDS \
|
|
FIELD(F_TUN_ID, "tun_id", 0) \
|
|
FIELD(F_IN_PORT, "in_port", FWW_IN_PORT) \
|
|
FIELD(F_DL_VLAN, "dl_vlan", 0) \
|
|
FIELD(F_DL_VLAN_PCP, "dl_vlan_pcp", 0) \
|
|
FIELD(F_DL_SRC, "dl_src", FWW_DL_SRC) \
|
|
FIELD(F_DL_DST, "dl_dst", FWW_DL_DST) \
|
|
FIELD(F_DL_TYPE, "dl_type", FWW_DL_TYPE) \
|
|
FIELD(F_NW_SRC, "nw_src", 0) \
|
|
FIELD(F_NW_DST, "nw_dst", 0) \
|
|
FIELD(F_NW_PROTO, "nw_proto", FWW_NW_PROTO) \
|
|
FIELD(F_NW_TOS, "nw_tos", FWW_NW_TOS) \
|
|
FIELD(F_TP_SRC, "tp_src", FWW_TP_SRC) \
|
|
FIELD(F_TP_DST, "tp_dst", FWW_TP_DST) \
|
|
FIELD(F_ICMP_TYPE, "icmp_type", FWW_TP_SRC) \
|
|
FIELD(F_ICMP_CODE, "icmp_code", FWW_TP_DST) \
|
|
FIELD(F_ARP_SHA, "arp_sha", FWW_ARP_SHA) \
|
|
FIELD(F_ARP_THA, "arp_tha", FWW_ARP_THA) \
|
|
FIELD(F_IPV6_SRC, "ipv6_src", 0) \
|
|
FIELD(F_IPV6_DST, "ipv6_dst", 0) \
|
|
FIELD(F_ND_TARGET, "nd_target", FWW_ND_TARGET) \
|
|
FIELD(F_ND_SLL, "nd_sll", FWW_ARP_SHA) \
|
|
FIELD(F_ND_TLL, "nd_tll", FWW_ARP_THA)
|
|
|
|
enum field_index {
|
|
#define FIELD(ENUM, NAME, WILDCARD) ENUM,
|
|
FIELDS
|
|
#undef FIELD
|
|
N_FIELDS
|
|
};
|
|
|
|
struct field {
|
|
enum field_index index;
|
|
const char *name;
|
|
flow_wildcards_t wildcard; /* FWW_* bit. */
|
|
};
|
|
|
|
static bool
|
|
parse_field_name(const char *name, const struct field **f_out)
|
|
{
|
|
static const struct field fields[N_FIELDS] = {
|
|
#define FIELD(ENUM, NAME, WILDCARD) { ENUM, NAME, WILDCARD },
|
|
FIELDS
|
|
#undef FIELD
|
|
};
|
|
const struct field *f;
|
|
|
|
for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) {
|
|
if (!strcmp(f->name, name)) {
|
|
*f_out = f;
|
|
return true;
|
|
}
|
|
}
|
|
*f_out = NULL;
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
parse_field_value(struct cls_rule *rule, enum field_index index,
|
|
const char *value)
|
|
{
|
|
uint8_t mac[ETH_ADDR_LEN];
|
|
ovs_be64 tun_id, tun_mask;
|
|
ovs_be32 ip, mask;
|
|
struct in6_addr ipv6, ipv6_mask;
|
|
uint16_t port_no;
|
|
|
|
switch (index) {
|
|
case F_TUN_ID:
|
|
str_to_tun_id(value, &tun_id, &tun_mask);
|
|
cls_rule_set_tun_id_masked(rule, tun_id, tun_mask);
|
|
break;
|
|
|
|
case F_IN_PORT:
|
|
if (!parse_port_name(value, &port_no)) {
|
|
port_no = atoi(value);
|
|
}
|
|
cls_rule_set_in_port(rule, port_no);
|
|
break;
|
|
|
|
case F_DL_VLAN:
|
|
cls_rule_set_dl_vlan(rule, htons(str_to_u32(value)));
|
|
break;
|
|
|
|
case F_DL_VLAN_PCP:
|
|
cls_rule_set_dl_vlan_pcp(rule, str_to_u32(value));
|
|
break;
|
|
|
|
case F_DL_SRC:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_dl_src(rule, mac);
|
|
break;
|
|
|
|
case F_DL_DST:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_dl_dst(rule, mac);
|
|
break;
|
|
|
|
case F_DL_TYPE:
|
|
cls_rule_set_dl_type(rule, htons(str_to_u32(value)));
|
|
break;
|
|
|
|
case F_NW_SRC:
|
|
str_to_ip(value, &ip, &mask);
|
|
cls_rule_set_nw_src_masked(rule, ip, mask);
|
|
break;
|
|
|
|
case F_NW_DST:
|
|
str_to_ip(value, &ip, &mask);
|
|
cls_rule_set_nw_dst_masked(rule, ip, mask);
|
|
break;
|
|
|
|
case F_NW_PROTO:
|
|
cls_rule_set_nw_proto(rule, str_to_u32(value));
|
|
break;
|
|
|
|
case F_NW_TOS:
|
|
cls_rule_set_nw_tos(rule, str_to_u32(value));
|
|
break;
|
|
|
|
case F_TP_SRC:
|
|
cls_rule_set_tp_src(rule, htons(str_to_u32(value)));
|
|
break;
|
|
|
|
case F_TP_DST:
|
|
cls_rule_set_tp_dst(rule, htons(str_to_u32(value)));
|
|
break;
|
|
|
|
case F_ICMP_TYPE:
|
|
cls_rule_set_icmp_type(rule, str_to_u32(value));
|
|
break;
|
|
|
|
case F_ICMP_CODE:
|
|
cls_rule_set_icmp_code(rule, str_to_u32(value));
|
|
break;
|
|
|
|
case F_ARP_SHA:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_arp_sha(rule, mac);
|
|
break;
|
|
|
|
case F_ARP_THA:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_arp_tha(rule, mac);
|
|
break;
|
|
|
|
case F_IPV6_SRC:
|
|
str_to_ipv6(value, &ipv6, &ipv6_mask);
|
|
cls_rule_set_ipv6_src_masked(rule, &ipv6, &ipv6_mask);
|
|
break;
|
|
|
|
case F_IPV6_DST:
|
|
str_to_ipv6(value, &ipv6, &ipv6_mask);
|
|
cls_rule_set_ipv6_dst_masked(rule, &ipv6, &ipv6_mask);
|
|
break;
|
|
|
|
case F_ND_TARGET:
|
|
str_to_ipv6(value, &ipv6, NULL);
|
|
cls_rule_set_nd_target(rule, ipv6);
|
|
break;
|
|
|
|
case F_ND_SLL:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_arp_sha(rule, mac);
|
|
break;
|
|
|
|
case F_ND_TLL:
|
|
str_to_mac(value, mac);
|
|
cls_rule_set_arp_tha(rule, mac);
|
|
break;
|
|
|
|
case N_FIELDS:
|
|
NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
static void
|
|
parse_reg_value(struct cls_rule *rule, int reg_idx, const char *value)
|
|
{
|
|
uint32_t reg_value, reg_mask;
|
|
|
|
if (!strcmp(value, "ANY") || !strcmp(value, "*")) {
|
|
cls_rule_set_reg_masked(rule, reg_idx, 0, 0);
|
|
} else if (sscanf(value, "%"SCNi32"/%"SCNi32,
|
|
®_value, ®_mask) == 2) {
|
|
cls_rule_set_reg_masked(rule, reg_idx, reg_value, reg_mask);
|
|
} else if (sscanf(value, "%"SCNi32, ®_value)) {
|
|
cls_rule_set_reg(rule, reg_idx, reg_value);
|
|
} else {
|
|
ovs_fatal(0, "register fields must take the form <value> "
|
|
"or <value>/<mask>");
|
|
}
|
|
}
|
|
|
|
/* Convert 'string' (as described in the Flow Syntax section of the ovs-ofctl
|
|
* man page) into 'pf'. If 'actions' is specified, an action must be in
|
|
* 'string' and may be expanded or reallocated. */
|
|
void
|
|
parse_ofp_str(struct flow_mod *fm, struct ofpbuf *actions, char *string)
|
|
{
|
|
char *save_ptr = NULL;
|
|
char *name;
|
|
|
|
cls_rule_init_catchall(&fm->cr, OFP_DEFAULT_PRIORITY);
|
|
fm->cookie = htonll(0);
|
|
fm->table_id = 0xff;
|
|
fm->command = UINT16_MAX;
|
|
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 (actions) {
|
|
char *act_str = strstr(string, "action");
|
|
if (!act_str) {
|
|
ovs_fatal(0, "must specify an action");
|
|
}
|
|
*act_str = '\0';
|
|
|
|
act_str = strchr(act_str + 1, '=');
|
|
if (!act_str) {
|
|
ovs_fatal(0, "must specify an action");
|
|
}
|
|
|
|
act_str++;
|
|
|
|
str_to_action(act_str, actions);
|
|
fm->actions = actions->data;
|
|
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)) {
|
|
const struct protocol *p;
|
|
|
|
if (parse_protocol(name, &p)) {
|
|
cls_rule_set_dl_type(&fm->cr, htons(p->dl_type));
|
|
if (p->nw_proto) {
|
|
cls_rule_set_nw_proto(&fm->cr, p->nw_proto);
|
|
}
|
|
} else {
|
|
const struct field *f;
|
|
char *value;
|
|
|
|
value = strtok_r(NULL, ", \t\r\n", &save_ptr);
|
|
if (!value) {
|
|
ovs_fatal(0, "field %s missing value", name);
|
|
}
|
|
|
|
if (!strcmp(name, "table")) {
|
|
fm->table_id = atoi(value);
|
|
} else if (!strcmp(name, "out_port")) {
|
|
fm->out_port = atoi(value);
|
|
} else if (!strcmp(name, "priority")) {
|
|
fm->cr.priority = atoi(value);
|
|
} else if (!strcmp(name, "idle_timeout")) {
|
|
fm->idle_timeout = atoi(value);
|
|
} else if (!strcmp(name, "hard_timeout")) {
|
|
fm->hard_timeout = atoi(value);
|
|
} else if (!strcmp(name, "cookie")) {
|
|
fm->cookie = htonll(str_to_u64(value));
|
|
} else if (parse_field_name(name, &f)) {
|
|
if (!strcmp(value, "*") || !strcmp(value, "ANY")) {
|
|
if (f->wildcard) {
|
|
fm->cr.wc.wildcards |= f->wildcard;
|
|
cls_rule_zero_wildcarded_fields(&fm->cr);
|
|
} else if (f->index == F_NW_SRC) {
|
|
cls_rule_set_nw_src_masked(&fm->cr, 0, 0);
|
|
} else if (f->index == F_NW_DST) {
|
|
cls_rule_set_nw_dst_masked(&fm->cr, 0, 0);
|
|
} else if (f->index == F_IPV6_SRC) {
|
|
cls_rule_set_ipv6_src_masked(&fm->cr,
|
|
&in6addr_any, &in6addr_any);
|
|
} else if (f->index == F_IPV6_DST) {
|
|
cls_rule_set_ipv6_dst_masked(&fm->cr,
|
|
&in6addr_any, &in6addr_any);
|
|
} else if (f->index == F_DL_VLAN) {
|
|
cls_rule_set_any_vid(&fm->cr);
|
|
} else if (f->index == F_DL_VLAN_PCP) {
|
|
cls_rule_set_any_pcp(&fm->cr);
|
|
} else {
|
|
NOT_REACHED();
|
|
}
|
|
} else {
|
|
parse_field_value(&fm->cr, f->index, value);
|
|
}
|
|
} else if (!strncmp(name, "reg", 3)
|
|
&& isdigit((unsigned char) name[3])) {
|
|
unsigned int reg_idx = atoi(name + 3);
|
|
if (reg_idx >= FLOW_N_REGS) {
|
|
ovs_fatal(0, "only %d registers supported", FLOW_N_REGS);
|
|
}
|
|
parse_reg_value(&fm->cr, reg_idx, value);
|
|
} else {
|
|
ovs_fatal(0, "unknown keyword %s", name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Parses 'string' as an OFPT_FLOW_MOD or NXT_FLOW_MOD with command 'command'
|
|
* (one of OFPFC_*) and appends the parsed OpenFlow message to 'packets'.
|
|
* '*cur_format' should initially contain the flow format currently configured
|
|
* on the connection; this function will add a message to change the flow
|
|
* format and update '*cur_format', if this is necessary to add the parsed
|
|
* flow. */
|
|
void
|
|
parse_ofp_flow_mod_str(struct list *packets, enum nx_flow_format *cur_format,
|
|
bool *flow_mod_table_id, char *string, uint16_t command)
|
|
{
|
|
bool is_del = command == OFPFC_DELETE || command == OFPFC_DELETE_STRICT;
|
|
enum nx_flow_format min_format, next_format;
|
|
struct cls_rule rule_copy;
|
|
struct ofpbuf actions;
|
|
struct ofpbuf *ofm;
|
|
struct flow_mod fm;
|
|
|
|
ofpbuf_init(&actions, 64);
|
|
parse_ofp_str(&fm, is_del ? NULL : &actions, string);
|
|
fm.command = command;
|
|
|
|
min_format = ofputil_min_flow_format(&fm.cr);
|
|
next_format = MAX(*cur_format, min_format);
|
|
if (next_format != *cur_format) {
|
|
struct ofpbuf *sff = ofputil_make_set_flow_format(next_format);
|
|
list_push_back(packets, &sff->list_node);
|
|
*cur_format = next_format;
|
|
}
|
|
|
|
/* Normalize a copy of the rule. 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. */
|
|
rule_copy = fm.cr;
|
|
ofputil_normalize_rule(&rule_copy, next_format);
|
|
|
|
if (fm.table_id != 0xff && !*flow_mod_table_id) {
|
|
struct ofpbuf *sff = ofputil_make_flow_mod_table_id(true);
|
|
list_push_back(packets, &sff->list_node);
|
|
*flow_mod_table_id = true;
|
|
}
|
|
|
|
ofm = ofputil_encode_flow_mod(&fm, *cur_format, *flow_mod_table_id);
|
|
list_push_back(packets, &ofm->list_node);
|
|
|
|
ofpbuf_uninit(&actions);
|
|
}
|
|
|
|
/* Similar to parse_ofp_flow_mod_str(), except that the string is read from
|
|
* 'stream' and the command is always OFPFC_ADD. Returns false if end-of-file
|
|
* is reached before reading a flow, otherwise true. */
|
|
bool
|
|
parse_ofp_flow_mod_file(struct list *packets,
|
|
enum nx_flow_format *cur, bool *flow_mod_table_id,
|
|
FILE *stream, uint16_t command)
|
|
{
|
|
struct ds s;
|
|
bool ok;
|
|
|
|
ds_init(&s);
|
|
ok = ds_get_preprocessed_line(&s, stream) == 0;
|
|
if (ok) {
|
|
parse_ofp_flow_mod_str(packets, cur, flow_mod_table_id,
|
|
ds_cstr(&s), command);
|
|
}
|
|
ds_destroy(&s);
|
|
|
|
return ok;
|
|
}
|
|
|
|
void
|
|
parse_ofp_flow_stats_request_str(struct flow_stats_request *fsr,
|
|
bool aggregate, char *string)
|
|
{
|
|
struct flow_mod fm;
|
|
|
|
parse_ofp_str(&fm, NULL, string);
|
|
fsr->aggregate = aggregate;
|
|
fsr->match = fm.cr;
|
|
fsr->out_port = fm.out_port;
|
|
fsr->table_id = fm.table_id;
|
|
}
|