Skip to content
Snippets Groups Projects
ip6_output.c 40.3 KiB
Newer Older
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

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;
	struct neighbour *neigh;
Linus Torvalds's avatar
Linus Torvalds committed

	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

			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 (IPV6_ADDR_MC_SCOPE(&ipv6_hdr(skb)->daddr) <=
		    IPV6_ADDR_SCOPE_NODELOCAL &&
		    !(dev->flags & IFF_LOOPBACK)) {
			kfree_skb(skb);
			return 0;
		}
	nexthop = rt6_nexthop((struct rt6_info *)dst);
	neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
	if (unlikely(!neigh))
		neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
	if (!IS_ERR(neigh)) {
		ret = dst_neigh_output(dst, neigh, skb);
		rcu_read_unlock_bh();
		return ret;
	}
	rcu_read_unlock_bh();
	IP6_INC_STATS(dev_net(dst->dev),
		      ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES);
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)) ||
	    (IP6CB(skb)->frag_max_size && skb->len > IP6CB(skb)->frag_max_size))
		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 flowi6 *fl6,
	     struct ipv6_txoptions *opt, int tclass)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct net *net = sock_net(sk);
	struct ipv6_pinfo *np = inet6_sk(sk);
	struct in6_addr *first_hop = &fl6->daddr;
Eric Dumazet's avatar
Eric Dumazet committed
	struct dst_entry *dst = skb_dst(skb);
Linus Torvalds's avatar
Linus Torvalds committed
	struct ipv6hdr *hdr;
	u8  proto = fl6->flowi6_proto;
Linus Torvalds's avatar
Linus Torvalds committed
	int seg_len = skb->len;
	int hlimit = -1;
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;
			}
			consume_skb(skb);
			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

	/*
Loading
Loading full blame...