- /* Every output action needs a separate clone of 'skb', but the common
- * case is just a single output action, so that doing a clone and
- * then freeing the original skbuff 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 eth_proto;
- int i;
-
- prev_port = -1;
- eth_proto = ntohs(key->dl_type);
-
- for (i = 0; i < n_actions; i++) {
- const struct ofp_action *a = &actions[i];
-
- if (prev_port != -1) {
- do_output(dp, skb_clone(skb, GFP_ATOMIC),
- max_len, prev_port);
- prev_port = -1;
- }
-
- if (likely(a->type == htons(OFPAT_OUTPUT))) {
- prev_port = ntohs(a->arg.output.port);
- max_len = ntohs(a->arg.output.max_len);
- } else {
- if (!make_writable(&skb)) {
- printk("make_writable failed\n");
- break;
- }
- skb = execute_setter(skb, eth_proto, key, a);
- }
- }
- if (prev_port != -1)
- do_output(dp, skb, max_len, prev_port);
- else
- kfree_skb(skb);
-}
-
-/* Updates 'sum', which is a field in 'skb''s data, given that a 4-byte field
- * covered by the sum has been changed from 'from' to 'to'. If set,
- * 'pseudohdr' indicates that the field is in the TCP or UDP pseudo-header.
- * Based on nf_proto_csum_replace4. */
-static void update_csum(__sum16 *sum, struct sk_buff *skb,
- __be32 from, __be32 to, int pseudohdr)
-{
- __be32 diff[] = { ~from, to };
- if (skb->ip_summed != CHECKSUM_PARTIAL) {
- *sum = csum_fold(csum_partial((char *)diff, sizeof(diff),
- ~csum_unfold(*sum)));
- if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
- skb->csum = ~csum_partial((char *)diff, sizeof(diff),
- ~skb->csum);
- } else if (pseudohdr)
- *sum = ~csum_fold(csum_partial((char *)diff, sizeof(diff),
- csum_unfold(*sum)));
-}
-
-static void modify_nh(struct sk_buff *skb, uint16_t eth_proto,
- uint8_t nw_proto, const struct ofp_action *a)
-{
- if (eth_proto == ETH_P_IP) {
- struct iphdr *nh = ip_hdr(skb);
- uint32_t new, *field;
-
- new = a->arg.nw_addr;
-
- if (a->type == htons(OFPAT_SET_NW_SRC))
- field = &nh->saddr;
- else
- field = &nh->daddr;
-
- if (nw_proto == IPPROTO_TCP) {
- struct tcphdr *th = tcp_hdr(skb);
- update_csum(&th->check, skb, *field, new, 1);
- } else if (nw_proto == IPPROTO_UDP) {
- struct udphdr *th = udp_hdr(skb);
- update_csum(&th->check, skb, *field, new, 1);
- }
- update_csum(&nh->check, skb, *field, new, 0);
- *field = new;
- }
-}
-
-static void modify_th(struct sk_buff *skb, uint16_t eth_proto,
- uint8_t nw_proto, const struct ofp_action *a)
-{
- if (eth_proto == ETH_P_IP) {
- uint16_t new, *field;
-
- new = a->arg.tp;
-
- if (nw_proto == IPPROTO_TCP) {
- struct tcphdr *th = tcp_hdr(skb);
-
- if (a->type == htons(OFPAT_SET_TP_SRC))
- field = &th->source;
- else
- field = &th->dest;
-
- update_csum(&th->check, skb, *field, new, 1);
- *field = new;
- } else if (nw_proto == IPPROTO_UDP) {
- struct udphdr *th = udp_hdr(skb);
-
- if (a->type == htons(OFPAT_SET_TP_SRC))
- field = &th->source;
- else
- field = &th->dest;
-
- update_csum(&th->check, skb, *field, new, 1);
- *field = new;
- }
- }
-}
-
-static struct sk_buff *vlan_pull_tag(struct sk_buff *skb)
-{
- struct vlan_ethhdr *vh = vlan_eth_hdr(skb);
- struct ethhdr *eh;
-
-
- /* Verify we were given a vlan packet */
- if (vh->h_vlan_proto != htons(ETH_P_8021Q))
- return skb;
-
- memmove(skb->data + VLAN_HLEN, skb->data, 2 * VLAN_ETH_ALEN);
-
- eh = (struct ethhdr *)skb_pull(skb, VLAN_HLEN);
-
- skb->protocol = eh->h_proto;
- skb->mac_header += VLAN_HLEN;
-
- return skb;
-}
-
-static struct sk_buff *modify_vlan(struct sk_buff *skb,
- const struct sw_flow_key *key, const struct ofp_action *a)
-{
- uint16_t new_id = a->arg.vlan_id;
-
- if (new_id != OFP_VLAN_NONE) {
- if (key->dl_vlan != htons(OFP_VLAN_NONE)) {
- /* Modify vlan id, but maintain other TCI values */
- struct vlan_ethhdr *vh = vlan_eth_hdr(skb);
- vh->h_vlan_TCI = (vh->h_vlan_TCI
- & ~(htons(VLAN_VID_MASK))) | htons(new_id);
- } else {
- /* Add vlan header */
- skb = vlan_put_tag(skb, new_id);
- }
- } else {
- /* Remove an existing vlan header if it exists */
- vlan_pull_tag(skb);
- }
-
- return skb;