Files
openflow/switch/dp_act.c
T
Ben Pfaff 6b617bac12 Move exported headers to include/openflow, private headers to lib/.
This makes it easier to install the headers, or to use them directly from
another software package with -I$(openflow)/include, without invading
the top-level include file namespace.
2008-10-29 15:15:45 -07:00

477 lines
15 KiB
C

/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
* Junior University
*
* We are making the OpenFlow specification and associated documentation
* (Software) available for public use and benefit with the expectation
* that others will use, modify and enhance the Software and contribute
* those enhancements back to the community. However, since we would
* like to make the Software available for broadest use, with as few
* restrictions as possible permission is hereby granted, free of
* charge, to any person obtaining a copy of this Software to deal in
* the Software under the copyrights without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* The name and trademarks of copyright holder(s) may NOT be used in
* advertising or publicity pertaining to the Software or any
* derivatives without specific, written prior permission.
*/
/* Functions for executing OpenFlow actions. */
#include <arpa/inet.h>
#include "csum.h"
#include "packets.h"
#include "dp_act.h"
#include "openflow/nicira-ext.h"
#include "nx_act.h"
static uint16_t
validate_output(struct datapath *dp, const struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_output *oa = (struct ofp_action_output *)ah;
/* To prevent loops, make sure there's no action to send to the
* OFP_TABLE virtual port.
*/
if (oa->port == htons(OFPP_NONE) || oa->port == key->flow.in_port) {
return OFPBAC_BAD_OUT_PORT;
}
return ACT_VALIDATION_OK;
}
static void
do_output(struct datapath *dp, struct ofpbuf *buffer, int in_port,
size_t max_len, int out_port, bool ignore_no_fwd)
{
if (out_port != OFPP_CONTROLLER) {
dp_output_port(dp, buffer, in_port, out_port, ignore_no_fwd);
} else {
dp_output_control(dp, buffer, in_port, max_len, OFPR_ACTION);
}
}
/* Modify vlan tag control information (TCI). Only sets the TCI bits
* indicated by 'mask'. If no vlan tag is present, one is added.
*/
static void
modify_vlan_tci(struct ofpbuf *buffer, struct sw_flow_key *key,
uint16_t tci, uint16_t mask)
{
struct vlan_eth_header *veh;
if (key->flow.dl_vlan != htons(OFP_VLAN_NONE)) {
/* Modify vlan id, but maintain other TCI values */
veh = buffer->l2;
veh->veth_tci &= ~htons(mask);
veh->veth_tci |= htons(tci);
} else {
/* Insert new vlan id. */
struct eth_header *eh = buffer->l2;
struct vlan_eth_header tmp;
memcpy(tmp.veth_dst, eh->eth_dst, ETH_ADDR_LEN);
memcpy(tmp.veth_src, eh->eth_src, ETH_ADDR_LEN);
tmp.veth_type = htons(ETH_TYPE_VLAN);
tmp.veth_tci = htons(tci);
tmp.veth_next_type = eh->eth_type;
veh = ofpbuf_push_uninit(buffer, VLAN_HEADER_LEN);
memcpy(veh, &tmp, sizeof tmp);
buffer->l2 = (char*)buffer->l2 - VLAN_HEADER_LEN;
}
key->flow.dl_vlan = veh->veth_tci & htons(VLAN_VID_MASK);
}
/* Remove an existing vlan header if it exists. */
static void
vlan_pull_tag(struct ofpbuf *buffer)
{
struct vlan_eth_header *veh = buffer->l2;
if (veh->veth_type == htons(ETH_TYPE_VLAN)) {
struct eth_header tmp;
memcpy(tmp.eth_dst, veh->veth_dst, ETH_ADDR_LEN);
memcpy(tmp.eth_src, veh->veth_src, ETH_ADDR_LEN);
tmp.eth_type = veh->veth_next_type;
buffer->size -= VLAN_HEADER_LEN;
buffer->data = (char*)buffer->data + VLAN_HEADER_LEN;
buffer->l2 = (char*)buffer->l2 + VLAN_HEADER_LEN;
memcpy(buffer->data, &tmp, sizeof tmp);
}
}
static void
set_vlan_vid(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_vlan_vid *va = (struct ofp_action_vlan_vid *)ah;
uint16_t tci = ntohs(va->vlan_vid);
modify_vlan_tci(buffer, key, tci, VLAN_VID_MASK);
}
static void
set_vlan_pcp(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_vlan_pcp *va = (struct ofp_action_vlan_pcp *)ah;
uint16_t tci = (uint16_t)va->vlan_pcp << 13;
modify_vlan_tci(buffer, key, tci, VLAN_PCP_MASK);
}
static void
strip_vlan(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
vlan_pull_tag(buffer);
key->flow.dl_vlan = htons(OFP_VLAN_NONE);
}
static void
set_dl_addr(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_dl_addr *da = (struct ofp_action_dl_addr *)ah;
struct eth_header *eh = buffer->l2;
if (da->type == htons(OFPAT_SET_DL_SRC)) {
memcpy(eh->eth_src, da->dl_addr, sizeof eh->eth_src);
} else {
memcpy(eh->eth_dst, da->dl_addr, sizeof eh->eth_dst);
}
}
static void
set_nw_addr(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_nw_addr *na = (struct ofp_action_nw_addr *)ah;
uint16_t eth_proto = ntohs(key->flow.dl_type);
if (eth_proto == ETH_TYPE_IP) {
struct ip_header *nh = buffer->l3;
uint8_t nw_proto = key->flow.nw_proto;
uint32_t new, *field;
new = na->nw_addr;
field = na->type == OFPAT_SET_NW_SRC ? &nh->ip_src : &nh->ip_dst;
if (nw_proto == IP_TYPE_TCP) {
struct tcp_header *th = buffer->l4;
th->tcp_csum = recalc_csum32(th->tcp_csum, *field, new);
} else if (nw_proto == IP_TYPE_UDP) {
struct udp_header *th = buffer->l4;
if (th->udp_csum) {
th->udp_csum = recalc_csum32(th->udp_csum, *field, new);
if (!th->udp_csum) {
th->udp_csum = 0xffff;
}
}
}
nh->ip_csum = recalc_csum32(nh->ip_csum, *field, new);
*field = new;
}
}
static void
set_tp_port(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_tp_port *ta = (struct ofp_action_tp_port *)ah;
uint16_t eth_proto = ntohs(key->flow.dl_type);
if (eth_proto == ETH_TYPE_IP) {
uint8_t nw_proto = key->flow.nw_proto;
uint16_t new, *field;
new = ta->tp_port;
if (nw_proto == IP_TYPE_TCP) {
struct tcp_header *th = buffer->l4;
field = ta->type == OFPAT_SET_TP_SRC ? &th->tcp_src : &th->tcp_dst;
th->tcp_csum = recalc_csum16(th->tcp_csum, *field, new);
*field = new;
} else if (nw_proto == IP_TYPE_UDP) {
struct udp_header *th = buffer->l4;
field = ta->type == OFPAT_SET_TP_SRC ? &th->udp_src : &th->udp_dst;
th->udp_csum = recalc_csum16(th->udp_csum, *field, new);
*field = new;
}
}
}
struct openflow_action {
size_t min_size;
size_t max_size;
uint16_t (*validate)(struct datapath *dp,
const struct sw_flow_key *key,
const struct ofp_action_header *ah);
void (*execute)(struct ofpbuf *buffer,
struct sw_flow_key *key,
const struct ofp_action_header *ah);
};
static const struct openflow_action of_actions[] = {
[OFPAT_OUTPUT] = {
sizeof(struct ofp_action_output),
sizeof(struct ofp_action_output),
validate_output,
NULL /* This is optimized into execute_actions */
},
[OFPAT_SET_VLAN_VID] = {
sizeof(struct ofp_action_vlan_vid),
sizeof(struct ofp_action_vlan_vid),
NULL,
set_vlan_vid
},
[OFPAT_SET_VLAN_PCP] = {
sizeof(struct ofp_action_vlan_pcp),
sizeof(struct ofp_action_vlan_pcp),
NULL,
set_vlan_pcp
},
[OFPAT_STRIP_VLAN] = {
sizeof(struct ofp_action_header),
sizeof(struct ofp_action_header),
NULL,
strip_vlan
},
[OFPAT_SET_DL_SRC] = {
sizeof(struct ofp_action_dl_addr),
sizeof(struct ofp_action_dl_addr),
NULL,
set_dl_addr
},
[OFPAT_SET_DL_DST] = {
sizeof(struct ofp_action_dl_addr),
sizeof(struct ofp_action_dl_addr),
NULL,
set_dl_addr
},
[OFPAT_SET_NW_SRC] = {
sizeof(struct ofp_action_nw_addr),
sizeof(struct ofp_action_nw_addr),
NULL,
set_nw_addr
},
[OFPAT_SET_NW_DST] = {
sizeof(struct ofp_action_nw_addr),
sizeof(struct ofp_action_nw_addr),
NULL,
set_nw_addr
},
[OFPAT_SET_TP_SRC] = {
sizeof(struct ofp_action_tp_port),
sizeof(struct ofp_action_tp_port),
NULL,
set_tp_port
},
[OFPAT_SET_TP_DST] = {
sizeof(struct ofp_action_tp_port),
sizeof(struct ofp_action_tp_port),
NULL,
set_tp_port
}
/* OFPAT_VENDOR is not here, since it would blow up the array size. */
};
/* Validate built-in OpenFlow actions. Either returns ACT_VALIDATION_OK
* or an OFPET_BAD_ACTION error code. */
static uint16_t
validate_ofpat(struct datapath *dp, const struct sw_flow_key *key,
const struct ofp_action_header *ah, uint16_t type, uint16_t len)
{
int ret = ACT_VALIDATION_OK;
const struct openflow_action *act = &of_actions[type];
if ((len < act->min_size) || (len > act->max_size)) {
return OFPBAC_BAD_LEN;
}
if (act->validate) {
ret = act->validate(dp, key, ah);
}
return ret;
}
/* Validate vendor-defined actions. Either returns ACT_VALIDATION_OK
* or an OFPET_BAD_ACTION error code. */
static uint16_t
validate_vendor(struct datapath *dp, const struct sw_flow_key *key,
const struct ofp_action_header *ah, uint16_t len)
{
struct ofp_action_vendor_header *avh;
int ret = ACT_VALIDATION_OK;
if (len < sizeof(struct ofp_action_vendor_header)) {
return OFPBAC_BAD_LEN;
}
avh = (struct ofp_action_vendor_header *)ah;
switch(ntohl(avh->vendor)) {
case NX_VENDOR_ID:
ret = nx_validate_act(dp, key, avh, len);
break;
default:
return OFPBAC_BAD_VENDOR;
}
return ret;
}
/* Validates a list of actions. If a problem is found, a code for the
* OFPET_BAD_ACTION error type is returned. If the action list validates,
* ACT_VALIDATION_OK is returned. */
uint16_t
validate_actions(struct datapath *dp, const struct sw_flow_key *key,
const struct ofp_action_header *actions, size_t actions_len)
{
uint8_t *p = (uint8_t *)actions;
int err;
while (actions_len >= sizeof(struct ofp_action_header)) {
struct ofp_action_header *ah = (struct ofp_action_header *)p;
size_t len = ntohs(ah->len);
uint16_t type;
/* Make there's enough remaining data for the specified length
* and that the action length is a multiple of 64 bits. */
if ((actions_len < len) || (len % 8) != 0) {
return OFPBAC_BAD_LEN;
}
type = ntohs(ah->type);
if (type < ARRAY_SIZE(of_actions)) {
err = validate_ofpat(dp, key, ah, type, len);
if (err != ACT_VALIDATION_OK) {
return err;
}
} else if (type == OFPAT_VENDOR) {
err = validate_vendor(dp, key, ah, len);
if (err != ACT_VALIDATION_OK) {
return err;
}
} else {
return OFPBAC_BAD_TYPE;
}
p += len;
actions_len -= len;
}
/* Check if there's any trailing garbage. */
if (actions_len != 0) {
return OFPBAC_BAD_LEN;
}
return ACT_VALIDATION_OK;
}
/* Execute a built-in OpenFlow action against 'buffer'. */
static void
execute_ofpat(struct ofpbuf *buffer, struct sw_flow_key *key,
const struct ofp_action_header *ah, uint16_t type)
{
const struct openflow_action *act = &of_actions[type];
if (act->execute) {
act->execute(buffer, key, ah);
}
}
/* Execute a vendor-defined action against 'buffer'. */
static void
execute_vendor(struct ofpbuf *buffer, const struct sw_flow_key *key,
const struct ofp_action_header *ah)
{
struct ofp_action_vendor_header *avh
= (struct ofp_action_vendor_header *)ah;
switch(ntohl(avh->vendor)) {
case NX_VENDOR_ID:
nx_execute_act(buffer, key, avh);
break;
default:
/* This should not be possible due to prior validation. */
printf("attempt to execute action with unknown vendor: %#x\n",
ntohl(avh->vendor));
break;
}
}
/* Execute a list of actions against 'buffer'. */
void execute_actions(struct datapath *dp, struct ofpbuf *buffer,
struct sw_flow_key *key,
const struct ofp_action_header *actions, size_t actions_len,
int ignore_no_fwd)
{
/* Every output action needs a separate clone of 'buffer', but the common
* case is just a single output action, so that doing a clone and then
* freeing the original buffer is wasteful. So the following code is
* slightly obscure just to avoid that. */
int prev_port;
size_t max_len=0; /* Initialze to make compiler happy */
uint16_t in_port = ntohs(key->flow.in_port);
uint8_t *p = (uint8_t *)actions;
prev_port = -1;
/* The action list was already validated, so we can be a bit looser
* in our sanity-checking. */
while (actions_len > 0) {
struct ofp_action_header *ah = (struct ofp_action_header *)p;
size_t len = htons(ah->len);
if (prev_port != -1) {
do_output(dp, ofpbuf_clone(buffer), in_port, max_len,
prev_port, ignore_no_fwd);
prev_port = -1;
}
if (ah->type == htons(OFPAT_OUTPUT)) {
struct ofp_action_output *oa = (struct ofp_action_output *)p;
prev_port = ntohs(oa->port);
max_len = ntohs(oa->max_len);
} else {
uint16_t type = ntohs(ah->type);
if (type < ARRAY_SIZE(of_actions)) {
execute_ofpat(buffer, key, ah, type);
} else if (type == OFPAT_VENDOR) {
execute_vendor(buffer, key, ah);
}
}
p += len;
actions_len -= len;
}
if (prev_port != -1) {
do_output(dp, buffer, in_port, max_len, prev_port, ignore_no_fwd);
} else {
ofpbuf_delete(buffer);
}
}