📄 ndisc.c
字号:
rt->u.dst.metrics[RTAX_HOPLIMIT-1] = ra_msg->icmph.icmp6_hop_limit; }skip_defrtr: /* * 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 (!neigh) neigh = __neigh_lookup(&nd_tbl, &ipv6_hdr(skb)->saddr, skb->dev, 1); if (neigh) { u8 *lladdr = NULL; if (ndopts.nd_opts_src_lladdr) { lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, skb->dev); if (!lladdr) { 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); }#ifdef CONFIG_IPV6_ROUTE_INFO if (in6_dev->cnf.accept_ra_rtr_pref && ndopts.nd_opts_ri) { struct nd_opt_hdr *p; for (p = ndopts.nd_opts_ri; p; p = ndisc_next_option(p, ndopts.nd_opts_ri_end)) { if (((struct route_info *)p)->prefix_len > in6_dev->cnf.accept_ra_rt_info_max_plen) continue; rt6_route_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3, &ipv6_hdr(skb)->saddr); } }#endif if (in6_dev->cnf.accept_ra_pinfo && 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) { __be32 n; u32 mtu; memcpy(&n, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu)); mtu = ntohl(n); 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_useropts) { struct nd_opt_hdr *opt; for (opt = ndopts.nd_useropts; opt; opt = ndisc_next_useropt(opt, ndopts.nd_useropts_end)) { ndisc_ra_useropt(skb, opt); } } 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); else if (neigh) neigh_release(neigh); 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; if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: source address is not link-local.\n"); return; } optlen = skb->tail - skb->transport_header; optlen -= sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr); if (optlen < 0) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: packet too short\n"); return; } icmph = icmp6_hdr(skb); 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_equal(dest, target)) { on_link = 1; } else if (ipv6_addr_type(target) != (IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: target address is not link-local unicast.\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 = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, skb->dev); if (!lladdr) { 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, &ipv6_hdr(skb)->daddr, &ipv6_hdr(skb)->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; u8 ha_buf[MAX_ADDR_LEN], *ha = NULL; dev = skb->dev; if (ipv6_get_lladdr(dev, &saddr_buf, IFA_F_TENTATIVE)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: no link-local address on %s\n", dev->name); return; } if (!ipv6_addr_equal(&ipv6_hdr(skb)->daddr, target) && ipv6_addr_type(target) != (IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING "ICMPv6 Redirect: target address is not link-local unicast.\n"); return; } ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &ipv6_hdr(skb)->saddr, dev->ifindex); dst = ip6_route_output(NULL, &fl); if (dst == NULL) return; err = xfrm_lookup(&dst, &fl, NULL, 0); if (err) 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) { read_lock_bh(&neigh->lock); if (neigh->nud_state & NUD_VALID) { memcpy(ha_buf, neigh->ha, dev->addr_len); read_unlock_bh(&neigh->lock); ha = ha_buf; len += ndisc_opt_addr_space(dev); } else read_unlock_bh(&neigh->lock); } 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 + sizeof(struct ipv6hdr) + 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, &ipv6_hdr(skb)->saddr, IPPROTO_ICMPV6, len); skb_set_transport_header(buff, skb_tail_pointer(buff) - buff->data); skb_put(buff, len); icmph = icmp6_hdr(buff); 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, &ipv6_hdr(skb)->daddr); opt = (u8*) (addrp + 1); /* * include target_address option */ if (ha) opt = ndisc_fill_addr_option(opt, ND_OPT_TARGET_LL_ADDR, ha, dev->addr_len, dev->type); /* * 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, ipv6_hdr(skb), rd_len - 8); icmph->icmp6_cksum = csum_ipv6_magic(&saddr_buf, &ipv6_hdr(skb)->saddr, len, IPPROTO_ICMPV6, csum_partial((u8 *) icmph, len, 0)); buff->dst = dst; idev = in6_dev_get(dst->dev); IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, buff, NULL, dst->dev, dst_output); if (!err) { ICMP6MSGOUT_INC_STATS(idev, NDISC_REDIRECT); ICMP6_INC_STATS(idev, ICMP6_MIB_OUTMSGS); } if (likely(idev != NULL)) in6_dev_put(idev);}static void pndisc_redo(struct sk_buff *skb){ ndisc_recv_ns(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_transport_header(skb); __skb_push(skb, skb->data - skb_transport_header(skb)); if (ipv6_hdr(skb)->hop_limit != 255) { ND_PRINTK2(KERN_WARNING "ICMPv6 NDISC: invalid hop-limit: %d\n", ipv6_hdr(skb)->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; } memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb)); 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_SOLICITATION: ndisc_recv_rs(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; if (dev->nd_net != &init_net) return NOTIFY_DONE; switch (event) { case NETDEV_CHANGEADDR: neigh_changeaddr(&nd_tbl, dev); fib6_run_gc(~0UL); break; case NETDEV_DOWN: neigh_ifdown(&nd_tbl, dev); fib6_run_gc(~0UL); break; default: break; } return NOTIFY_DONE;}static struct notifier_block ndisc_netdev_notifier = { .notifier_call = ndisc_netdev_event,};#ifdef CONFIG_SYSCTLstatic void ndisc_warn_deprecated_sysctl(struct ctl_table *ctl, const char *func, const char *dev_name){ static char warncomm[TASK_COMM_LEN]; static int warned; if (strcmp(warncomm, current->comm) && warned < 5) { strcpy(warncomm, current->comm); printk(KERN_WARNING "process `%s' is using deprecated sysctl (%s) " "net.ipv6.neigh.%s.%s; " "Use net.ipv6.neigh.%s.%s_ms " "instead.\n", warncomm, func, dev_name, ctl->procname, dev_name, ctl->procname); warned++; }}int 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; int ret; if ((strcmp(ctl->procname, "retrans_time") == 0) || (strcmp(ctl->procname, "base_reachable_time") == 0)) ndisc_warn_deprecated_sysctl(ctl, "syscall", dev ? dev->name : "default"); if (strcmp(ctl->procname, "retrans_time") == 0) ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos); else if (strcmp(ctl->procname, "base_reachable_time") == 0) ret = proc_dointvec_jiffies(ctl, write, filp, buffer, lenp, ppos); else if ((strcmp(ctl->procname, "retrans_time_ms") == 0) || (strcmp(ctl->procname, "base_reachable_time_ms") == 0)) ret = proc_dointvec_ms_jiffies(ctl, write, filp, buffer, lenp, ppos); else ret = -1; if (write && ret == 0 && dev && (idev = in6_dev_get(dev)) != NULL) { if (ctl->data == &idev->nd_parms->base_reachable_time) idev->nd_parms->reachable_time = neigh_rand_reach_time(idev->nd_parms->base_reachable_time); idev->tstamp = jiffies; inet6_ifinfo_notify(RTM_NEWLINK, idev); in6_dev_put(idev); } return ret;}static int ndisc_ifinfo_sysctl_strategy(ctl_table *ctl, int __user *name, int nlen, void __user *oldval, size_t __user *oldlenp, void __user *newval, size_t newlen){ struct net_device *dev = ctl->extra1; struct inet6_dev *idev; int ret; if (ctl->ctl_name == NET_NEIGH_RETRANS_TIME || ctl->ctl_name == NET_NEIGH_REACHABLE_TIME) ndisc_warn_deprecated_sysctl(ctl, "procfs", dev ? dev->name : "default"); switch (ctl->ctl_name) { case NET_NEIGH_REACHABLE_TIME: ret = sysctl_jiffies(ctl, name, nlen, oldval, oldlenp, newval, newlen); break; case NET_NEIGH_RETRANS_TIME_MS: case NET_NEIGH_REACHABLE_TIME_MS: ret = sysctl_ms_jiffies(ctl, name, nlen, oldval, oldlenp, newval, newlen); break; default: ret = 0; } if (newval && newlen && ret > 0 && dev && (idev = in6_dev_get(dev)) != NULL) { if (ctl->ctl_name == NET_NEIGH_REACHABLE_TIME || ctl->ctl_name == NET_NEIGH_REACHABLE_TIME_MS) idev->nd_parms->reachable_time = neigh_rand_reach_time(idev->nd_parms->base_reachable_time); idev->tstamp = jiffies; inet6_ifinfo_notify(RTM_NEWLINK, idev); in6_dev_put(idev); } return ret;}#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, &ndisc_ifinfo_sysctl_strategy);#endif register_netdevice_notifier(&ndisc_netdev_notifier); return 0;}void ndisc_cleanup(void){ unregister_netdevice_notifier(&ndisc_netdev_notifier);#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 + -