Skip to content

Commit

Permalink
datapath: add ipv6 'set' action
Browse files Browse the repository at this point in the history
This patch adds ipv6 set action functionality. It allows to change
traffic class, flow label, hop-limit, ipv6 source and destination
address fields.

Acked-by: Jesse Gross <[email protected]>
Signed-off-by: Ansis Atteka <[email protected]>
  • Loading branch information
Ansis Atteka committed Nov 13, 2012
1 parent 3d97490 commit bc7a5ac
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 1 deletion.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ v1.9.0 - xx xxx xxxx
- The tunneling code no longer assumes input and output keys are symmetric.
If they are not, PMTUD needs to be disabled for tunneling to work. Note
this only applies to flow-based keys.
- Datapath: Support for ipv6 set action.
- FreeBSD is now a supported platform, thanks to code contributions from
Gaetano Catalli, Ed Maste, and Giuseppe Lettieri.
- ovs-bugtool: New --ovs option to report only OVS related information.
Expand Down
94 changes: 94 additions & 0 deletions datapath/actions.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <linux/if_arp.h>
#include <linux/if_vlan.h>
#include <net/ip.h>
#include <net/ipv6.h>
#include <net/checksum.h>
#include <net/dsfield.h>

Expand Down Expand Up @@ -166,6 +167,54 @@ static void set_ip_addr(struct sk_buff *skb, struct iphdr *nh,
*addr = new_addr;
}

static void update_ipv6_checksum(struct sk_buff *skb, u8 l4_proto,
__be32 addr[4], const __be32 new_addr[4])
{
int transport_len = skb->len - skb_transport_offset(skb);

if (l4_proto == IPPROTO_TCP) {
if (likely(transport_len >= sizeof(struct tcphdr)))
inet_proto_csum_replace16(&tcp_hdr(skb)->check, skb,
addr, new_addr, 1);
} else if (l4_proto == 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;
}
}
}
}

static void set_ipv6_addr(struct sk_buff *skb, u8 l4_proto,
__be32 addr[4], const __be32 new_addr[4],
bool recalculate_csum)
{
if (recalculate_csum)
update_ipv6_checksum(skb, l4_proto, addr, new_addr);

skb_clear_rxhash(skb);
memcpy(addr, new_addr, sizeof(__be32[4]));
}

static void set_ipv6_tc(struct ipv6hdr *nh, u8 tc)
{
nh->priority = 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 & 0x000F0000) >> 16;
nh->flow_lbl[1] = (fl & 0x0000FF00) >> 8;
nh->flow_lbl[2] = fl & 0x000000FF;
}

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));
Expand Down Expand Up @@ -199,6 +248,47 @@ 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;
__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, ipv6_key->ipv6_proto, saddr,
ipv6_key->ipv6_src, true);

if (memcmp(ipv6_key->ipv6_dst, daddr, sizeof(ipv6_key->ipv6_dst))) {
unsigned int offset = 0;
int flags = OVS_IP6T_FH_F_SKIP_RH;
bool recalc_csum = true;

if (ipv6_ext_hdr(nh->nexthdr))
recalc_csum = ipv6_find_hdr(skb, &offset,
NEXTHDR_ROUTING, NULL,
&flags) != NEXTHDR_ROUTING;

set_ipv6_addr(skb, ipv6_key->ipv6_proto, daddr,
ipv6_key->ipv6_dst, recalc_csum);
}

set_ipv6_tc(nh, ipv6_key->ipv6_tclass);
set_ipv6_fl(nh, ntohl(ipv6_key->ipv6_label));
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)
Expand Down Expand Up @@ -373,6 +463,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;
Expand Down
24 changes: 24 additions & 0 deletions datapath/checksum.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,30 @@ static inline void inet_proto_csum_replace4(__sum16 *sum, struct sk_buff *skb,
}
#endif

#if defined(NEED_CSUM_NORMALIZE) || LINUX_VERSION_CODE < KERNEL_VERSION(3,7,0)
#define inet_proto_csum_replace16 rpl_inet_proto_csum_replace16
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 (get_ip_summed(skb) != OVS_CSUM_PARTIAL) {
*sum = csum_fold(csum_partial(diff, sizeof(diff),
~csum_unfold(*sum)));
if (get_ip_summed(skb) == OVS_CSUM_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

#ifdef NEED_CSUM_NORMALIZE
static inline void update_csum_start(struct sk_buff *skb, int delta)
{
Expand Down
20 changes: 20 additions & 0 deletions datapath/datapath.c
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ static int validate_set(const struct nlattr *a,
switch (key_type) {
const struct ovs_key_ipv4 *ipv4_key;
const struct ovs_key_ipv4_tunnel *tun_key;
const struct ovs_key_ipv6 *ipv6_key;

case OVS_KEY_ATTR_PRIORITY:
case OVS_KEY_ATTR_TUN_ID:
Expand Down Expand Up @@ -616,6 +617,25 @@ static int validate_set(const struct nlattr *a,

break;

case OVS_KEY_ATTR_IPV6:
if (flow_key->eth.type != htons(ETH_P_IPV6))
return -EINVAL;

if (!flow_key->ip.proto)
return -EINVAL;

ipv6_key = nla_data(ovs_key);
if (ipv6_key->ipv6_proto != flow_key->ip.proto)
return -EINVAL;

if (ipv6_key->ipv6_frag != flow_key->ip.frag)
return -EINVAL;

if (ntohl(ipv6_key->ipv6_label) & 0xFFF00000)
return -EINVAL;

break;

case OVS_KEY_ATTR_TCP:
if (flow_key->ip.proto != IPPROTO_TCP)
return -EINVAL;
Expand Down
1 change: 1 addition & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ openvswitch (1.9.0-1) unstable; urgency=low
- The tunneling code no longer assumes input and output keys are symmetric.
If they are not, PMTUD needs to be disabled for tunneling to work. Note
this only applies to flow-based keys.
- Datapath: Support for ipv6 set action.
- FreeBSD is now a supported platform, thanks to code contributions from
Gaetano Catalli, Ed Maste, and Giuseppe Lettieri.
- ovs-bugtool: New --ovs option to report only OVS related information.
Expand Down
15 changes: 15 additions & 0 deletions lib/csum.c
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,21 @@ recalc_csum32(ovs_be16 old_csum, ovs_be32 old_u32, ovs_be32 new_u32)
old_u32 >> 16, new_u32 >> 16);
}

/* Returns the new checksum for a packet in which the checksum field previously
* contained 'old_csum' and in which a field that contained 'old_u32[4]' was
* changed to contain 'new_u32[4]'. */
ovs_be16
recalc_csum128(ovs_be16 old_csum, ovs_be32 old_u32[4],
const ovs_be32 new_u32[4])
{
ovs_be16 new_csum = old_csum;
int i;

for (i = 0; i < 4; ++i) {
new_csum = recalc_csum32(new_csum, old_u32[i], new_u32[i]);
}
return new_csum;
}
#else /* __CHECKER__ */
/* Making sparse happy with these functions also makes them unreadable, so
* don't bother to show it their implementations. */
Expand Down
2 changes: 2 additions & 0 deletions lib/csum.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ uint32_t csum_continue(uint32_t partial, const void *, size_t);
ovs_be16 csum_finish(uint32_t partial);
ovs_be16 recalc_csum16(ovs_be16 old_csum, ovs_be16 old_u16, ovs_be16 new_u16);
ovs_be16 recalc_csum32(ovs_be16 old_csum, ovs_be32 old_u32, ovs_be32 new_u32);
ovs_be16 recalc_csum128(ovs_be16 old_csum, ovs_be32 old_u32[4],
const ovs_be32 new_u32[4]);

#endif /* csum.h */
9 changes: 8 additions & 1 deletion lib/dpif-netdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -1181,13 +1181,13 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
{
enum ovs_key_attr type = nl_attr_type(a);
const struct ovs_key_ipv4 *ipv4_key;
const struct ovs_key_ipv6 *ipv6_key;
const struct ovs_key_tcp *tcp_key;
const struct ovs_key_udp *udp_key;

switch (type) {
case OVS_KEY_ATTR_TUN_ID:
case OVS_KEY_ATTR_PRIORITY:
case OVS_KEY_ATTR_IPV6:
case OVS_KEY_ATTR_IPV4_TUNNEL:
/* not implemented */
break;
Expand All @@ -1203,6 +1203,13 @@ execute_set_action(struct ofpbuf *packet, const struct nlattr *a)
ipv4_key->ipv4_tos, ipv4_key->ipv4_ttl);
break;

case OVS_KEY_ATTR_IPV6:
ipv6_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_ipv6));
packet_set_ipv6(packet, ipv6_key->ipv6_proto, ipv6_key->ipv6_src,
ipv6_key->ipv6_dst, ipv6_key->ipv6_tclass,
ipv6_key->ipv6_label, ipv6_key->ipv6_hlimit);
break;

case OVS_KEY_ATTR_TCP:
tcp_key = nl_attr_get_unspec(a, sizeof(struct ovs_key_tcp));
packet_set_tcp_port(packet, tcp_key->tcp_src, tcp_key->tcp_dst);
Expand Down
Loading

0 comments on commit bc7a5ac

Please sign in to comment.