From 10f72e3da94f99cf71ebe49cc03ef49d88a55656 Mon Sep 17 00:00:00 2001 From: Joe Stringer Date: Thu, 22 Aug 2013 20:24:43 +1200 Subject: [PATCH] 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 Signed-off-by: Joe Stringer Signed-off-by: Ben Pfaff --- datapath/actions.c | 39 +++++++++++ datapath/datapath.c | 6 ++ datapath/flow.c | 65 +++++++++++++++++++ datapath/flow.h | 8 +-- datapath/linux/Modules.mk | 4 +- datapath/linux/compat/include/linux/sctp.h | 13 ++++ datapath/linux/compat/include/net/ipv6.h | 4 ++ .../linux/compat/include/net/sctp/checksum.h | 31 +++++++++ include/linux/openvswitch.h | 6 ++ 9 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 datapath/linux/compat/include/linux/sctp.h create mode 100644 datapath/linux/compat/include/net/sctp/checksum.h 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 #include #include +#include #include #include #include @@ -31,6 +32,7 @@ #include #include #include +#include #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 #include #include +#include #include #include #include @@ -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 + +#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 +#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 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25) +#include +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,25) */ +#include_next +#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 diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h index a119b149b..09c26b56f 100644 --- a/include/linux/openvswitch.h +++ b/include/linux/openvswitch.h @@ -282,6 +282,7 @@ enum ovs_key_attr { OVS_KEY_ATTR_ND, /* struct ovs_key_nd */ OVS_KEY_ATTR_SKB_MARK, /* u32 skb mark */ OVS_KEY_ATTR_TUNNEL, /* Nested set of ovs_tunnel attributes */ + OVS_KEY_ATTR_SCTP, /* struct ovs_key_sctp */ #ifdef __KERNEL__ OVS_KEY_ATTR_IPV4_TUNNEL, /* struct ovs_key_ipv4_tunnel */ @@ -364,6 +365,11 @@ struct ovs_key_udp { __be16 udp_dst; }; +struct ovs_key_sctp { + __be16 sctp_src; + __be16 sctp_dst; +}; + struct ovs_key_icmp { __u8 icmp_type; __u8 icmp_code; -- 2.43.0