📄 ndisc.c
字号:
if (rt == NULL && lifetime) { ND_PRINTK3(KERN_DEBUG "ICMPv6 RA: adding default router.\n"); rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); if (rt == NULL) { ND_PRINTK0(KERN_ERR "ICMPv6 RA: %s() failed to add default route.\n", __FUNCTION__); in6_dev_put(in6_dev); return; } neigh = rt->rt6i_nexthop; if (neigh == NULL) { ND_PRINTK0(KERN_ERR "ICMPv6 RA: %s() got default router without neighbour.\n", __FUNCTION__); dst_release(&rt->u.dst); in6_dev_put(in6_dev); return; } neigh->flags |= NTF_ROUTER; /* * If we where using an "all destinations on link" route * delete it */ rt6_purge_dflt_routers(RTF_ALLONLINK); } if (rt) rt->rt6i_expires = jiffies + (HZ * lifetime); if (ra_msg->icmph.icmp6_hop_limit) in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit; /* * Update Reachable Time and Retrans Timer */ if (in6_dev->nd_parms) { unsigned long rtime = ntohl(ra_msg->retrans_timer); if (rtime && rtime/1000 < MAX_SCHEDULE_TIMEOUT/HZ) { rtime = (rtime*HZ)/1000; if (rtime < HZ/10) rtime = HZ/10; in6_dev->nd_parms->retrans_time = rtime; in6_dev->tstamp = jiffies; inet6_ifinfo_notify(RTM_NEWLINK, in6_dev); } rtime = ntohl(ra_msg->reachable_time); if (rtime && rtime/1000 < MAX_SCHEDULE_TIMEOUT/(3*HZ)) { rtime = (rtime*HZ)/1000; if (rtime < HZ/10) rtime = HZ/10; if (rtime != in6_dev->nd_parms->base_reachable_time) { in6_dev->nd_parms->base_reachable_time = rtime; in6_dev->nd_parms->gc_staletime = 3 * rtime; in6_dev->nd_parms->reachable_time = neigh_rand_reach_time(rtime); in6_dev->tstamp = jiffies; inet6_ifinfo_notify(RTM_NEWLINK, in6_dev); } } } /* * Process options. */ if (rt && (neigh = rt->rt6i_nexthop) != NULL) { u8 *lladdr = NULL; int lladdrlen; if (ndopts.nd_opts_src_lladdr) { lladdr = (u8*)((ndopts.nd_opts_src_lladdr)+1); lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3; if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) { ND_PRINTK2(KERN_WARNING "ICMPv6 RA: invalid link-layer address length\n"); goto out; } } neigh_update(neigh, lladdr, NUD_STALE, NEIGH_UPDATE_F_WEAK_OVERRIDE| NEIGH_UPDATE_F_OVERRIDE| NEIGH_UPDATE_F_OVERRIDE_ISROUTER| NEIGH_UPDATE_F_ISROUTER); } if (ndopts.nd_opts_pi) { struct nd_opt_hdr *p; for (p = ndopts.nd_opts_pi; p; p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) { addrconf_prefix_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3); } } if (ndopts.nd_opts_mtu) { u32 mtu; memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu)); mtu = ntohl(mtu); if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) { ND_PRINTK2(KERN_WARNING "ICMPv6 RA: invalid mtu: %d\n", mtu); } else if (in6_dev->cnf.mtu6 != mtu) { in6_dev->cnf.mtu6 = mtu; if (rt) rt->u.dst.metrics[RTAX_MTU-1] = mtu; rt6_mtu_change(skb->dev, mtu); } } if (ndopts.nd_opts_tgt_lladdr || ndopts.nd_opts_rh) { ND_PRINTK2(KERN_WARNING "ICMPv6 RA: invalid RA options"); }out: if (rt) dst_release(&rt->u.dst); in6_dev_put(in6_dev);}static void ndisc_redirect_rcv(struct sk_buff *skb){ struct inet6_dev *in6_dev; struct icmp6hdr *icmph; struct in6_addr *dest; struct in6_addr *target; /* new first hop to destination */ struct neighbour *neigh; int on_link = 0; struct ndisc_options ndopts; int optlen; u8 *lladdr = NULL; int lladdrlen; if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr) & IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: source address is not link-local.\n"); return; } optlen = skb->tail - skb->h.raw; optlen -= sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr); if (optlen < 0) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: packet too short\n"); return; } icmph = (struct icmp6hdr *) skb->h.raw; target = (struct in6_addr *) (icmph + 1); dest = target + 1; if (ipv6_addr_is_multicast(dest)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: destination address is multicast.\n"); return; } if (ipv6_addr_cmp(dest, target) == 0) { on_link = 1; } else if (!(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: target address is not link-local.\n"); return; } in6_dev = in6_dev_get(skb->dev); if (!in6_dev) return; if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects) { in6_dev_put(in6_dev); return; } /* RFC2461 8.1: * The IP source address of the Redirect MUST be the same as the current * first-hop router for the specified ICMP Destination Address. */ if (!ndisc_parse_options((u8*)(dest + 1), optlen, &ndopts)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: invalid ND options\n"); in6_dev_put(in6_dev); return; } if (ndopts.nd_opts_tgt_lladdr) { lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1); lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3; if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: invalid link-layer address length\n"); in6_dev_put(in6_dev); return; } } neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1); if (neigh) { rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, lladdr, on_link); neigh_release(neigh); } in6_dev_put(in6_dev);}void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, struct in6_addr *target){ struct sock *sk = ndisc_socket->sk; int len = sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr); struct sk_buff *buff; struct icmp6hdr *icmph; struct in6_addr saddr_buf; struct in6_addr *addrp; struct net_device *dev; struct rt6_info *rt; struct dst_entry *dst; struct inet6_dev *idev; struct flowi fl; u8 *opt; int rd_len; int err; int hlen; dev = skb->dev; if (ipv6_get_lladdr(dev, &saddr_buf)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: no link-local address on %s\n", dev->name); return; } ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr); rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev->ifindex, 1); if (rt == NULL) return; dst = &rt->u.dst; err = xfrm_lookup(&dst, &fl, NULL, 0); if (err) { dst_release(dst); return; } rt = (struct rt6_info *) dst; if (rt->rt6i_flags & RTF_GATEWAY) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: destination is not a neighbour.\n"); dst_release(dst); return; } if (!xrlim_allow(dst, 1*HZ)) { dst_release(dst); return; } if (dev->addr_len) { if (neigh->nud_state&NUD_VALID) { len += NDISC_OPT_SPACE(dev->addr_len); } else { /* If nexthop is not valid, do not redirect! We will make it later, when will be sure, that it is alive. */ dst_release(dst); return; } } rd_len = min_t(unsigned int, IPV6_MIN_MTU-sizeof(struct ipv6hdr)-len, skb->len + 8); rd_len &= ~0x7; len += rd_len; buff = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev), 1, &err); if (buff == NULL) { ND_PRINTK0(KERN_ERR "ICMPv6 Redirect: %s() failed to allocate an skb.\n", __FUNCTION__); dst_release(dst); return; } hlen = 0; skb_reserve(buff, LL_RESERVED_SPACE(dev)); ip6_nd_hdr(sk, buff, dev, &saddr_buf, &skb->nh.ipv6h->saddr, IPPROTO_ICMPV6, len); icmph = (struct icmp6hdr *)skb_put(buff, len); buff->h.raw = (unsigned char*)icmph; memset(icmph, 0, sizeof(struct icmp6hdr)); icmph->icmp6_type = NDISC_REDIRECT; /* * copy target and destination addresses */ addrp = (struct in6_addr *)(icmph + 1); ipv6_addr_copy(addrp, target); addrp++; ipv6_addr_copy(addrp, &skb->nh.ipv6h->daddr); opt = (u8*) (addrp + 1); /* * include target_address option */ if (dev->addr_len) opt = ndisc_fill_option(opt, ND_OPT_TARGET_LL_ADDR, neigh->ha, dev->addr_len); /* * build redirect option and copy skb over to the new packet. */ memset(opt, 0, 8); *(opt++) = ND_OPT_REDIRECT_HDR; *(opt++) = (rd_len >> 3); opt += 6; memcpy(opt, skb->nh.ipv6h, rd_len - 8); icmph->icmp6_cksum = csum_ipv6_magic(&saddr_buf, &skb->nh.ipv6h->saddr, len, IPPROTO_ICMPV6, csum_partial((u8 *) icmph, len, 0)); buff->dst = dst; idev = in6_dev_get(dst->dev); IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, buff, NULL, dst->dev, dst_output); if (!err) { ICMP6_INC_STATS(idev, ICMP6_MIB_OUTREDIRECTS); ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS); } if (likely(idev != NULL)) in6_dev_put(idev);}static void pndisc_redo(struct sk_buff *skb){ ndisc_rcv(skb); kfree_skb(skb);}int ndisc_rcv(struct sk_buff *skb){ struct nd_msg *msg; if (!pskb_may_pull(skb, skb->len)) return 0; msg = (struct nd_msg *) skb->h.raw; __skb_push(skb, skb->data-skb->h.raw); if (skb->nh.ipv6h->hop_limit != 255) { ND_PRINTK2(KERN_WARNING "ICMPv6 NDISC: invalid hop-limit: %d\n", skb->nh.ipv6h->hop_limit); return 0; } if (msg->icmph.icmp6_code != 0) { ND_PRINTK2(KERN_WARNING "ICMPv6 NDISC: invalid ICMPv6 code: %d\n", msg->icmph.icmp6_code); return 0; } switch (msg->icmph.icmp6_type) { case NDISC_NEIGHBOUR_SOLICITATION: ndisc_recv_ns(skb); break; case NDISC_NEIGHBOUR_ADVERTISEMENT: ndisc_recv_na(skb); break; case NDISC_ROUTER_ADVERTISEMENT: ndisc_router_discovery(skb); break; case NDISC_REDIRECT: ndisc_redirect_rcv(skb); break; }; return 0;}static int ndisc_netdev_event(struct notifier_block *this, unsigned long event, void *ptr){ struct net_device *dev = ptr; switch (event) { case NETDEV_CHANGEADDR: neigh_changeaddr(&nd_tbl, dev); fib6_run_gc(0); break; case NETDEV_DOWN: neigh_ifdown(&nd_tbl, dev); fib6_run_gc(0); break; default: break; } return NOTIFY_DONE;}static struct notifier_block ndisc_netdev_notifier = { .notifier_call = ndisc_netdev_event,};#ifdef CONFIG_SYSCTLint ndisc_ifinfo_sysctl_change(struct ctl_table *ctl, int write, struct file * filp, void __user *buffer, size_t *lenp, loff_t *ppos){ struct net_device *dev = ctl->extra1; struct inet6_dev *idev; if (write && dev && (idev = in6_dev_get(dev)) != NULL) { idev->tstamp = jiffies; inet6_ifinfo_notify(RTM_NEWLINK, idev); in6_dev_put(idev); } return proc_dointvec(ctl, write, filp, buffer, lenp, ppos);}#endifint __init ndisc_init(struct net_proto_family *ops){ struct ipv6_pinfo *np; struct sock *sk; int err; err = sock_create_kern(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6, &ndisc_socket); if (err < 0) { ND_PRINTK0(KERN_ERR "ICMPv6 NDISC: Failed to initialize the control socket (err %d).\n", err); ndisc_socket = NULL; /* For safety. */ return err; } sk = ndisc_socket->sk; np = inet6_sk(sk); sk->sk_allocation = GFP_ATOMIC; np->hop_limit = 255; /* Do not loopback ndisc messages */ np->mc_loop = 0; sk->sk_prot->unhash(sk); /* * Initialize the neighbour table */ neigh_table_init(&nd_tbl);#ifdef CONFIG_SYSCTL neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6", &ndisc_ifinfo_sysctl_change);#endif register_netdevice_notifier(&ndisc_netdev_notifier); return 0;}void ndisc_cleanup(void){#ifdef CONFIG_SYSCTL neigh_sysctl_unregister(&nd_tbl.parms);#endif neigh_table_clear(&nd_tbl); sock_release(ndisc_socket); ndisc_socket = NULL; /* For safety. */}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -