Skip to content
Snippets Groups Projects
ip6_output.c 40 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	IPv6 output functions
    
     *	Linux INET6 implementation
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     *	Authors:
    
     *	Pedro Roque		<roque@di.fc.ul.pt>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     *
     *	Based on linux/net/ipv4/ip_output.c
     *
     *	This program is free software; you can redistribute it and/or
     *      modify it under the terms of the GNU General Public License
     *      as published by the Free Software Foundation; either version
     *      2 of the License, or (at your option) any later version.
     *
     *	Changes:
     *	A.N.Kuznetsov	:	airthmetics in fragmentation.
     *				extension headers are implemented.
     *				route changes now work.
     *				ip6_forward does not confuse sniffers.
     *				etc.
     *
     *      H. von Brand    :       Added missing #include <linux/string.h>
     *	Imran Patel	: 	frag id should be in NBO
     *      Kazunori MIYAZAWA @USAGI
     *			:       add ip6_append_data and related functions
     *				for datagram xmit
     */
    
    #include <linux/errno.h>
    
    Herbert Xu's avatar
    Herbert Xu committed
    #include <linux/kernel.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    #include <linux/string.h>
    #include <linux/socket.h>
    #include <linux/net.h>
    #include <linux/netdevice.h>
    #include <linux/if_arp.h>
    #include <linux/in6.h>
    #include <linux/tcp.h>
    #include <linux/route.h>
    
    #include <linux/module.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #include <linux/netfilter.h>
    #include <linux/netfilter_ipv6.h>
    
    #include <net/sock.h>
    #include <net/snmp.h>
    
    #include <net/ipv6.h>
    #include <net/ndisc.h>
    #include <net/protocol.h>
    #include <net/ip6_route.h>
    #include <net/addrconf.h>
    #include <net/rawv6.h>
    #include <net/icmp.h>
    #include <net/xfrm.h>
    #include <net/checksum.h>
    
    #include <linux/mroute6.h>
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Herbert Xu's avatar
    Herbert Xu committed
    int __ip6_local_out(struct sk_buff *skb)
    {
    	int len;
    
    	len = skb->len - sizeof(struct ipv6hdr);
    	if (len > IPV6_MAXPLEN)
    		len = 0;
    	ipv6_hdr(skb)->payload_len = htons(len);
    
    
    	return nf_hook(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL,
    		       skb_dst(skb)->dev, dst_output);
    
    Herbert Xu's avatar
    Herbert Xu committed
    }
    
    int ip6_local_out(struct sk_buff *skb)
    {
    	int err;
    
    	err = __ip6_local_out(skb);
    	if (likely(err == 1))
    		err = dst_output(skb);
    
    	return err;
    }
    EXPORT_SYMBOL_GPL(ip6_local_out);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /* dev_loopback_xmit for use with netfilter. */
    static int ip6_dev_loopback_xmit(struct sk_buff *newskb)
    {
    
    	skb_reset_mac_header(newskb);
    
    	__skb_pull(newskb, skb_network_offset(newskb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	newskb->pkt_type = PACKET_LOOPBACK;
    	newskb->ip_summed = CHECKSUM_UNNECESSARY;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	WARN_ON(!skb_dst(newskb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	netif_rx_ni(newskb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    
    static int ip6_finish_output2(struct sk_buff *skb)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct dst_entry *dst = skb_dst(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct net_device *dev = dst->dev;
    
    	skb->protocol = htons(ETH_P_IPV6);
    	skb->dev = dev;
    
    
    	if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    		struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    		if (!(dev->flags & IFF_LOOPBACK) && sk_mc_loop(skb->sk) &&
    
    		    ((mroute6_socket(dev_net(dev), skb) &&
    
    		     !(IP6CB(skb)->flags & IP6SKB_FORWARDED)) ||
    
    		     ipv6_chk_mcast_addr(dev, &ipv6_hdr(skb)->daddr,
    					 &ipv6_hdr(skb)->saddr))) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
    
    			/* Do not check for IFF_ALLMULTI; multicast routing
    			   is not supported in any case.
    			 */
    			if (newskb)
    
    				NF_HOOK(NFPROTO_IPV6, NF_INET_POST_ROUTING,
    					newskb, NULL, newskb->dev,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    					ip6_dev_loopback_xmit);
    
    
    			if (ipv6_hdr(skb)->hop_limit == 0) {
    
    				IP6_INC_STATS(dev_net(dev), idev,
    					      IPSTATS_MIB_OUTDISCARDS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				kfree_skb(skb);
    				return 0;
    			}
    		}
    
    
    		IP6_UPD_PO_STATS(dev_net(dev), idev, IPSTATS_MIB_OUTMCAST,
    				skb->len);
    
    	if (dst->hh)
    		return neigh_hh_output(dst->hh, skb);
    	else if (dst->neighbour)
    		return dst->neighbour->output(skb);
    
    	IP6_INC_STATS_BH(dev_net(dst->dev),
    			 ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
    	kfree_skb(skb);
    	return -EINVAL;
    
    static int ip6_finish_output(struct sk_buff *skb)
    {
    	if ((skb->len > ip6_skb_dst_mtu(skb) && !skb_is_gso(skb)) ||
    	    dst_allfrag(skb_dst(skb)))
    		return ip6_fragment(skb, ip6_finish_output2);
    	else
    		return ip6_finish_output2(skb);
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    int ip6_output(struct sk_buff *skb)
    {
    
    	struct net_device *dev = skb_dst(skb)->dev;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct inet6_dev *idev = ip6_dst_idev(skb_dst(skb));
    
    		IP6_INC_STATS(dev_net(dev), idev,
    
    			      IPSTATS_MIB_OUTDISCARDS);
    
    	return NF_HOOK_COND(NFPROTO_IPV6, NF_INET_POST_ROUTING, skb, NULL, dev,
    			    ip6_finish_output,
    			    !(IP6CB(skb)->flags & IP6SKB_REROUTED));
    
     *	xmit an sk_buff (used by TCP, SCTP and DCCP)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     */
    
    int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
    
    	     struct ipv6_txoptions *opt)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct net *net = sock_net(sk);
    
    	struct ipv6_pinfo *np = inet6_sk(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct in6_addr *first_hop = &fl->fl6_dst;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct dst_entry *dst = skb_dst(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6hdr *hdr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int seg_len = skb->len;
    
    	int hlimit = -1;
    	int tclass = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	u32 mtu;
    
    	if (opt) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		/* First: exthdrs may take lots of space (~8K for now)
    		   MAX_HEADER is not enough.
    		 */
    		head_room = opt->opt_nflen + opt->opt_flen;
    		seg_len += head_room;
    		head_room += sizeof(struct ipv6hdr) + LL_RESERVED_SPACE(dst->dev);
    
    		if (skb_headroom(skb) < head_room) {
    			struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room);
    
    			if (skb2 == NULL) {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    				IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    					      IPSTATS_MIB_OUTDISCARDS);
    				kfree_skb(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				return -ENOBUFS;
    			}
    
    			kfree_skb(skb);
    			skb = skb2;
    
    			skb_set_owner_w(skb, sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    		if (opt->opt_flen)
    			ipv6_push_frag_opts(skb, opt, &proto);
    		if (opt->opt_nflen)
    			ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop);
    	}
    
    
    	skb_push(skb, sizeof(struct ipv6hdr));
    	skb_reset_network_header(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	Fill in the IPv6 header
    	 */
    
    	if (np) {
    		tclass = np->tclass;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		hlimit = np->hop_limit;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (hlimit < 0)
    
    		hlimit = ip6_dst_hoplimit(dst);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	*(__be32 *)hdr = htonl(0x60000000 | (tclass << 20)) | fl->fl6_flowlabel;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	hdr->payload_len = htons(seg_len);
    	hdr->nexthdr = proto;
    	hdr->hop_limit = hlimit;
    
    	ipv6_addr_copy(&hdr->saddr, &fl->fl6_src);
    	ipv6_addr_copy(&hdr->daddr, first_hop);
    
    
    	skb->priority = sk->sk_priority;
    
    	skb->mark = sk->sk_mark;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	mtu = dst_mtu(dst);
    
    	if ((skb->len <= mtu) || skb->local_df || skb_is_gso(skb)) {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    		IP6_UPD_PO_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    			      IPSTATS_MIB_OUT, skb->len);
    
    		return NF_HOOK(NFPROTO_IPV6, NF_INET_LOCAL_OUT, skb, NULL,
    			       dst->dev, dst_output);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if (net_ratelimit())
    		printk(KERN_DEBUG "IPv6: sending pkt_too_big to self\n");
    	skb->dev = dst->dev;
    
    	icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)), IPSTATS_MIB_FRAGFAILS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	kfree_skb(skb);
    	return -EMSGSIZE;
    }
    
    
    EXPORT_SYMBOL(ip6_xmit);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     *	To avoid extra problems ND packets are send through this
     *	routine. It's code duplication but I really want to avoid
     *	extra checks since ipv6_build_header is used by TCP (which
     *	is for us performance critical)
     */
    
    int ip6_nd_hdr(struct sock *sk, struct sk_buff *skb, struct net_device *dev,
    
    	       const struct in6_addr *saddr, const struct in6_addr *daddr,
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	       int proto, int len)
    {
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct ipv6hdr *hdr;
    
    	skb->protocol = htons(ETH_P_IPV6);
    	skb->dev = dev;
    
    
    	skb_reset_network_header(skb);
    	skb_put(skb, sizeof(struct ipv6hdr));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Al Viro's avatar
    Al Viro committed
    	*(__be32*)hdr = htonl(0x60000000);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	hdr->payload_len = htons(len);
    	hdr->nexthdr = proto;
    	hdr->hop_limit = np->hop_limit;
    
    	ipv6_addr_copy(&hdr->saddr, saddr);
    	ipv6_addr_copy(&hdr->daddr, daddr);
    
    	return 0;
    }
    
    static int ip6_call_ra_chain(struct sk_buff *skb, int sel)
    {
    	struct ip6_ra_chain *ra;
    	struct sock *last = NULL;
    
    	read_lock(&ip6_ra_lock);
    	for (ra = ip6_ra_chain; ra; ra = ra->next) {
    		struct sock *sk = ra->sk;
    
    		if (sk && ra->sel == sel &&
    		    (!sk->sk_bound_dev_if ||
    		     sk->sk_bound_dev_if == skb->dev->ifindex)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (last) {
    				struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
    				if (skb2)
    					rawv6_rcv(last, skb2);
    			}
    			last = sk;
    		}
    	}
    
    	if (last) {
    		rawv6_rcv(last, skb);
    		read_unlock(&ip6_ra_lock);
    		return 1;
    	}
    	read_unlock(&ip6_ra_lock);
    	return 0;
    }
    
    
    static int ip6_forward_proxy_check(struct sk_buff *skb)
    {
    
    	struct ipv6hdr *hdr = ipv6_hdr(skb);
    
    	u8 nexthdr = hdr->nexthdr;
    	int offset;
    
    	if (ipv6_ext_hdr(nexthdr)) {
    		offset = ipv6_skip_exthdr(skb, sizeof(*hdr), &nexthdr);
    		if (offset < 0)
    			return 0;
    	} else
    		offset = sizeof(struct ipv6hdr);
    
    	if (nexthdr == IPPROTO_ICMPV6) {
    		struct icmp6hdr *icmp6;
    
    
    		if (!pskb_may_pull(skb, (skb_network_header(skb) +
    					 offset + 1 - skb->data)))
    
    		icmp6 = (struct icmp6hdr *)(skb_network_header(skb) + offset);
    
    
    		switch (icmp6->icmp6_type) {
    		case NDISC_ROUTER_SOLICITATION:
    		case NDISC_ROUTER_ADVERTISEMENT:
    		case NDISC_NEIGHBOUR_SOLICITATION:
    		case NDISC_NEIGHBOUR_ADVERTISEMENT:
    		case NDISC_REDIRECT:
    			/* For reaction involving unicast neighbor discovery
    			 * message destined to the proxied address, pass it to
    			 * input function.
    			 */
    			return 1;
    		default:
    			break;
    		}
    	}
    
    
    	/*
    	 * The proxying router can't forward traffic sent to a link-local
    	 * address, so signal the sender and discard the packet. This
    	 * behavior is clarified by the MIPv6 specification.
    	 */
    	if (ipv6_addr_type(&hdr->daddr) & IPV6_ADDR_LINKLOCAL) {
    		dst_link_failure(skb);
    		return -1;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    static inline int ip6_forward_finish(struct sk_buff *skb)
    {
    	return dst_output(skb);
    }
    
    int ip6_forward(struct sk_buff *skb)
    {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct dst_entry *dst = skb_dst(skb);
    
    	struct ipv6hdr *hdr = ipv6_hdr(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct inet6_skb_parm *opt = IP6CB(skb);
    
    	struct net *net = dev_net(dst->dev);
    
    	if (net->ipv6.devconf_all->forwarding == 0)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto error;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!xfrm6_policy_check(NULL, XFRM_POLICY_FWD, skb)) {
    
    		IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_INDISCARDS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    	}
    
    
    	if (skb->pkt_type != PACKET_HOST)
    		goto drop;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 *	We DO NOT make any processing on
    	 *	RA packets, pushing them to user level AS IS
    	 *	without ane WARRANTY that application will be able
    	 *	to interpret them. The reason is that we
    	 *	cannot make anything clever here.
    	 *
    	 *	We are not end-node, so that if packet contains
    	 *	AH/ESP, we cannot make anything.
    	 *	Defragmentation also would be mistake, RA packets
    	 *	cannot be fragmented, because there is no warranty
    	 *	that different fragments will go along one path. --ANK
    	 */
    	if (opt->ra) {
    
    		u8 *ptr = skb_network_header(skb) + opt->ra;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (ip6_call_ra_chain(skb, (ptr[2]<<8) + ptr[3]))
    			return 0;
    	}
    
    	/*
    	 *	check and decrement ttl
    	 */
    	if (hdr->hop_limit <= 1) {
    		/* Force OUTPUT device used as source address */
    		skb->dev = dst->dev;
    
    		icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, 0);
    
    		IP6_INC_STATS_BH(net,
    				 ip6_dst_idev(dst), IPSTATS_MIB_INHDRERRORS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		kfree_skb(skb);
    		return -ETIMEDOUT;
    	}
    
    
    	/* XXX: idev->cnf.proxy_ndp? */
    
    	if (net->ipv6.devconf_all->proxy_ndp &&
    
    	    pneigh_lookup(&nd_tbl, net, &hdr->daddr, skb->dev, 0)) {
    
    		int proxied = ip6_forward_proxy_check(skb);
    		if (proxied > 0)
    
    			IP6_INC_STATS(net, ip6_dst_idev(dst),
    				      IPSTATS_MIB_INDISCARDS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!xfrm6_route_forward(skb)) {
    
    		IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_INDISCARDS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    	}
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	dst = skb_dst(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* IPv6 specs say nothing about it, but it is clear that we cannot
    	   send redirects to source routed frames.
    
    	   We don't send redirects to frames decapsulated from IPsec.
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	 */
    
    	if (skb->dev == dst->dev && dst->neighbour && opt->srcrt == 0 &&
    
    	    !skb_sec_path(skb)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		struct in6_addr *target = NULL;
    		struct rt6_info *rt;
    		struct neighbour *n = dst->neighbour;
    
    		/*
    		 *	incoming and outgoing devices are the same
    		 *	send a redirect.
    		 */
    
    		rt = (struct rt6_info *) dst;
    		if ((rt->rt6i_flags & RTF_GATEWAY))
    			target = (struct in6_addr*)&n->primary_key;
    		else
    			target = &hdr->daddr;
    
    
    		if (!rt->rt6i_peer)
    			rt6_bind_peer(rt, 1);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* Limit redirects both by destination (here)
    		   and by source (inside ndisc_send_redirect)
    		 */
    
    		if (inet_peer_xrlim_allow(rt->rt6i_peer, 1*HZ))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			ndisc_send_redirect(skb, n, target);
    
    	} else {
    		int addrtype = ipv6_addr_type(&hdr->saddr);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* This check is security critical. */
    
    		if (addrtype == IPV6_ADDR_ANY ||
    		    addrtype & (IPV6_ADDR_MULTICAST | IPV6_ADDR_LOOPBACK))
    
    			goto error;
    		if (addrtype & IPV6_ADDR_LINKLOCAL) {
    			icmpv6_send(skb, ICMPV6_DEST_UNREACH,
    
    				    ICMPV6_NOT_NEIGHBOUR, 0);
    
    	mtu = dst_mtu(dst);
    	if (mtu < IPV6_MIN_MTU)
    		mtu = IPV6_MIN_MTU;
    
    
    	if (skb->len > mtu && !skb_is_gso(skb)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* Again, force OUTPUT device used as source address */
    		skb->dev = dst->dev;
    
    		icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
    
    		IP6_INC_STATS_BH(net,
    				 ip6_dst_idev(dst), IPSTATS_MIB_INTOOBIGERRORS);
    		IP6_INC_STATS_BH(net,
    				 ip6_dst_idev(dst), IPSTATS_MIB_FRAGFAILS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		kfree_skb(skb);
    		return -EMSGSIZE;
    	}
    
    	if (skb_cow(skb, dst->dev->hard_header_len)) {
    
    		IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTDISCARDS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto drop;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Mangling hops number delayed to point after skb COW */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	hdr->hop_limit--;
    
    
    	IP6_INC_STATS_BH(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTFORWDATAGRAMS);
    
    	return NF_HOOK(NFPROTO_IPV6, NF_INET_FORWARD, skb, skb->dev, dst->dev,
    
    		       ip6_forward_finish);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    error:
    
    	IP6_INC_STATS_BH(net, ip6_dst_idev(dst), IPSTATS_MIB_INADDRERRORS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    drop:
    	kfree_skb(skb);
    	return -EINVAL;
    }
    
    static void ip6_copy_metadata(struct sk_buff *to, struct sk_buff *from)
    {
    	to->pkt_type = from->pkt_type;
    	to->priority = from->priority;
    	to->protocol = from->protocol;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	skb_dst_drop(to);
    	skb_dst_set(to, dst_clone(skb_dst(from)));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	to->dev = from->dev;
    
    	to->mark = from->mark;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    #ifdef CONFIG_NET_SCHED
    	to->tc_index = from->tc_index;
    #endif
    
    #if defined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \
        defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)
    	to->nf_trace = from->nf_trace;
    #endif
    
    	skb_copy_secmark(to, from);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    int ip6_find_1stfragopt(struct sk_buff *skb, u8 **nexthdr)
    {
    	u16 offset = sizeof(struct ipv6hdr);
    
    	struct ipv6_opt_hdr *exthdr =
    				(struct ipv6_opt_hdr *)(ipv6_hdr(skb) + 1);
    
    	unsigned int packet_len = skb->tail - skb->network_header;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int found_rhdr = 0;
    
    	*nexthdr = &ipv6_hdr(skb)->nexthdr;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	while (offset + 1 <= packet_len) {
    
    		switch (**nexthdr) {
    
    		case NEXTHDR_HOP:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case NEXTHDR_ROUTING:
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		case NEXTHDR_DEST:
    
    #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
    
    			if (ipv6_find_tlv(skb, offset, IPV6_TLV_HAO) >= 0)
    				break;
    #endif
    			if (found_rhdr)
    				return offset;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			break;
    		default :
    			return offset;
    		}
    
    
    		offset += ipv6_optlen(exthdr);
    		*nexthdr = &exthdr->nexthdr;
    
    		exthdr = (struct ipv6_opt_hdr *)(skb_network_header(skb) +
    						 offset);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	return offset;
    }
    
    
    int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct sk_buff *frag;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct rt6_info *rt = (struct rt6_info*)skb_dst(skb);
    
    	struct ipv6_pinfo *np = skb->sk ? inet6_sk(skb->sk) : NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct ipv6hdr *tmp_hdr;
    	struct frag_hdr *fh;
    	unsigned int mtu, hlen, left, len;
    
    Al Viro's avatar
    Al Viro committed
    	__be32 frag_id = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	int ptr, offset = 0, err=0;
    	u8 *prevhdr, nexthdr = 0;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	struct net *net = dev_net(skb_dst(skb)->dev);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	hlen = ip6_find_1stfragopt(skb, &prevhdr);
    	nexthdr = *prevhdr;
    
    
    	mtu = ip6_skb_dst_mtu(skb);
    
    
    	/* We must not fragment if the socket is set to force MTU discovery
    
    	 * or if the skb it not generated by a local socket.
    
    	if (!skb->local_df && skb->len > mtu) {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    		skb->dev = skb_dst(skb)->dev;
    
    		icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    		IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    			      IPSTATS_MIB_FRAGFAILS);
    
    		kfree_skb(skb);
    		return -EMSGSIZE;
    	}
    
    
    	if (np && np->frag_size < mtu) {
    		if (np->frag_size)
    			mtu = np->frag_size;
    	}
    	mtu -= hlen + sizeof(struct frag_hdr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (skb_has_frag_list(skb)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		int first_len = skb_pagelen(skb);
    
    		struct sk_buff *frag2;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (first_len - hlen > mtu ||
    		    ((first_len - hlen) & 7) ||
    		    skb_cloned(skb))
    			goto slow_path;
    
    
    		skb_walk_frags(skb, frag) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			/* Correct geometry. */
    			if (frag->len > mtu ||
    			    ((frag->len & 7) && frag->next) ||
    			    skb_headroom(frag) < hlen)
    
    				goto slow_path_clean;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    			/* Partially cloned skb? */
    			if (skb_shared(frag))
    
    				goto slow_path_clean;
    
    
    			BUG_ON(frag->sk);
    			if (skb->sk) {
    				frag->sk = skb->sk;
    				frag->destructor = sock_wfree;
    			}
    
    			skb->truesize -= frag->truesize;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    
    		err = 0;
    		offset = 0;
    		frag = skb_shinfo(skb)->frag_list;
    
    		skb_frag_list_init(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		/* BUILD HEADER */
    
    
    		*prevhdr = NEXTHDR_FRAGMENT;
    
    		tmp_hdr = kmemdup(skb_network_header(skb), hlen, GFP_ATOMIC);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (!tmp_hdr) {
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    			IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    				      IPSTATS_MIB_FRAGFAILS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return -ENOMEM;
    		}
    
    		__skb_pull(skb, hlen);
    		fh = (struct frag_hdr*)__skb_push(skb, sizeof(struct frag_hdr));
    
    		__skb_push(skb, hlen);
    		skb_reset_network_header(skb);
    
    		memcpy(skb_network_header(skb), tmp_hdr, hlen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		fh->nexthdr = nexthdr;
    		fh->reserved = 0;
    		fh->frag_off = htons(IP6_MF);
    		frag_id = fh->identification;
    
    		first_len = skb_pagelen(skb);
    		skb->data_len = first_len - skb_headlen(skb);
    		skb->len = first_len;
    
    		ipv6_hdr(skb)->payload_len = htons(first_len -
    						   sizeof(struct ipv6hdr));
    
    		dst_hold(&rt->dst);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		for (;;) {
    			/* Prepare header of the next frame,
    			 * before previous one went down. */
    			if (frag) {
    				frag->ip_summed = CHECKSUM_NONE;
    
    				skb_reset_transport_header(frag);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				fh = (struct frag_hdr*)__skb_push(frag, sizeof(struct frag_hdr));
    
    				__skb_push(frag, hlen);
    				skb_reset_network_header(frag);
    
    				memcpy(skb_network_header(frag), tmp_hdr,
    				       hlen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				offset += skb->len - hlen - sizeof(struct frag_hdr);
    				fh->nexthdr = nexthdr;
    				fh->reserved = 0;
    				fh->frag_off = htons(offset);
    				if (frag->next != NULL)
    					fh->frag_off |= htons(IP6_MF);
    				fh->identification = frag_id;
    
    				ipv6_hdr(frag)->payload_len =
    						htons(frag->len -
    						      sizeof(struct ipv6hdr));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				ip6_copy_metadata(frag, skb);
    			}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			err = output(skb);
    
    				IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
    
    					      IPSTATS_MIB_FRAGCREATES);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			if (err || !frag)
    				break;
    
    			skb = frag;
    			frag = skb->next;
    			skb->next = NULL;
    		}
    
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    		kfree(tmp_hdr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		if (err == 0) {
    
    			IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
    
    				      IPSTATS_MIB_FRAGOKS);
    
    			dst_release(&rt->dst);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			return 0;
    		}
    
    		while (frag) {
    			skb = frag->next;
    			kfree_skb(frag);
    			frag = skb;
    		}
    
    
    		IP6_INC_STATS(net, ip6_dst_idev(&rt->dst),
    
    			      IPSTATS_MIB_FRAGFAILS);
    
    		dst_release(&rt->dst);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return err;
    
    
    slow_path_clean:
    		skb_walk_frags(skb, frag2) {
    			if (frag2 == frag)
    				break;
    			frag2->sk = NULL;
    			frag2->destructor = NULL;
    			skb->truesize += frag2->truesize;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    slow_path:
    	left = skb->len - hlen;		/* Space per frame */
    	ptr = hlen;			/* Where to start from */
    
    	/*
    	 *	Fragment the datagram.
    	 */
    
    	*prevhdr = NEXTHDR_FRAGMENT;
    
    	/*
    	 *	Keep copying data until we run out.
    	 */
    	while(left > 0)	{
    		len = left;
    		/* IF: it doesn't fit, use 'mtu' - the data space left */
    		if (len > mtu)
    			len = mtu;
    		/* IF: we are not sending upto and including the packet end
    		   then align the next start on an eight byte boundary */
    		if (len < left)	{
    			len &= ~7;
    		}
    		/*
    		 *	Allocate buffer.
    		 */
    
    
    		if ((frag = alloc_skb(len+hlen+sizeof(struct frag_hdr)+LL_ALLOCATED_SPACE(rt->dst.dev), GFP_ATOMIC)) == NULL) {
    
    			NETDEBUG(KERN_INFO "IPv6: frag: no memory for new fragment!\n");
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    			IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    				      IPSTATS_MIB_FRAGFAILS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			err = -ENOMEM;
    			goto fail;
    		}
    
    		/*
    		 *	Set up data on packet
    		 */
    
    		ip6_copy_metadata(frag, skb);
    
    		skb_reserve(frag, LL_RESERVED_SPACE(rt->dst.dev));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		skb_put(frag, len + hlen + sizeof(struct frag_hdr));
    
    		skb_reset_network_header(frag);
    
    		fh = (struct frag_hdr *)(skb_network_header(frag) + hlen);
    
    		frag->transport_header = (frag->network_header + hlen +
    					  sizeof(struct frag_hdr));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		/*
    		 *	Charge the memory for the fragment to any owner
    		 *	it might possess
    		 */
    		if (skb->sk)
    			skb_set_owner_w(frag, skb->sk);
    
    		/*
    		 *	Copy the packet header into the new buffer.
    		 */
    
    		skb_copy_from_linear_data(skb, skb_network_header(frag), hlen);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		/*
    		 *	Build fragment header.
    		 */
    		fh->nexthdr = nexthdr;
    		fh->reserved = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			frag_id = fh->identification;
    		} else
    			fh->identification = frag_id;
    
    		/*
    		 *	Copy a block of the IP datagram.
    		 */
    
    		if (skb_copy_bits(skb, ptr, skb_transport_header(frag), len))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			BUG();
    		left -= len;
    
    		fh->frag_off = htons(offset);
    		if (left > 0)
    			fh->frag_off |= htons(IP6_MF);
    
    		ipv6_hdr(frag)->payload_len = htons(frag->len -
    						    sizeof(struct ipv6hdr));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    		ptr += len;
    		offset += len;
    
    		/*
    		 *	Put this fragment into the sending queue.
    		 */
    		err = output(frag);
    		if (err)
    			goto fail;
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    		IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    			      IPSTATS_MIB_FRAGCREATES);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    		      IPSTATS_MIB_FRAGOKS);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	kfree_skb(skb);
    	return err;
    
    fail:
    
    Eric Dumazet's avatar
    Eric Dumazet committed
    	IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
    
    		      IPSTATS_MIB_FRAGFAILS);
    
    	kfree_skb(skb);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return err;
    }
    
    
    static inline int ip6_rt_check(struct rt6key *rt_key,
    			       struct in6_addr *fl_addr,
    			       struct in6_addr *addr_cache)
    {
    
    	return (rt_key->plen != 128 || !ipv6_addr_equal(fl_addr, &rt_key->addr)) &&
    		(addr_cache == NULL || !ipv6_addr_equal(fl_addr, addr_cache));
    
    static struct dst_entry *ip6_sk_dst_check(struct sock *sk,
    					  struct dst_entry *dst,
    					  struct flowi *fl)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    
    	struct ipv6_pinfo *np = inet6_sk(sk);
    	struct rt6_info *rt = (struct rt6_info *)dst;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if (!dst)
    		goto out;
    
    	/* Yes, checking route validity in not connected
    	 * case is not very simple. Take into account,
    	 * that we do not support routing by source, TOS,
    	 * and MSG_DONTROUTE 		--ANK (980726)
    	 *
    
    	 * 1. ip6_rt_check(): If route was host route,
    	 *    check that cached destination is current.
    
    	 *    If it is network route, we still may
    	 *    check its validity using saved pointer
    	 *    to the last used address: daddr_cache.
    	 *    We do not want to save whole address now,
    	 *    (because main consumer of this service
    	 *    is tcp, which has not this problem),
    	 *    so that the last trick works only on connected
    	 *    sockets.
    	 * 2. oif also should be the same.
    	 */
    
    	if (ip6_rt_check(&rt->rt6i_dst, &fl->fl6_dst, np->daddr_cache) ||
    
    #ifdef CONFIG_IPV6_SUBTREES
    	    ip6_rt_check(&rt->rt6i_src, &fl->fl6_src, np->saddr_cache) ||
    #endif
    
    	    (fl->flowi_oif && fl->flowi_oif != dst->dev->ifindex)) {
    
    		dst_release(dst);
    		dst = NULL;
    
    out:
    	return dst;
    }
    
    static int ip6_dst_lookup_tail(struct sock *sk,
    			       struct dst_entry **dst, struct flowi *fl)
    {
    	int err;
    
    	struct net *net = sock_net(sk);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (*dst == NULL)
    
    		*dst = ip6_route_output(net, sk, fl);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if ((err = (*dst)->error))
    		goto out_err_release;
    
    	if (ipv6_addr_any(&fl->fl6_src)) {
    
    		err = ipv6_dev_get_saddr(net, ip6_dst_idev(*dst)->dev,
    
    					 &fl->fl6_dst,
    					 sk ? inet6_sk(sk)->srcprefs : 0,
    					 &fl->fl6_src);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_err_release;
    	}
    
    
    	/*
    	 * Here if the dst entry we've looked up
    	 * has a neighbour entry that is in the INCOMPLETE
    	 * state and the src address from the flow is
    	 * marked as OPTIMISTIC, we release the found
    	 * dst entry and replace it instead with the
    	 * dst entry of the nexthop router
    	 */
    	if ((*dst)->neighbour && !((*dst)->neighbour->nud_state & NUD_VALID)) {
    		struct inet6_ifaddr *ifp;
    		struct flowi fl_gw;
    		int redirect;
    
    		ifp = ipv6_get_ifaddr(net, &fl->fl6_src,
    				      (*dst)->dev, 1);
    
    		redirect = (ifp && ifp->flags & IFA_F_OPTIMISTIC);
    		if (ifp)
    			in6_ifa_put(ifp);
    
    		if (redirect) {
    			/*
    			 * We need to get the dst entry for the
    			 * default router instead
    			 */
    			dst_release(*dst);
    			memcpy(&fl_gw, fl, sizeof(struct flowi));
    			memset(&fl_gw.fl6_dst, 0, sizeof(struct in6_addr));
    			*dst = ip6_route_output(net, sk, &fl_gw);
    			if ((err = (*dst)->error))
    				goto out_err_release;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    
    out_err_release:
    
    		IP6_INC_STATS_BH(net, NULL, IPSTATS_MIB_OUTNOROUTES);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dst_release(*dst);
    	*dst = NULL;
    	return err;
    }
    
    /**
     *	ip6_dst_lookup - perform route lookup on flow
     *	@sk: socket which provides route info
     *	@dst: pointer to dst_entry * for result
     *	@fl: flow to lookup
     *
     *	This function performs a route lookup on the given flow.
     *
     *	It returns zero on success, or a standard errno code on error.
     */
    int ip6_dst_lookup(struct sock *sk, struct dst_entry **dst, struct flowi *fl)
    {
    	*dst = NULL;
    	return ip6_dst_lookup_tail(sk, dst, fl);