+/* 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 "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);
+ }
+}