2050 lines · 52.9 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-or-later/* *	Neighbour Discovery for IPv6 *	Linux INET6 implementation * *	Authors: *	Pedro Roque		<roque@di.fc.ul.pt> *	Mike Shaver		<shaver@ingenia.com> */ /* *	Changes: * *	Alexey I. Froloff		:	RFC6106 (DNSSL) support *	Pierre Ynard			:	export userland ND options *						through netlink (RDNSS support) *	Lars Fenneberg			:	fixed MTU setting on receipt *						of an RA. *	Janos Farkas			:	kmalloc failure checks *	Alexey Kuznetsov		:	state machine reworked *						and moved to net/core. *	Pekka Savola			:	RFC2461 validation *	YOSHIFUJI Hideaki @USAGI	:	Verify ND options properly */ #define pr_fmt(fmt) "ICMPv6: " fmt #include <linux/module.h>#include <linux/errno.h>#include <linux/types.h>#include <linux/socket.h>#include <linux/sockios.h>#include <linux/sched.h>#include <linux/net.h>#include <linux/in6.h>#include <linux/route.h>#include <linux/init.h>#include <linux/rcupdate.h>#include <linux/slab.h>#ifdef CONFIG_SYSCTL#include <linux/sysctl.h>#endif #include <linux/if_addr.h>#include <linux/if_ether.h>#include <linux/if_arp.h>#include <linux/ipv6.h>#include <linux/icmpv6.h>#include <linux/jhash.h> #include <net/sock.h>#include <net/snmp.h> #include <net/ipv6.h>#include <net/protocol.h>#include <net/ndisc.h>#include <net/ip6_route.h>#include <net/addrconf.h>#include <net/icmp.h> #include <net/netlink.h>#include <linux/rtnetlink.h> #include <net/flow.h>#include <net/ip6_checksum.h>#include <net/inet_common.h>#include <linux/proc_fs.h> #include <linux/netfilter.h>#include <linux/netfilter_ipv6.h> static u32 ndisc_hash(const void *pkey,		      const struct net_device *dev,		      __u32 *hash_rnd);static bool ndisc_key_eq(const struct neighbour *neigh, const void *pkey);static bool ndisc_allow_add(const struct net_device *dev,			    struct netlink_ext_ack *extack);static int ndisc_constructor(struct neighbour *neigh);static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb);static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb);static int pndisc_constructor(struct pneigh_entry *n);static void pndisc_destructor(struct pneigh_entry *n);static void pndisc_redo(struct sk_buff *skb);static int ndisc_is_multicast(const void *pkey); static const struct neigh_ops ndisc_generic_ops = {	.family =		AF_INET6,	.solicit =		ndisc_solicit,	.error_report =		ndisc_error_report,	.output =		neigh_resolve_output,	.connected_output =	neigh_connected_output,}; static const struct neigh_ops ndisc_hh_ops = {	.family =		AF_INET6,	.solicit =		ndisc_solicit,	.error_report =		ndisc_error_report,	.output =		neigh_resolve_output,	.connected_output =	neigh_resolve_output,};  static const struct neigh_ops ndisc_direct_ops = {	.family =		AF_INET6,	.output =		neigh_direct_output,	.connected_output =	neigh_direct_output,}; struct neigh_table nd_tbl = {	.family =	AF_INET6,	.key_len =	sizeof(struct in6_addr),	.protocol =	cpu_to_be16(ETH_P_IPV6),	.hash =		ndisc_hash,	.key_eq =	ndisc_key_eq,	.constructor =	ndisc_constructor,	.pconstructor =	pndisc_constructor,	.pdestructor =	pndisc_destructor,	.proxy_redo =	pndisc_redo,	.is_multicast =	ndisc_is_multicast,	.allow_add  =   ndisc_allow_add,	.id =		"ndisc_cache",	.parms = {		.tbl			= &nd_tbl,		.reachable_time		= ND_REACHABLE_TIME,		.data = {			[NEIGH_VAR_MCAST_PROBES] = 3,			[NEIGH_VAR_UCAST_PROBES] = 3,			[NEIGH_VAR_RETRANS_TIME] = ND_RETRANS_TIMER,			[NEIGH_VAR_BASE_REACHABLE_TIME] = ND_REACHABLE_TIME,			[NEIGH_VAR_DELAY_PROBE_TIME] = 5 * HZ,			[NEIGH_VAR_INTERVAL_PROBE_TIME_MS] = 5 * HZ,			[NEIGH_VAR_GC_STALETIME] = 60 * HZ,			[NEIGH_VAR_QUEUE_LEN_BYTES] = SK_WMEM_DEFAULT,			[NEIGH_VAR_PROXY_QLEN] = 64,			[NEIGH_VAR_ANYCAST_DELAY] = 1 * HZ,			[NEIGH_VAR_PROXY_DELAY] = (8 * HZ) / 10,		},	},	.gc_interval =	  30 * HZ,	.gc_thresh1 =	 128,	.gc_thresh2 =	 512,	.gc_thresh3 =	1024,};EXPORT_SYMBOL_GPL(nd_tbl); void __ndisc_fill_addr_option(struct sk_buff *skb, int type, const void *data,			      int data_len, int pad){	int space = __ndisc_opt_addr_space(data_len, pad);	u8 *opt = skb_put(skb, space); 	opt[0] = type;	opt[1] = space>>3; 	memset(opt + 2, 0, pad);	opt   += pad;	space -= pad; 	memcpy(opt+2, data, data_len);	data_len += 2;	opt += data_len;	space -= data_len;	if (space > 0)		memset(opt, 0, space);}EXPORT_SYMBOL_GPL(__ndisc_fill_addr_option); static inline void ndisc_fill_addr_option(struct sk_buff *skb, int type,					  const void *data, u8 icmp6_type){	__ndisc_fill_addr_option(skb, type, data, skb->dev->addr_len,				 ndisc_addr_option_pad(skb->dev->type));	ndisc_ops_fill_addr_option(skb->dev, skb, icmp6_type);} static inline void ndisc_fill_redirect_addr_option(struct sk_buff *skb,						   void *ha,						   const u8 *ops_data){	ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR, ha, NDISC_REDIRECT);	ndisc_ops_fill_redirect_addr_option(skb->dev, skb, ops_data);} static struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,					    struct nd_opt_hdr *end){	int type;	if (!cur || !end || cur >= end)		return NULL;	type = cur->nd_opt_type;	do {		cur = ((void *)cur) + (cur->nd_opt_len << 3);	} while (cur < end && cur->nd_opt_type != type);	return cur <= end && cur->nd_opt_type == type ? cur : NULL;} static inline int ndisc_is_useropt(const struct net_device *dev,				   struct nd_opt_hdr *opt){	return opt->nd_opt_type == ND_OPT_PREFIX_INFO ||		opt->nd_opt_type == ND_OPT_RDNSS ||		opt->nd_opt_type == ND_OPT_DNSSL ||		opt->nd_opt_type == ND_OPT_6CO ||		opt->nd_opt_type == ND_OPT_CAPTIVE_PORTAL ||		opt->nd_opt_type == ND_OPT_PREF64;} static struct nd_opt_hdr *ndisc_next_useropt(const struct net_device *dev,					     struct nd_opt_hdr *cur,					     struct nd_opt_hdr *end){	if (!cur || !end || cur >= end)		return NULL;	do {		cur = ((void *)cur) + (cur->nd_opt_len << 3);	} while (cur < end && !ndisc_is_useropt(dev, cur));	return cur <= end && ndisc_is_useropt(dev, cur) ? cur : NULL;} struct ndisc_options *ndisc_parse_options(const struct net_device *dev,					  u8 *opt, int opt_len,					  struct ndisc_options *ndopts){	struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt; 	if (!nd_opt || opt_len < 0 || !ndopts)		return NULL;	memset(ndopts, 0, sizeof(*ndopts));	while (opt_len) {		bool unknown = false;		int l;		if (opt_len < sizeof(struct nd_opt_hdr))			return NULL;		l = nd_opt->nd_opt_len << 3;		if (opt_len < l || l == 0)			return NULL;		if (ndisc_ops_parse_options(dev, nd_opt, ndopts))			goto next_opt;		switch (nd_opt->nd_opt_type) {		case ND_OPT_SOURCE_LL_ADDR:		case ND_OPT_TARGET_LL_ADDR:		case ND_OPT_MTU:		case ND_OPT_NONCE:		case ND_OPT_REDIRECT_HDR:			if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {				net_dbg_ratelimited("%s: duplicated ND6 option found: type=%d\n",						    __func__, nd_opt->nd_opt_type);			} else {				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;			}			break;		case ND_OPT_PREFIX_INFO:			ndopts->nd_opts_pi_end = nd_opt;			if (!ndopts->nd_opt_array[nd_opt->nd_opt_type])				ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;			break;#ifdef CONFIG_IPV6_ROUTE_INFO		case ND_OPT_ROUTE_INFO:			ndopts->nd_opts_ri_end = nd_opt;			if (!ndopts->nd_opts_ri)				ndopts->nd_opts_ri = nd_opt;			break;#endif		default:			unknown = true;		}		if (ndisc_is_useropt(dev, nd_opt)) {			ndopts->nd_useropts_end = nd_opt;			if (!ndopts->nd_useropts)				ndopts->nd_useropts = nd_opt;		} else if (unknown) {			/*			 * Unknown options must be silently ignored,			 * to accommodate future extension to the			 * protocol.			 */			net_dbg_ratelimited("%s: ignored unsupported option; type=%d, len=%d\n",					    __func__, nd_opt->nd_opt_type, nd_opt->nd_opt_len);		}next_opt:		opt_len -= l;		nd_opt = ((void *)nd_opt) + l;	}	return ndopts;}EXPORT_SYMBOL_GPL(ndisc_parse_options); int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir){	switch (dev->type) {	case ARPHRD_ETHER:	case ARPHRD_IEEE802:	/* Not sure. Check it later. --ANK */	case ARPHRD_FDDI:		ipv6_eth_mc_map(addr, buf);		return 0;	case ARPHRD_ARCNET:		ipv6_arcnet_mc_map(addr, buf);		return 0;	case ARPHRD_INFINIBAND:		ipv6_ib_mc_map(addr, dev->broadcast, buf);		return 0;	case ARPHRD_IPGRE:		return ipv6_ipgre_mc_map(addr, dev->broadcast, buf);	default:		if (dir) {			memcpy(buf, dev->broadcast, dev->addr_len);			return 0;		}	}	return -EINVAL;}EXPORT_SYMBOL(ndisc_mc_map); static u32 ndisc_hash(const void *pkey,		      const struct net_device *dev,		      __u32 *hash_rnd){	return ndisc_hashfn(pkey, dev, hash_rnd);} static bool ndisc_key_eq(const struct neighbour *n, const void *pkey){	return neigh_key_eq128(n, pkey);} static int ndisc_constructor(struct neighbour *neigh){	struct in6_addr *addr = (struct in6_addr *)&neigh->primary_key;	struct net_device *dev = neigh->dev;	struct inet6_dev *in6_dev;	struct neigh_parms *parms;	bool is_multicast = ipv6_addr_is_multicast(addr); 	in6_dev = in6_dev_get(dev);	if (!in6_dev) {		return -EINVAL;	} 	parms = in6_dev->nd_parms;	__neigh_parms_put(neigh->parms);	neigh->parms = neigh_parms_clone(parms); 	neigh->type = is_multicast ? RTN_MULTICAST : RTN_UNICAST;	if (!dev->header_ops) {		neigh->nud_state = NUD_NOARP;		neigh->ops = &ndisc_direct_ops;		neigh->output = neigh_direct_output;	} else {		if (is_multicast) {			neigh->nud_state = NUD_NOARP;			ndisc_mc_map(addr, neigh->ha, dev, 1);		} else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {			neigh->nud_state = NUD_NOARP;			memcpy(neigh->ha, dev->dev_addr, dev->addr_len);			if (dev->flags&IFF_LOOPBACK)				neigh->type = RTN_LOCAL;		} else if (dev->flags&IFF_POINTOPOINT) {			neigh->nud_state = NUD_NOARP;			memcpy(neigh->ha, dev->broadcast, dev->addr_len);		}		if (dev->header_ops->cache)			neigh->ops = &ndisc_hh_ops;		else			neigh->ops = &ndisc_generic_ops;		if (neigh->nud_state&NUD_VALID)			neigh->output = neigh->ops->connected_output;		else			neigh->output = neigh->ops->output;	}	in6_dev_put(in6_dev);	return 0;} static int pndisc_constructor(struct pneigh_entry *n){	struct in6_addr *addr = (struct in6_addr *)&n->key;	struct net_device *dev = n->dev;	struct in6_addr maddr; 	if (!dev)		return -EINVAL; 	addrconf_addr_solict_mult(addr, &maddr);	return ipv6_dev_mc_inc(dev, &maddr);} static void pndisc_destructor(struct pneigh_entry *n){	struct in6_addr *addr = (struct in6_addr *)&n->key;	struct net_device *dev = n->dev;	struct in6_addr maddr; 	if (!dev)		return; 	addrconf_addr_solict_mult(addr, &maddr);	ipv6_dev_mc_dec(dev, &maddr);} /* called with rtnl held */static bool ndisc_allow_add(const struct net_device *dev,			    struct netlink_ext_ack *extack){	struct inet6_dev *idev = __in6_dev_get(dev); 	if (!idev || idev->cnf.disable_ipv6) {		NL_SET_ERR_MSG(extack, "IPv6 is disabled on this device");		return false;	} 	return true;} static struct sk_buff *ndisc_alloc_skb(struct net_device *dev,				       int len){	int hlen = LL_RESERVED_SPACE(dev);	int tlen = dev->needed_tailroom;	struct sk_buff *skb; 	skb = alloc_skb(hlen + sizeof(struct ipv6hdr) + len + tlen, GFP_ATOMIC);	if (!skb)		return NULL; 	skb->protocol = htons(ETH_P_IPV6);	skb->dev = dev; 	skb_reserve(skb, hlen + sizeof(struct ipv6hdr));	skb_reset_transport_header(skb); 	/* Manually assign socket ownership as we avoid calling	 * sock_alloc_send_pskb() to bypass wmem buffer limits	 */	rcu_read_lock();	skb_set_owner_w(skb, dev_net_rcu(dev)->ipv6.ndisc_sk);	rcu_read_unlock(); 	return skb;} static void ip6_nd_hdr(struct sk_buff *skb,		       const struct in6_addr *saddr,		       const struct in6_addr *daddr,		       int hop_limit, int len){	struct ipv6hdr *hdr;	struct inet6_dev *idev;	unsigned tclass; 	rcu_read_lock();	idev = __in6_dev_get(skb->dev);	tclass = idev ? READ_ONCE(idev->cnf.ndisc_tclass) : 0;	rcu_read_unlock(); 	skb_push(skb, sizeof(*hdr));	skb_reset_network_header(skb);	hdr = ipv6_hdr(skb); 	ip6_flow_hdr(hdr, tclass, 0); 	hdr->payload_len = htons(len);	hdr->nexthdr = IPPROTO_ICMPV6;	hdr->hop_limit = hop_limit; 	hdr->saddr = *saddr;	hdr->daddr = *daddr;} void ndisc_send_skb(struct sk_buff *skb, const struct in6_addr *daddr,		    const struct in6_addr *saddr){	struct icmp6hdr *icmp6h = icmp6_hdr(skb);	struct dst_entry *dst = skb_dst(skb);	struct net_device *dev;	struct inet6_dev *idev;	struct net *net;	struct sock *sk;	int err;	u8 type; 	type = icmp6h->icmp6_type; 	rcu_read_lock(); 	net = dev_net_rcu(skb->dev);	sk = net->ipv6.ndisc_sk;	if (!dst) {		struct flowi6 fl6;		int oif = skb->dev->ifindex; 		icmpv6_flow_init(sk, &fl6, type, saddr, daddr, oif);		dst = icmp6_dst_alloc(skb->dev, &fl6);		if (IS_ERR(dst)) {			rcu_read_unlock();			kfree_skb(skb);			return;		} 		skb_dst_set(skb, dst);	}

Lines 1–500 of 2050.