datapath: Support for Linux kernel 3.9.
[sliver-openvswitch.git] / datapath / linux / compat / gso.c
1 /*
2  * Copyright (c) 2007-2013 Nicira, Inc.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of version 2 of the GNU General Public
6  * License as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11  * General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
16  * 02110-1301, USA
17  */
18
19 #include <linux/module.h>
20 #include <linux/if.h>
21 #include <linux/if_tunnel.h>
22 #include <linux/icmp.h>
23 #include <linux/in.h>
24 #include <linux/ip.h>
25 #include <linux/kernel.h>
26 #include <linux/kmod.h>
27 #include <linux/netdevice.h>
28 #include <linux/skbuff.h>
29 #include <linux/spinlock.h>
30
31 #include <net/gre.h>
32 #include <net/icmp.h>
33 #include <net/protocol.h>
34 #include <net/route.h>
35 #include <net/xfrm.h>
36
37 #include "gso.h"
38
39 static __be16 __skb_network_protocol(struct sk_buff *skb)
40 {
41         __be16 type = skb->protocol;
42         int vlan_depth = ETH_HLEN;
43
44         while (type == htons(ETH_P_8021Q) || type == htons(ETH_P_8021AD)) {
45                 struct vlan_hdr *vh;
46
47                 if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN)))
48                         return 0;
49
50                 vh = (struct vlan_hdr *)(skb->data + vlan_depth);
51                 type = vh->h_vlan_encapsulated_proto;
52                 vlan_depth += VLAN_HLEN;
53         }
54
55         return type;
56 }
57
58 static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb,
59                                            netdev_features_t features,
60                                            bool tx_path)
61 {
62         struct iphdr *iph = ip_hdr(skb);
63         int pkt_hlen = skb_inner_network_offset(skb); /* inner l2 + tunnel hdr. */
64         int mac_offset = skb_inner_mac_offset(skb);
65         struct sk_buff *skb1 = skb;
66         struct sk_buff *segs;
67         __be16 proto = skb->protocol;
68         char cb[sizeof(skb->cb)];
69
70         /* setup whole inner packet to get protocol. */
71         __skb_pull(skb, mac_offset);
72         skb->protocol = __skb_network_protocol(skb);
73
74         /* setup l3 packet to gso, to get around segmentation bug on older kernel.*/
75         __skb_pull(skb, (pkt_hlen - mac_offset));
76         skb_reset_mac_header(skb);
77         skb_reset_network_header(skb);
78         skb_reset_transport_header(skb);
79
80         /* From 3.9 kernel skb->cb is used by skb gso. Therefore
81          * make copy of it to restore it back. */
82         memcpy(cb, skb->cb, sizeof(cb));
83
84         segs = __skb_gso_segment(skb, 0, tx_path);
85         if (!segs || IS_ERR(segs))
86                 goto free;
87
88         skb = segs;
89         while (skb) {
90                 __skb_push(skb, pkt_hlen);
91                 skb_reset_mac_header(skb);
92                 skb_reset_network_header(skb);
93                 skb_set_transport_header(skb, sizeof(struct iphdr));
94                 skb->mac_len = 0;
95
96                 memcpy(ip_hdr(skb), iph, pkt_hlen);
97                 memcpy(skb->cb, cb, sizeof(cb));
98                 if (OVS_GSO_CB(skb)->fix_segment)
99                         OVS_GSO_CB(skb)->fix_segment(skb);
100
101                 skb->protocol = proto;
102                 skb = skb->next;
103         }
104 free:
105         consume_skb(skb1);
106         return segs;
107 }
108
109 int rpl_ip_local_out(struct sk_buff *skb)
110 {
111         int ret = NETDEV_TX_OK;
112         int id = -1;
113
114         if (skb_is_gso(skb)) {
115                 struct iphdr *iph;
116
117                 iph = ip_hdr(skb);
118                 id = ntohs(iph->id);
119                 skb = tnl_skb_gso_segment(skb, 0, false);
120                 if (!skb || IS_ERR(skb))
121                         return 0;
122         }  else if (skb->ip_summed == CHECKSUM_PARTIAL) {
123                 int err;
124
125                 err = skb_checksum_help(skb);
126                 if (unlikely(err))
127                         return 0;
128         }
129
130         while (skb) {
131                 struct sk_buff *next_skb = skb->next;
132                 struct iphdr *iph;
133                 int err;
134
135                 skb->next = NULL;
136
137                 iph = ip_hdr(skb);
138                 if (id >= 0)
139                         iph->id = htons(id++);
140
141                 memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
142
143 #undef ip_local_out
144                 err = ip_local_out(skb);
145                 if (unlikely(net_xmit_eval(err)))
146                         ret = err;
147
148                 skb = next_skb;
149         }
150         return ret;
151 }