text: /*
   * 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
   */
  
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  
  #include <linux/in.h>
  #include <linux/in_route.h>
  #include <linux/inetdevice.h>
  #include <linux/jhash.h>
  #include <linux/list.h>
  #include <linux/kernel.h>
  #include <linux/version.h>
  #include <linux/workqueue.h>
  #include <linux/rculist.h>
  #include <net/ip_tunnels.h>
  #include <net/route.h>
  #include <net/xfrm.h>
  
  #include "compat.h"
  #include "gso.h"
  
  int iptunnel_xmit(struct rtable *rt,
  		  struct sk_buff *skb,
  		  __be32 src, __be32 dst, __u8 proto,
  		  __u8 tos, __u8 ttl, __be16 df)
  {
  	int pkt_len = skb->len;
  	struct iphdr *iph;
  	int err;
  
  	nf_reset(skb);
  	secpath_reset(skb);
  	skb_clear_rxhash(skb);
  	skb_dst_drop(skb);
  	skb_dst_set(skb, &rt_dst(rt));
  #if 0
  	/* Do not clear ovs_skb_cb.  It will be done in gso code. */
  	memset(IPCB(skb), 0, sizeof(*IPCB(skb)));
  #endif
  
  	/* Push down and install the IP header. */
  	__skb_push(skb, sizeof(struct iphdr));
  	skb_reset_network_header(skb);
  
  	iph = ip_hdr(skb);
  
  	iph->version	=	4;
  	iph->ihl	=	sizeof(struct iphdr) >> 2;
  	iph->frag_off	=	df;
  	iph->protocol	=	proto;
  	iph->tos	=	tos;
  	iph->daddr	=	dst;
  	iph->saddr	=	src;
  	iph->ttl	=	ttl;
  	__ip_select_ident(iph, &rt_dst(rt), (skb_shinfo(skb)->gso_segs ?: 1) - 1);
  
  	err = ip_local_out(skb);
  	if (unlikely(net_xmit_eval(err)))
  		pkt_len = 0;
  	return pkt_len;
  }
  
  int iptunnel_pull_header(struct sk_buff *skb, int hdr_len, __be16 inner_proto)
  {
  	if (unlikely(!pskb_may_pull(skb, hdr_len)))
  		return -ENOMEM;
  
  	skb_pull_rcsum(skb, hdr_len);
  
  	if (inner_proto == htons(ETH_P_TEB)) {
  		struct ethhdr *eh;
  
  		if (unlikely(!pskb_may_pull(skb, ETH_HLEN)))
  			return -ENOMEM;
  
  		eh = (struct ethhdr *)skb->data;
  
  		if (likely(ntohs(eh->h_proto) >= ETH_P_802_3_MIN))
  			skb->protocol = eh->h_proto;
  		else
  			skb->protocol = htons(ETH_P_802_2);
  
  	} else {
  		skb->protocol = inner_proto;
  	}
  
  	nf_reset(skb);
  	secpath_reset(skb);
  	skb_clear_rxhash(skb);
  	skb_dst_drop(skb);
  	vlan_set_tci(skb, 0);
  	skb_set_queue_mapping(skb, 0);
  	skb->pkt_type = PACKET_HOST;
  	return 0;
  }
  