X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=datapath%2Flinux%2Fcompat%2Fgso.c;fp=datapath%2Flinux%2Fcompat%2Fgso.c;h=7379a579ceae9b8b675454097748202ec874aae9;hb=5ebaf571f9a3531d06eb52b62ac237ab7292f7b0;hp=0000000000000000000000000000000000000000;hpb=1a02b7689b01db7fff2cda87ac311389ff524283;p=sliver-openvswitch.git diff --git a/datapath/linux/compat/gso.c b/datapath/linux/compat/gso.c new file mode 100644 index 000000000..7379a579c --- /dev/null +++ b/datapath/linux/compat/gso.c @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2007-2013 Nicira, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "gso.h" + +static __be16 skb_network_protocol(struct sk_buff *skb) +{ + __be16 type = skb->protocol; + int vlan_depth = ETH_HLEN; + + while (type == htons(ETH_P_8021Q) || type == htons(ETH_P_8021AD)) { + struct vlan_hdr *vh; + + if (unlikely(!pskb_may_pull(skb, vlan_depth + VLAN_HLEN))) + return 0; + + vh = (struct vlan_hdr *)(skb->data + vlan_depth); + type = vh->h_vlan_encapsulated_proto; + vlan_depth += VLAN_HLEN; + } + + return type; +} + +static struct sk_buff *tnl_skb_gso_segment(struct sk_buff *skb, + netdev_features_t features, + bool tx_path) +{ + struct iphdr *iph = ip_hdr(skb); + int pkt_hlen = skb_inner_network_offset(skb); /* inner l2 + tunnel hdr. */ + int mac_offset = skb_inner_mac_offset(skb); + struct sk_buff *skb1 = skb; + struct sk_buff *segs; + __be16 proto = skb->protocol; + + /* setup whole inner packet to get protocol. */ + __skb_pull(skb, mac_offset); + skb->protocol = skb_network_protocol(skb); + + /* setup l3 packet to gso, to get around segmentation bug on older kernel.*/ + __skb_pull(skb, (pkt_hlen - mac_offset)); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_reset_transport_header(skb); + + segs = __skb_gso_segment(skb, 0, tx_path); + if (!segs || IS_ERR(segs)) + goto free; + + skb = segs; + while (skb) { + __skb_push(skb, pkt_hlen); + skb_reset_mac_header(skb); + skb_reset_network_header(skb); + skb_set_transport_header(skb, sizeof(struct iphdr)); + skb->mac_len = 0; + + memcpy(ip_hdr(skb), iph, pkt_hlen); + if (OVS_GSO_CB(skb)->fix_segment) + OVS_GSO_CB(skb)->fix_segment(skb); + + skb->protocol = proto; + skb = skb->next; + } +free: + consume_skb(skb1); + return segs; +} + +int rpl_ip_local_out(struct sk_buff *skb) +{ + int ret = NETDEV_TX_OK; + int id; + + if (skb_is_gso(skb)) { + struct iphdr *iph; + + iph = ip_hdr(skb); + id = ntohs(iph->id); + skb = tnl_skb_gso_segment(skb, 0, false); + 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; + id = -1; + } + + while (skb) { + struct sk_buff *next_skb = skb->next; + struct iphdr *iph; + int err; + + skb->next = NULL; + + iph = ip_hdr(skb); + if (id >= 0) + iph->id = htons(id++); + + memset(IPCB(skb), 0, sizeof(*IPCB(skb))); + +#undef ip_local_out + err = ip_local_out(skb); + if (unlikely(net_xmit_eval(err))) + ret = err; + + skb = next_skb; + } + return ret; +}