diff options
author | Joe Stringer <joe@wand.net.nz> | 2013-08-22 20:24:43 +1200 |
---|---|---|
committer | Ben Pfaff <blp@nicira.com> | 2013-08-22 09:29:39 -0700 |
commit | 10f72e3da94f99cf71ebe49cc03ef49d88a55656 (patch) | |
tree | 0d05ef058240c0aafa97de149e7d33680e799c20 /datapath | |
parent | 97025b23a71ace7c1e640e37891e7d11a87e83fa (diff) | |
download | openvswitch-10f72e3da94f99cf71ebe49cc03ef49d88a55656.tar.gz |
datapath: Add SCTP support
This patch adds support for rewriting SCTP src,dst ports similar to the
functionality already available for TCP/UDP.
Rewriting SCTP ports is expensive due to double-recalculation of the
SCTP checksums; this is performed to ensure that packets traversing OVS
with invalid checksums will continue to the destination with any
checksum corruption intact.
Reviewed-by: Simon Horman <horms@verge.net.au>
Signed-off-by: Joe Stringer <joe@wand.net.nz>
Signed-off-by: Ben Pfaff <blp@nicira.com>
Diffstat (limited to 'datapath')
-rw-r--r-- | datapath/actions.c | 39 | ||||
-rw-r--r-- | datapath/datapath.c | 6 | ||||
-rw-r--r-- | datapath/flow.c | 65 | ||||
-rw-r--r-- | datapath/flow.h | 8 | ||||
-rw-r--r-- | datapath/linux/Modules.mk | 4 | ||||
-rw-r--r-- | datapath/linux/compat/include/linux/sctp.h | 13 | ||||
-rw-r--r-- | datapath/linux/compat/include/net/ipv6.h | 4 | ||||
-rw-r--r-- | datapath/linux/compat/include/net/sctp/checksum.h | 31 |
8 files changed, 165 insertions, 5 deletions
diff --git a/datapath/actions.c b/datapath/actions.c index 2c09d57a8..fa4b9045f 100644 --- a/datapath/actions.c +++ b/datapath/actions.c @@ -22,6 +22,7 @@ #include <linux/in.h> #include <linux/ip.h> #include <linux/openvswitch.h> +#include <linux/sctp.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/in6.h> @@ -31,6 +32,7 @@ #include <net/ipv6.h> #include <net/checksum.h> #include <net/dsfield.h> +#include <net/sctp/checksum.h> #include "checksum.h" #include "datapath.h" @@ -360,6 +362,39 @@ static int set_tcp(struct sk_buff *skb, const struct ovs_key_tcp *tcp_port_key) return 0; } +static int set_sctp(struct sk_buff *skb, + const struct ovs_key_sctp *sctp_port_key) +{ + struct sctphdr *sh; + int err; + unsigned int sctphoff = skb_transport_offset(skb); + + err = make_writable(skb, sctphoff + sizeof(struct sctphdr)); + if (unlikely(err)) + return err; + + sh = sctp_hdr(skb); + if (sctp_port_key->sctp_src != sh->source || + sctp_port_key->sctp_dst != sh->dest) { + __le32 old_correct_csum, new_csum, old_csum; + + old_csum = sh->checksum; + old_correct_csum = sctp_compute_cksum(skb, sctphoff); + + sh->source = sctp_port_key->sctp_src; + sh->dest = sctp_port_key->sctp_dst; + + new_csum = sctp_compute_cksum(skb, sctphoff); + + /* Carry any checksum errors through. */ + sh->checksum = old_csum ^ old_correct_csum ^ new_csum; + + skb_clear_rxhash(skb); + } + + return 0; +} + static int do_output(struct datapath *dp, struct sk_buff *skb, int out_port) { struct vport *vport; @@ -469,6 +504,10 @@ static int execute_set_action(struct sk_buff *skb, case OVS_KEY_ATTR_UDP: err = set_udp(skb, nla_data(nested_attr)); break; + + case OVS_KEY_ATTR_SCTP: + err = set_sctp(skb, nla_data(nested_attr)); + break; } return err; diff --git a/datapath/datapath.c b/datapath/datapath.c index 48f17c091..27deec8d9 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -726,6 +726,12 @@ static int validate_set(const struct nlattr *a, return validate_tp_port(flow_key); + case OVS_KEY_ATTR_SCTP: + if (flow_key->ip.proto != IPPROTO_SCTP) + return -EINVAL; + + return validate_tp_port(flow_key); + default: return -EINVAL; } diff --git a/datapath/flow.c b/datapath/flow.c index 6a85292b3..62357b9e3 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -34,6 +34,7 @@ #include <linux/if_arp.h> #include <linux/ip.h> #include <linux/ipv6.h> +#include <linux/sctp.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/icmp.h> @@ -130,6 +131,7 @@ static bool ovs_match_validate(const struct sw_flow_match *match, | (1ULL << OVS_KEY_ATTR_IPV6) | (1ULL << OVS_KEY_ATTR_TCP) | (1ULL << OVS_KEY_ATTR_UDP) + | (1ULL << OVS_KEY_ATTR_SCTP) | (1ULL << OVS_KEY_ATTR_ICMP) | (1ULL << OVS_KEY_ATTR_ICMPV6) | (1ULL << OVS_KEY_ATTR_ARP) @@ -160,6 +162,12 @@ static bool ovs_match_validate(const struct sw_flow_match *match, mask_allowed |= 1ULL << OVS_KEY_ATTR_UDP; } + if (match->key->ip.proto == IPPROTO_SCTP) { + key_expected |= 1ULL << OVS_KEY_ATTR_SCTP; + if (match->mask && (match->mask->key.ip.proto == 0xff)) + mask_allowed |= 1ULL << OVS_KEY_ATTR_SCTP; + } + if (match->key->ip.proto == IPPROTO_TCP) { key_expected |= 1ULL << OVS_KEY_ATTR_TCP; if (match->mask && (match->mask->key.ip.proto == 0xff)) @@ -186,6 +194,12 @@ static bool ovs_match_validate(const struct sw_flow_match *match, mask_allowed |= 1ULL << OVS_KEY_ATTR_UDP; } + if (match->key->ip.proto == IPPROTO_SCTP) { + key_expected |= 1ULL << OVS_KEY_ATTR_SCTP; + if (match->mask && (match->mask->key.ip.proto == 0xff)) + mask_allowed |= 1ULL << OVS_KEY_ATTR_SCTP; + } + if (match->key->ip.proto == IPPROTO_TCP) { key_expected |= 1ULL << OVS_KEY_ATTR_TCP; if (match->mask && (match->mask->key.ip.proto == 0xff)) @@ -281,6 +295,12 @@ static bool udphdr_ok(struct sk_buff *skb) sizeof(struct udphdr)); } +static bool sctphdr_ok(struct sk_buff *skb) +{ + return pskb_may_pull(skb, skb_transport_offset(skb) + + sizeof(struct sctphdr)); +} + static bool icmphdr_ok(struct sk_buff *skb) { return pskb_may_pull(skb, skb_transport_offset(skb) + @@ -899,6 +919,12 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key) key->ipv4.tp.src = udp->source; key->ipv4.tp.dst = udp->dest; } + } else if (key->ip.proto == IPPROTO_SCTP) { + if (sctphdr_ok(skb)) { + struct sctphdr *sctp = sctp_hdr(skb); + key->ipv4.tp.src = sctp->source; + key->ipv4.tp.dst = sctp->dest; + } } else if (key->ip.proto == IPPROTO_ICMP) { if (icmphdr_ok(skb)) { struct icmphdr *icmp = icmp_hdr(skb); @@ -961,6 +987,12 @@ int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key) key->ipv6.tp.src = udp->source; key->ipv6.tp.dst = udp->dest; } + } else if (key->ip.proto == NEXTHDR_SCTP) { + if (sctphdr_ok(skb)) { + struct sctphdr *sctp = sctp_hdr(skb); + key->ipv6.tp.src = sctp->source; + key->ipv6.tp.dst = sctp->dest; + } } else if (key->ip.proto == NEXTHDR_ICMP) { if (icmp6hdr_ok(skb)) { error = parse_icmpv6(skb, key, nh_len); @@ -1095,6 +1127,7 @@ const int ovs_key_lens[OVS_KEY_ATTR_MAX + 1] = { [OVS_KEY_ATTR_IPV6] = sizeof(struct ovs_key_ipv6), [OVS_KEY_ATTR_TCP] = sizeof(struct ovs_key_tcp), [OVS_KEY_ATTR_UDP] = sizeof(struct ovs_key_udp), + [OVS_KEY_ATTR_SCTP] = sizeof(struct ovs_key_sctp), [OVS_KEY_ATTR_ICMP] = sizeof(struct ovs_key_icmp), [OVS_KEY_ATTR_ICMPV6] = sizeof(struct ovs_key_icmpv6), [OVS_KEY_ATTR_ARP] = sizeof(struct ovs_key_arp), @@ -1514,6 +1547,24 @@ static int ovs_key_from_nlattrs(struct sw_flow_match *match, u64 attrs, attrs &= ~(1ULL << OVS_KEY_ATTR_UDP); } + if (attrs & (1ULL << OVS_KEY_ATTR_SCTP)) { + const struct ovs_key_sctp *sctp_key; + + sctp_key = nla_data(a[OVS_KEY_ATTR_SCTP]); + if (orig_attrs & (1ULL << OVS_KEY_ATTR_IPV4)) { + SW_FLOW_KEY_PUT(match, ipv4.tp.src, + sctp_key->sctp_src, is_mask); + SW_FLOW_KEY_PUT(match, ipv4.tp.dst, + sctp_key->sctp_dst, is_mask); + } else { + SW_FLOW_KEY_PUT(match, ipv6.tp.src, + sctp_key->sctp_src, is_mask); + SW_FLOW_KEY_PUT(match, ipv6.tp.dst, + sctp_key->sctp_dst, is_mask); + } + attrs &= ~(1ULL << OVS_KEY_ATTR_SCTP); + } + if (attrs & (1ULL << OVS_KEY_ATTR_ICMP)) { const struct ovs_key_icmp *icmp_key; @@ -1836,6 +1887,20 @@ int ovs_flow_to_nlattrs(const struct sw_flow_key *swkey, udp_key->udp_src = output->ipv6.tp.src; udp_key->udp_dst = output->ipv6.tp.dst; } + } else if (swkey->ip.proto == IPPROTO_SCTP) { + struct ovs_key_sctp *sctp_key; + + nla = nla_reserve(skb, OVS_KEY_ATTR_SCTP, sizeof(*sctp_key)); + if (!nla) + goto nla_put_failure; + sctp_key = nla_data(nla); + if (swkey->eth.type == htons(ETH_P_IP)) { + sctp_key->sctp_src = swkey->ipv4.tp.src; + sctp_key->sctp_dst = swkey->ipv4.tp.dst; + } else if (swkey->eth.type == htons(ETH_P_IPV6)) { + sctp_key->sctp_src = swkey->ipv6.tp.src; + sctp_key->sctp_dst = swkey->ipv6.tp.dst; + } } else if (swkey->eth.type == htons(ETH_P_IP) && swkey->ip.proto == IPPROTO_ICMP) { struct ovs_key_icmp *icmp_key; diff --git a/datapath/flow.h b/datapath/flow.h index d8277b5b2..bc91ec91e 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -101,8 +101,8 @@ struct sw_flow_key { } addr; union { struct { - __be16 src; /* TCP/UDP source port. */ - __be16 dst; /* TCP/UDP destination port. */ + __be16 src; /* TCP/UDP/SCTP source port. */ + __be16 dst; /* TCP/UDP/SCTP destination port. */ } tp; struct { u8 sha[ETH_ALEN]; /* ARP source hardware address. */ @@ -117,8 +117,8 @@ struct sw_flow_key { } addr; __be32 label; /* IPv6 flow label. */ struct { - __be16 src; /* TCP/UDP source port. */ - __be16 dst; /* TCP/UDP destination port. */ + __be16 src; /* TCP/UDP/SCTP source port. */ + __be16 dst; /* TCP/UDP/SCTP destination port. */ } tp; struct { struct in6_addr target; /* ND target address. */ diff --git a/datapath/linux/Modules.mk b/datapath/linux/Modules.mk index 5f9c79239..178cd5e35 100644 --- a/datapath/linux/Modules.mk +++ b/datapath/linux/Modules.mk @@ -57,6 +57,7 @@ openvswitch_headers += \ linux/compat/include/linux/rcupdate.h \ linux/compat/include/linux/reciprocal_div.h \ linux/compat/include/linux/rtnetlink.h \ + linux/compat/include/linux/sctp.h \ linux/compat/include/linux/skbuff.h \ linux/compat/include/linux/slab.h \ linux/compat/include/linux/stddef.h \ @@ -81,4 +82,5 @@ openvswitch_headers += \ linux/compat/include/net/route.h \ linux/compat/include/net/sock.h \ linux/compat/include/net/netns/generic.h \ - linux/compat/include/net/vxlan.h + linux/compat/include/net/vxlan.h \ + linux/compat/include/net/sctp/checksum.h diff --git a/datapath/linux/compat/include/linux/sctp.h b/datapath/linux/compat/include/linux/sctp.h new file mode 100644 index 000000000..b91c9c69d --- /dev/null +++ b/datapath/linux/compat/include/linux/sctp.h @@ -0,0 +1,13 @@ +#ifndef __LINUX_SCTP_WRAPPER_H +#define __LINUX_SCTP_WRAPPER_H 1 + +#include_next <linux/sctp.h> + +#ifndef HAVE_SKBUFF_HEADER_HELPERS +static inline struct sctphdr *sctp_hdr(const struct sk_buff *skb) +{ + return (struct sctphdr *)skb_transport_header(skb); +} +#endif /* HAVE_SKBUFF_HEADER_HELPERS */ + +#endif diff --git a/datapath/linux/compat/include/net/ipv6.h b/datapath/linux/compat/include/net/ipv6.h index 7ab234acb..71f470883 100644 --- a/datapath/linux/compat/include/net/ipv6.h +++ b/datapath/linux/compat/include/net/ipv6.h @@ -5,6 +5,10 @@ #include_next <net/ipv6.h> +#ifndef NEXTHDR_SCTP +#define NEXTHDR_SCTP 132 /* Stream Control Transport Protocol */ +#endif + #if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0) #define ipv6_skip_exthdr rpl_ipv6_skip_exthdr extern int ipv6_skip_exthdr(const struct sk_buff *skb, int start, diff --git a/datapath/linux/compat/include/net/sctp/checksum.h b/datapath/linux/compat/include/net/sctp/checksum.h new file mode 100644 index 000000000..11fb0b634 --- /dev/null +++ b/datapath/linux/compat/include/net/sctp/checksum.h @@ -0,0 +1,31 @@ +#ifndef __SCTP_CHECKSUM_WRAPPER_H +#define __SCTP_CHECKSUM_WRAPPER_H 1 + +#include <linux/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) +#include <net/sctp/sctp.h> +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) */ +#include_next <net/sctp/checksum.h> +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0) +static inline __le32 sctp_compute_cksum(const struct sk_buff *skb, + unsigned int offset) +{ + const struct sk_buff *iter; + + __u32 crc32 = sctp_start_cksum(skb->data + offset, + skb_headlen(skb) - offset); + skb_walk_frags(skb, iter) + crc32 = sctp_update_cksum((__u8 *) iter->data, + skb_headlen(iter), crc32); + + /* Open-code sctp_end_cksum() to avoid a sparse warning due to a bug in + * sparse annotations in Linux fixed in 3.10 in commit eee1d5a14 (sctp: + * Correct type and usage of sctp_end_cksum()). */ + return cpu_to_le32(~crc32); +} +#endif + +#endif |