This patch adds ipv6 set action functionality. It allows to change
traffic class, flow label, hop-limit, ipv6 source and destination
address fields.

Signed-off-by: Ansis Atteka <aatt...@nicira.com>
---
 datapath/actions.c |   83 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 datapath/compat.h  |   23 +++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/datapath/actions.c b/datapath/actions.c
index ec9b595..7e60b0f 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -165,6 +165,46 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr 
*nh,
        *addr = new_addr;
 }
 
+static void set_ipv6_addr(struct sk_buff *skb, struct ipv6hdr *nh,
+                         __be32 addr[4], const __be32 new_addr[4])
+{
+       int transport_len = skb->len - skb_transport_offset(skb);
+
+       if (nh->nexthdr == IPPROTO_TCP) {
+               if (likely(transport_len >= sizeof(struct tcphdr)))
+                       inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
+                                                 addr, new_addr, 1);
+       } else if (nh->nexthdr == IPPROTO_UDP) {
+               if (likely(transport_len >= sizeof(struct udphdr))) {
+                       struct udphdr *uh = udp_hdr(skb);
+
+                       if (uh->check ||
+                           get_ip_summed(skb) == OVS_CSUM_PARTIAL) {
+                               inet_proto_csum_replace16(&uh->check, skb,
+                                                         addr, new_addr, 1);
+                               if (!uh->check)
+                                       uh->check = CSUM_MANGLED_0;
+                       }
+               }
+       }
+
+       skb_clear_rxhash(skb);
+       memcpy(addr, new_addr, sizeof(addr));
+}
+
+static void set_ipv6_tc(struct ipv6hdr *nh, __u8 tc)
+{
+       nh->priority = (nh->priority & 0xf0) | (tc >> 4);
+       nh->flow_lbl[0] = (nh->flow_lbl[0] & 0x0f) | ((tc & 0x0f) << 4);
+}
+
+static void set_ipv6_fl(struct ipv6hdr *nh, __u32 fl)
+{
+       nh->flow_lbl[0] = (nh->flow_lbl[0] & 0xf0) | ((fl & 0x0f0000) >> 16);
+       nh->flow_lbl[1] = (fl & 0x00ff00) >> 8;
+       nh->flow_lbl[2] = fl & 0x0000ff;
+}
+
 static void set_ip_ttl(struct sk_buff *skb, struct iphdr *nh, u8 new_ttl)
 {
        csum_replace2(&nh->check, htons(nh->ttl << 8), htons(new_ttl << 8));
@@ -198,6 +238,45 @@ static int set_ipv4(struct sk_buff *skb, const struct 
ovs_key_ipv4 *ipv4_key)
        return 0;
 }
 
+static int set_ipv6(struct sk_buff *skb, const struct ovs_key_ipv6 *ipv6_key)
+{
+       struct ipv6hdr *nh;
+       int err;
+       __u8 tc;
+       __be32 fl;
+       __be32 *saddr;
+       __be32 *daddr;
+
+       err = make_writable(skb, skb_network_offset(skb) +
+                                sizeof(struct ipv6hdr));
+       if (unlikely(err))
+               return err;
+
+       nh = ipv6_hdr(skb);
+       saddr = (__be32 *)&nh->saddr;
+       daddr = (__be32 *)&nh->daddr;
+
+       if (memcmp(ipv6_key->ipv6_src, saddr, sizeof(ipv6_key->ipv6_src)))
+               set_ipv6_addr(skb, nh, saddr, ipv6_key->ipv6_src);
+
+       if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst)))
+               set_ipv6_addr(skb, nh, daddr, ipv6_key->ipv6_dst);
+
+       tc = (nh->priority << 4) | (nh->flow_lbl[0] >> 4);
+       if (ipv6_key->ipv6_tclass != tc)
+               set_ipv6_tc(nh, ipv6_key->ipv6_tclass);
+
+       fl = (nh->flow_lbl[0] & 0x0f) << 16 | nh->flow_lbl[1] << 8 |
+             nh->flow_lbl[2];
+       if (ipv6_key->ipv6_label != fl)
+               set_ipv6_fl(nh, ipv6_key->ipv6_label);
+
+       if (ipv6_key->ipv6_hlimit != nh->hop_limit)
+               nh->hop_limit = ipv6_key->ipv6_hlimit;
+
+       return 0;
+}
+
 /* Must follow make_writable() since that can move the skb data. */
 static void set_tp_port(struct sk_buff *skb, __be16 *port,
                         __be16 new_port, __sum16 *check)
@@ -354,6 +433,10 @@ static int execute_set_action(struct sk_buff *skb,
                err = set_ipv4(skb, nla_data(nested_attr));
                break;
 
+       case OVS_KEY_ATTR_IPV6:
+               err = set_ipv6(skb, nla_data(nested_attr));
+               break;
+
        case OVS_KEY_ATTR_TCP:
                err = set_tcp(skb, nla_data(nested_attr));
                break;
diff --git a/datapath/compat.h b/datapath/compat.h
index 3113b96..fb660d2 100644
--- a/datapath/compat.h
+++ b/datapath/compat.h
@@ -81,4 +81,27 @@ static inline void skb_clear_rxhash(struct sk_buff *skb)
 #define SET_NETNSOK    .netnsok = true,
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0)
+static inline void inet_proto_csum_replace16(__sum16 *sum,
+                                            struct sk_buff *skb,
+                                            const __be32 *from,
+                                            const __be32 *to,
+                                            int pseudohdr)
+{
+       __be32 diff[] = {
+               ~from[0], ~from[1], ~from[2], ~from[3],
+               to[0], to[1], to[2], to[3],
+       };
+       if (skb->ip_summed != CHECKSUM_PARTIAL) {
+               *sum = csum_fold(csum_partial(diff, sizeof(diff),
+                                ~csum_unfold(*sum)));
+               if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
+                       skb->csum = ~csum_partial(diff, sizeof(diff),
+                                                 ~skb->csum);
+       } else if (pseudohdr)
+               *sum = ~csum_fold(csum_partial(diff, sizeof(diff),
+                                 csum_unfold(*sum)));
+}
+#endif
+
 #endif /* compat.h */
-- 
1.7.9.5

_______________________________________________
dev mailing list
dev@openvswitch.org
http://openvswitch.org/mailman/listinfo/dev

Reply via email to