📄 ndisc.c
字号:
/* * Neighbour Discovery for IPv6 * Linux INET6 implementation * * Authors: * Pedro Roque <roque@di.fc.ul.pt> * Mike Shaver <shaver@ingenia.com> * * 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: * * 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 *//* Set to 3 to get tracing... */#define ND_DEBUG 1#define ND_PRINTK(fmt, args...) do { if (net_ratelimit()) { printk(fmt, ## args); } } while(0)#define ND_NOPRINTK(x...) do { ; } while(0)#define ND_PRINTK0 ND_PRINTK#define ND_PRINTK1 ND_NOPRINTK#define ND_PRINTK2 ND_NOPRINTK#define ND_PRINTK3 ND_NOPRINTK#if ND_DEBUG >= 1#undef ND_PRINTK1#define ND_PRINTK1 ND_PRINTK#endif#if ND_DEBUG >= 2#undef ND_PRINTK2#define ND_PRINTK2 ND_PRINTK#endif#if ND_DEBUG >= 3#undef ND_PRINTK3#define ND_PRINTK3 ND_PRINTK#endif#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>#ifdef CONFIG_SYSCTL#include <linux/sysctl.h>#endif#include <linux/if_addr.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 <linux/proc_fs.h>#include <linux/netfilter.h>#include <linux/netfilter_ipv6.h>static struct socket *ndisc_socket;static u32 ndisc_hash(const void *pkey, const struct net_device *dev);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 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, .hh_output = dev_queue_xmit, .queue_xmit = dev_queue_xmit,};static 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, .hh_output = dev_queue_xmit, .queue_xmit = dev_queue_xmit,};static struct neigh_ops ndisc_direct_ops = { .family = AF_INET6, .output = dev_queue_xmit, .connected_output = dev_queue_xmit, .hh_output = dev_queue_xmit, .queue_xmit = dev_queue_xmit,};struct neigh_table nd_tbl = { .family = AF_INET6, .entry_size = sizeof(struct neighbour) + sizeof(struct in6_addr), .key_len = sizeof(struct in6_addr), .hash = ndisc_hash, .constructor = ndisc_constructor, .pconstructor = pndisc_constructor, .pdestructor = pndisc_destructor, .proxy_redo = pndisc_redo, .id = "ndisc_cache", .parms = { .tbl = &nd_tbl, .base_reachable_time = 30 * HZ, .retrans_time = 1 * HZ, .gc_staletime = 60 * HZ, .reachable_time = 30 * HZ, .delay_probe_time = 5 * HZ, .queue_len = 3, .ucast_probes = 3, .mcast_probes = 3, .anycast_delay = 1 * HZ, .proxy_delay = (8 * HZ) / 10, .proxy_qlen = 64, }, .gc_interval = 30 * HZ, .gc_thresh1 = 128, .gc_thresh2 = 512, .gc_thresh3 = 1024,};/* ND options */struct ndisc_options { struct nd_opt_hdr *nd_opt_array[__ND_OPT_ARRAY_MAX];#ifdef CONFIG_IPV6_ROUTE_INFO struct nd_opt_hdr *nd_opts_ri; struct nd_opt_hdr *nd_opts_ri_end;#endif struct nd_opt_hdr *nd_useropts; struct nd_opt_hdr *nd_useropts_end;};#define nd_opts_src_lladdr nd_opt_array[ND_OPT_SOURCE_LL_ADDR]#define nd_opts_tgt_lladdr nd_opt_array[ND_OPT_TARGET_LL_ADDR]#define nd_opts_pi nd_opt_array[ND_OPT_PREFIX_INFO]#define nd_opts_pi_end nd_opt_array[__ND_OPT_PREFIX_INFO_END]#define nd_opts_rh nd_opt_array[ND_OPT_REDIRECT_HDR]#define nd_opts_mtu nd_opt_array[ND_OPT_MTU]#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)/* * Return the padding between the option length and the start of the * link addr. Currently only IP-over-InfiniBand needs this, although * if RFC 3831 IPv6-over-Fibre Channel is ever implemented it may * also need a pad of 2. */static int ndisc_addr_option_pad(unsigned short type){ switch (type) { case ARPHRD_INFINIBAND: return 2; default: return 0; }}static inline int ndisc_opt_addr_space(struct net_device *dev){ return NDISC_OPT_SPACE(dev->addr_len + ndisc_addr_option_pad(dev->type));}static u8 *ndisc_fill_addr_option(u8 *opt, int type, void *data, int data_len, unsigned short addr_type){ int space = NDISC_OPT_SPACE(data_len); int pad = ndisc_addr_option_pad(addr_type); 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; if ((space -= data_len) > 0) memset(opt, 0, space); return opt + space;}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(struct nd_opt_hdr *opt){ return (opt->nd_opt_type == ND_OPT_RDNSS);}static struct nd_opt_hdr *ndisc_next_useropt(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(cur)); return (cur <= end && ndisc_is_useropt(cur) ? cur : NULL);}static struct ndisc_options *ndisc_parse_options(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) { 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; 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_REDIRECT_HDR: if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { ND_PRINTK2(KERN_WARNING "%s(): duplicated ND6 option found: type=%d\n", __FUNCTION__, 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: if (ndisc_is_useropt(nd_opt)) { ndopts->nd_useropts_end = nd_opt; if (!ndopts->nd_useropts) ndopts->nd_useropts = nd_opt; } else { /* * Unknown options must be silently ignored, * to accommodate future extension to the * protocol. */ ND_PRINTK2(KERN_NOTICE "%s(): ignored unsupported option; type=%d, len=%d\n", __FUNCTION__, nd_opt->nd_opt_type, nd_opt->nd_opt_len); } } opt_len -= l; nd_opt = ((void *)nd_opt) + l; } return ndopts;}static inline u8 *ndisc_opt_addr_data(struct nd_opt_hdr *p, struct net_device *dev){ u8 *lladdr = (u8 *)(p + 1); int lladdrlen = p->nd_opt_len << 3; int prepad = ndisc_addr_option_pad(dev->type); if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len + prepad)) return NULL; return (lladdr + prepad);}int ndisc_mc_map(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_IEEE802_TR: ipv6_tr_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, buf); return 0; 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){ const u32 *p32 = pkey; u32 addr_hash, i; addr_hash = 0; for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++) addr_hash ^= *p32++; return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);}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; int is_multicast = ipv6_addr_is_multicast(addr); rcu_read_lock(); in6_dev = in6_dev_get(dev); if (in6_dev == NULL) { rcu_read_unlock(); return -EINVAL; } parms = in6_dev->nd_parms; __neigh_parms_put(neigh->parms); neigh->parms = neigh_parms_clone(parms); rcu_read_unlock(); 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->ops->queue_xmit; } 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 in6_addr maddr; struct net_device *dev = n->dev; if (dev == NULL || __in6_dev_get(dev) == NULL) return -EINVAL; addrconf_addr_solict_mult(addr, &maddr); ipv6_dev_mc_inc(dev, &maddr); return 0;}static void pndisc_destructor(struct pneigh_entry *n){ struct in6_addr *addr = (struct in6_addr*)&n->key; struct in6_addr maddr; struct net_device *dev = n->dev; if (dev == NULL || __in6_dev_get(dev) == NULL) return; addrconf_addr_solict_mult(addr, &maddr); ipv6_dev_mc_dec(dev, &maddr);}/* * Send a Neighbour Advertisement */static inline void ndisc_flow_init(struct flowi *fl, u8 type, struct in6_addr *saddr, struct in6_addr *daddr, int oif){ memset(fl, 0, sizeof(*fl)); ipv6_addr_copy(&fl->fl6_src, saddr); ipv6_addr_copy(&fl->fl6_dst, daddr); fl->proto = IPPROTO_ICMPV6; fl->fl_icmp_type = type; fl->fl_icmp_code = 0; fl->oif = oif; security_sk_classify_flow(ndisc_socket->sk, fl);}static void __ndisc_send(struct net_device *dev, struct neighbour *neigh, struct in6_addr *daddr, struct in6_addr *saddr, struct icmp6hdr *icmp6h, struct in6_addr *target, int llinfo){ struct flowi fl; struct dst_entry *dst; struct sock *sk = ndisc_socket->sk; struct sk_buff *skb; struct icmp6hdr *hdr; struct inet6_dev *idev; int len; int err; u8 *opt, type; type = icmp6h->icmp6_type; ndisc_flow_init(&fl, type, saddr, daddr, dev->ifindex); dst = ndisc_dst_alloc(dev, neigh, daddr, ip6_output); if (!dst) return; err = xfrm_lookup(&dst, &fl, NULL, 0); if (err < 0) return; if (!dev->addr_len) llinfo = 0; len = sizeof(struct icmp6hdr) + (target ? sizeof(*target) : 0); if (llinfo) len += ndisc_opt_addr_space(dev); skb = sock_alloc_send_skb(sk, (MAX_HEADER + sizeof(struct ipv6hdr) + len + LL_RESERVED_SPACE(dev)), 1, &err); if (!skb) { ND_PRINTK0(KERN_ERR "ICMPv6 ND: %s() failed to allocate an skb.\n", __FUNCTION__); dst_release(dst); return; } skb_reserve(skb, LL_RESERVED_SPACE(dev)); ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len); skb->transport_header = skb->tail; skb_put(skb, len); hdr = (struct icmp6hdr *)skb_transport_header(skb); memcpy(hdr, icmp6h, sizeof(*hdr)); opt = skb_transport_header(skb) + sizeof(struct icmp6hdr); if (target) { ipv6_addr_copy((struct in6_addr *)opt, target); opt += sizeof(*target); } if (llinfo) ndisc_fill_addr_option(opt, llinfo, dev->dev_addr, dev->addr_len, dev->type); hdr->icmp6_cksum = csum_ipv6_magic(saddr, daddr, len, IPPROTO_ICMPV6, csum_partial((__u8 *) hdr, len, 0)); skb->dst = dst; idev = in6_dev_get(dst->dev); IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output); if (!err) { ICMP6MSGOUT_INC_STATS(idev, type); ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS); } if (likely(idev != NULL)) in6_dev_put(idev);}static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, struct in6_addr *daddr, struct in6_addr *solicited_addr, int router, int solicited, int override, int inc_opt){ struct in6_addr tmpaddr; struct inet6_ifaddr *ifp; struct in6_addr *src_addr; struct icmp6hdr icmp6h = { .icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT, }; /* for anycast or proxy, solicited_addr != src_addr */ ifp = ipv6_get_ifaddr(solicited_addr, dev, 1); if (ifp) { src_addr = solicited_addr; if (ifp->flags & IFA_F_OPTIMISTIC) override = 0; in6_ifa_put(ifp); } else { if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr)) return; src_addr = &tmpaddr; } icmp6h.icmp6_router = router; icmp6h.icmp6_solicited = solicited; icmp6h.icmp6_override = override; __ndisc_send(dev, neigh, daddr, src_addr, &icmp6h, solicited_addr, inc_opt ? ND_OPT_TARGET_LL_ADDR : 0);}void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, struct in6_addr *solicit, struct in6_addr *daddr, struct in6_addr *saddr){ struct in6_addr addr_buf; struct icmp6hdr icmp6h = { .icmp6_type = NDISC_NEIGHBOUR_SOLICITATION, }; if (saddr == NULL) { if (ipv6_get_lladdr(dev, &addr_buf, (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC))) return; saddr = &addr_buf; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -