@@ -25,6 +25,7 @@
#include <linux/icmp.h>
#include <linux/in.h>
#include <linux/ip.h>
+#include <linux/ipv6.h>
#include <linux/kernel.h>
#include <linux/kmod.h>
#include <linux/netdevice.h>
@@ -179,7 +180,8 @@ static __be16 __skb_network_protocol(struct sk_buff *skb)
static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
netdev_features_t features,
- bool tx_path)
+ bool tx_path,
+ sa_family_t sa_family)
{
void *iph = skb_network_header(skb);
int pkt_hlen = skb_inner_network_offset(skb); /* inner l2 + tunnel hdr. */
@@ -191,6 +193,7 @@ static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
__be16 proto = skb->protocol;
char cb[sizeof(skb->cb)];
+ OVS_GSO_CB(skb)->ipv6 = (sa_family == AF_INET6);
/* setup whole inner packet to get protocol. */
__skb_pull(skb, mac_offset);
skb->protocol = __skb_network_protocol(skb);
@@ -267,7 +270,7 @@ int rpl_ip_local_out(struct sk_buff *skb)
iph = ip_hdr(skb);
id = ntohs(iph->id);
- skb = tnl_skb_gso_segment(skb, 0, false);
+ skb = tnl_skb_gso_segment(skb, 0, false, AF_INET);
if (!skb || IS_ERR(skb))
return 0;
} else if (skb->ip_summed == CHECKSUM_PARTIAL) {
@@ -295,4 +298,47 @@ int rpl_ip_local_out(struct sk_buff *skb)
}
EXPORT_SYMBOL_GPL(rpl_ip_local_out);
+static int output_ipv6(struct sk_buff *skb)
+{
+ int ret = NETDEV_TX_OK;
+ int err;
+
+ memset(IP6CB(skb), 0, sizeof (*IP6CB(skb)));
+#undef ip6_local_out
+ err = ip6_local_out(skb);
+ if (unlikely(net_xmit_eval(err)))
+ ret = err;
+
+ return ret;
+}
+
+int rpl_ip6_local_out(struct sk_buff *skb)
+{
+ int ret = NETDEV_TX_OK;
+
+ if (!OVS_GSO_CB(skb)->fix_segment)
+ return output_ipv6(skb);
+
+ if (skb_is_gso(skb)) {
+ skb = tnl_skb_gso_segment(skb, 0, false, AF_INET6);
+ if (!skb || IS_ERR(skb))
+ return 0;
+ } else if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ int err;
+
+ err = skb_checksum_help(skb);
+ if (unlikely(err))
+ return 0;
+ }
+
+ while (skb) {
+ struct sk_buff *next_skb = skb->next;
+
+ skb->next = NULL;
+ ret = output_ipv6(skb);
+ skb = next_skb;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(rpl_ip6_local_out);
#endif /* 3.18 */
@@ -23,6 +23,7 @@ struct ovs_gso_cb {
#ifndef HAVE_INNER_NETWORK_HEADER
unsigned int inner_network_header;
#endif
+ bool ipv6;
};
#define OVS_GSO_CB(skb) ((struct ovs_gso_cb *)(skb)->cb)
@@ -148,6 +149,9 @@ static inline int skb_inner_mac_offset(const struct sk_buff *skb)
return skb_inner_mac_header(skb) - skb->data;
}
+#define ip6_local_out rpl_ip6_local_out
+int rpl_ip6_local_out(struct sk_buff *skb);
+
#define skb_reset_inner_headers rpl_skb_reset_inner_headers
static inline void skb_reset_inner_headers(struct sk_buff *skb)
{
@@ -16,12 +16,12 @@ static inline void ip6tunnel_xmit(struct sock *sk, struct sk_buff *skb,
int pkt_len, err;
pkt_len = skb->len - skb_inner_network_offset(skb);
- /* TODO: Fix GSO for ipv6 */
#ifdef HAVE_IP6_LOCAL_OUT_SK
err = ip6_local_out_sk(sk, skb);
#else
err = ip6_local_out(skb);
#endif
+
if (net_xmit_eval(err) != 0)
pkt_len = net_xmit_eval(err);
else
@@ -141,31 +141,6 @@ void rpl_setup_udp_tunnel_sock(struct net *net, struct socket *sock,
}
EXPORT_SYMBOL_GPL(rpl_setup_udp_tunnel_sock);
-void ovs_udp_gso(struct sk_buff *skb)
-{
- int udp_offset = skb_transport_offset(skb);
- struct udphdr *uh;
-
- uh = udp_hdr(skb);
- uh->len = htons(skb->len - udp_offset);
-}
-EXPORT_SYMBOL_GPL(ovs_udp_gso);
-
-void ovs_udp_csum_gso(struct sk_buff *skb)
-{
- struct iphdr *iph = ip_hdr(skb);
- int udp_offset = skb_transport_offset(skb);
-
- ovs_udp_gso(skb);
-
- /* csum segment if tunnel sets skb with csum. The cleanest way
- * to do this just to set it up from scratch. */
- skb->ip_summed = CHECKSUM_NONE;
- udp_set_csum(false, skb, iph->saddr, iph->daddr,
- skb->len - udp_offset);
-}
-EXPORT_SYMBOL_GPL(ovs_udp_csum_gso);
-
void rpl_udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk,
struct sk_buff *skb, __be32 src, __be32 dst,
__u8 tos, __u8 ttl, __be16 df, __be16 src_port,
@@ -288,4 +263,40 @@ int rpl_udp_tunnel6_xmit_skb(struct dst_entry *dst, struct sock *sk,
return 0;
}
#endif
+
+void ovs_udp_gso(struct sk_buff *skb)
+{
+ int udp_offset = skb_transport_offset(skb);
+ struct udphdr *uh;
+
+ uh = udp_hdr(skb);
+ uh->len = htons(skb->len - udp_offset);
+}
+EXPORT_SYMBOL_GPL(ovs_udp_gso);
+
+void ovs_udp_csum_gso(struct sk_buff *skb)
+{
+ int udp_offset = skb_transport_offset(skb);
+
+ ovs_udp_gso(skb);
+
+ if (!OVS_GSO_CB(skb)->ipv6) {
+ struct iphdr *iph = ip_hdr(skb);
+
+ /* csum segment if tunnel sets skb with csum. The cleanest way
+ * to do this just to set it up from scratch. */
+ udp_set_csum(false, skb, iph->saddr, iph->daddr,
+ skb->len - udp_offset);
+#if IS_ENABLED(CONFIG_IPV6)
+ } else {
+ struct ipv6hdr *ip6h;
+
+ ip6h = ipv6_hdr(skb);
+ udp6_set_csum(false, skb, &ip6h->saddr, &ip6h->daddr,
+ skb->len - udp_offset);
+#endif
+ }
+}
+EXPORT_SYMBOL_GPL(ovs_udp_csum_gso);
+
#endif