X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=net%2Fipv6%2Fndisc.c;h=39bb658f3c447782ddd411102477cddaee9538be;hb=refs%2Fheads%2Fvserver;hp=685a02d2ebf5b9a47763f5b995b3f6c6371d6093;hpb=c7b5ebbddf7bcd3651947760f423e3783bbe6573;p=linux-2.6.git diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c index 685a02d2e..39bb658f3 100644 --- a/net/ipv6/ndisc.c +++ b/net/ipv6/ndisc.c @@ -48,7 +48,6 @@ #endif #include -#include #include #include #include @@ -63,6 +62,7 @@ #include #endif +#include #include #include #include @@ -156,25 +156,54 @@ struct neigh_table nd_tbl = { /* ND options */ struct ndisc_options { - struct nd_opt_hdr *nd_opt_array[7]; - struct nd_opt_hdr *nd_opt_piend; + 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 }; #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_piend +#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) -static u8 *ndisc_fill_option(u8 *opt, int type, void *data, int data_len) +/* + * 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; @@ -230,6 +259,13 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0) 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 options must be silently ignored, @@ -246,6 +282,17 @@ static struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len, 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) { @@ -260,6 +307,9 @@ int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int d 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); @@ -362,7 +412,8 @@ static void pndisc_destructor(struct pneigh_entry *n) */ static inline void ndisc_flow_init(struct flowi *fl, u8 type, - struct in6_addr *saddr, struct in6_addr *daddr) + struct in6_addr *saddr, struct in6_addr *daddr, + int oif) { memset(fl, 0, sizeof(*fl)); ipv6_addr_copy(&fl->fl6_src, saddr); @@ -370,6 +421,8 @@ static inline void ndisc_flow_init(struct flowi *fl, u8 type, 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_na(struct net_device *dev, struct neighbour *neigh, @@ -396,31 +449,32 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, src_addr = solicited_addr; in6_ifa_put(ifp); } else { - if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr, 0)) + if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr)) return; src_addr = &tmpaddr; } - ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, src_addr, daddr); + ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, src_addr, 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) { - dst_release(dst); + if (err < 0) return; - } if (inc_opt) { if (dev->addr_len) - len += NDISC_OPT_SPACE(dev->addr_len); + len += ndisc_opt_addr_space(dev); else inc_opt = 0; } - skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev), + skb = sock_alloc_send_skb(sk, + (MAX_HEADER + sizeof(struct ipv6hdr) + + len + LL_RESERVED_SPACE(dev)), 1, &err); if (skb == NULL) { @@ -444,13 +498,14 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, msg->icmph.icmp6_unused = 0; msg->icmph.icmp6_router = router; msg->icmph.icmp6_solicited = solicited; - msg->icmph.icmp6_override = !!override; + msg->icmph.icmp6_override = override; /* Set the target address. */ ipv6_addr_copy(&msg->target, solicited_addr); if (inc_opt) - ndisc_fill_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len); + ndisc_fill_addr_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, + dev->addr_len, dev->type); /* checksum */ msg->icmph.icmp6_cksum = csum_ipv6_magic(src_addr, daddr, len, @@ -460,7 +515,7 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh, skb->dst = dst; idev = in6_dev_get(dst->dev); - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output); if (!err) { ICMP6_INC_STATS(idev, ICMP6_MIB_OUTNEIGHBORADVERTISEMENTS); @@ -492,24 +547,25 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, saddr = &addr_buf; } - ndisc_flow_init(&fl, NDISC_NEIGHBOUR_SOLICITATION, saddr, daddr); + ndisc_flow_init(&fl, NDISC_NEIGHBOUR_SOLICITATION, 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) { - dst_release(dst); + if (err < 0) return; - } len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); send_llinfo = dev->addr_len && !ipv6_addr_any(saddr); if (send_llinfo) - len += NDISC_OPT_SPACE(dev->addr_len); + len += ndisc_opt_addr_space(dev); - skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev), + skb = sock_alloc_send_skb(sk, + (MAX_HEADER + sizeof(struct ipv6hdr) + + len + LL_RESERVED_SPACE(dev)), 1, &err); if (skb == NULL) { ND_PRINTK0(KERN_ERR @@ -533,7 +589,8 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, ipv6_addr_copy(&msg->target, solicit); if (send_llinfo) - ndisc_fill_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len); + ndisc_fill_addr_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, + dev->addr_len, dev->type); /* checksum */ msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, @@ -544,7 +601,7 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh, /* send it! */ skb->dst = dst; idev = in6_dev_get(dst->dev); - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output); if (!err) { ICMP6_INC_STATS(idev, ICMP6_MIB_OUTNEIGHBORSOLICITS); @@ -568,23 +625,24 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, int len; int err; - ndisc_flow_init(&fl, NDISC_ROUTER_SOLICITATION, saddr, daddr); + ndisc_flow_init(&fl, NDISC_ROUTER_SOLICITATION, saddr, daddr, + dev->ifindex); dst = ndisc_dst_alloc(dev, NULL, daddr, ip6_output); if (!dst) return; err = xfrm_lookup(&dst, &fl, NULL, 0); - if (err < 0) { - dst_release(dst); + if (err < 0) return; - } len = sizeof(struct icmp6hdr); if (dev->addr_len) - len += NDISC_OPT_SPACE(dev->addr_len); + len += ndisc_opt_addr_space(dev); - skb = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev), + skb = sock_alloc_send_skb(sk, + (MAX_HEADER + sizeof(struct ipv6hdr) + + len + LL_RESERVED_SPACE(dev)), 1, &err); if (skb == NULL) { ND_PRINTK0(KERN_ERR @@ -607,7 +665,8 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, opt = (u8*) (hdr + 1); if (dev->addr_len) - ndisc_fill_option(opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len); + ndisc_fill_addr_option(opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, + dev->addr_len, dev->type); /* checksum */ hdr->icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len, @@ -617,7 +676,7 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr, /* send it! */ skb->dst = dst; idev = in6_dev_get(dst->dev); - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + IP6_INC_STATS(idev, IPSTATS_MIB_OUTREQUESTS); err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, dst_output); if (!err) { ICMP6_INC_STATS(idev, ICMP6_MIB_OUTROUTERSOLICITS); @@ -656,7 +715,7 @@ static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb) if (!(neigh->nud_state & NUD_VALID)) { ND_PRINTK1(KERN_DEBUG "%s(): trying to ucast probe in NUD_INVALID: " - "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", + NIP6_FMT "\n", __FUNCTION__, NIP6(*target)); } @@ -677,15 +736,16 @@ static void ndisc_recv_ns(struct sk_buff *skb) struct in6_addr *saddr = &skb->nh.ipv6h->saddr; struct in6_addr *daddr = &skb->nh.ipv6h->daddr; u8 *lladdr = NULL; - int lladdrlen = 0; u32 ndoptlen = skb->tail - msg->opt; struct ndisc_options ndopts; struct net_device *dev = skb->dev; struct inet6_ifaddr *ifp; struct inet6_dev *idev = NULL; struct neighbour *neigh; + struct pneigh_entry *pneigh = NULL; int dad = ipv6_addr_any(saddr); int inc; + int is_router; if (ipv6_addr_is_multicast(&msg->target)) { ND_PRINTK2(KERN_WARNING @@ -714,9 +774,8 @@ static void ndisc_recv_ns(struct sk_buff *skb) } 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(dev->addr_len)) { + lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, dev); + if (!lladdr) { ND_PRINTK2(KERN_WARNING "ICMPv6 NS: invalid link-layer address length\n"); return; @@ -771,8 +830,10 @@ static void ndisc_recv_ns(struct sk_buff *skb) if (ipv6_chk_acast_addr(dev, &msg->target) || (idev->cnf.forwarding && - pneigh_lookup(&nd_tbl, &msg->target, dev, 0))) { - if (skb->stamp.tv_sec != LOCALLY_ENQUEUED && + (ipv6_devconf.proxy_ndp || idev->cnf.proxy_ndp) && + (pneigh = pneigh_lookup(&nd_tbl, + &msg->target, dev, 0)) != NULL)) { + if (!(NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED) && skb->pkt_type != PACKET_HOST && inc != 0 && idev->nd_parms->proxy_delay != 0) { @@ -792,12 +853,14 @@ static void ndisc_recv_ns(struct sk_buff *skb) goto out; } + is_router = !!(pneigh ? pneigh->flags & NTF_ROUTER : idev->cnf.forwarding); + if (dad) { struct in6_addr maddr; ipv6_addr_all_nodes(&maddr); ndisc_send_na(dev, NULL, &maddr, &msg->target, - idev->cnf.forwarding, 0, (ifp != NULL), 1); + is_router, 0, (ifp != NULL), 1); goto out; } @@ -818,7 +881,7 @@ static void ndisc_recv_ns(struct sk_buff *skb) NEIGH_UPDATE_F_OVERRIDE); if (neigh || !dev->hard_header) { ndisc_send_na(dev, neigh, saddr, &msg->target, - idev->cnf.forwarding, + is_router, 1, (ifp != NULL && inc), inc); if (neigh) neigh_release(neigh); @@ -839,7 +902,6 @@ static void ndisc_recv_na(struct sk_buff *skb) struct in6_addr *saddr = &skb->nh.ipv6h->saddr; struct in6_addr *daddr = &skb->nh.ipv6h->daddr; u8 *lladdr = NULL; - int lladdrlen = 0; u32 ndoptlen = skb->tail - msg->opt; struct ndisc_options ndopts; struct net_device *dev = skb->dev; @@ -871,9 +933,8 @@ static void ndisc_recv_na(struct sk_buff *skb) 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(dev->addr_len)) { + lladdr = ndisc_opt_addr_data(ndopts.nd_opts_tgt_lladdr, dev); + if (!lladdr) { ND_PRINTK2(KERN_WARNING "ICMPv6 NA: invalid link-layer address length\n"); return; @@ -900,6 +961,21 @@ static void ndisc_recv_na(struct sk_buff *skb) if (neigh) { u8 old_flags = neigh->flags; + if (neigh->nud_state & NUD_FAILED) + goto out; + + /* + * Don't update the neighbor cache entry on a proxy NA from + * ourselves because either the proxied node is off link or it + * has already sent a NA to us. + */ + if (lladdr && !memcmp(lladdr, dev->dev_addr, dev->addr_len) && + ipv6_devconf.forwarding && ipv6_devconf.proxy_ndp && + pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) { + /* XXX: idev->cnf.prixy_ndp */ + goto out; + } + neigh_update(neigh, lladdr, msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE, NEIGH_UPDATE_F_WEAK_OVERRIDE| @@ -914,22 +990,81 @@ static void ndisc_recv_na(struct sk_buff *skb) struct rt6_info *rt; rt = rt6_get_dflt_router(saddr, dev); if (rt) - ip6_del_rt(rt, NULL, NULL); + ip6_del_rt(rt); } +out: neigh_release(neigh); } } +static void ndisc_recv_rs(struct sk_buff *skb) +{ + struct rs_msg *rs_msg = (struct rs_msg *) skb->h.raw; + unsigned long ndoptlen = skb->len - sizeof(*rs_msg); + struct neighbour *neigh; + struct inet6_dev *idev; + struct in6_addr *saddr = &skb->nh.ipv6h->saddr; + struct ndisc_options ndopts; + u8 *lladdr = NULL; + + if (skb->len < sizeof(*rs_msg)) + return; + + idev = in6_dev_get(skb->dev); + if (!idev) { + if (net_ratelimit()) + ND_PRINTK1("ICMP6 RS: can't find in6 device\n"); + return; + } + + /* Don't accept RS if we're not in router mode */ + if (!idev->cnf.forwarding) + goto out; + + /* + * Don't update NCE if src = ::; + * this implies that the source node has no ip address assigned yet. + */ + if (ipv6_addr_any(saddr)) + goto out; + + /* Parse ND options */ + if (!ndisc_parse_options(rs_msg->opt, ndoptlen, &ndopts)) { + if (net_ratelimit()) + ND_PRINTK2("ICMP6 NS: invalid ND option, ignored\n"); + goto out; + } + + if (ndopts.nd_opts_src_lladdr) { + lladdr = ndisc_opt_addr_data(ndopts.nd_opts_src_lladdr, + skb->dev); + if (!lladdr) + goto out; + } + + neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1); + if (neigh) { + neigh_update(neigh, lladdr, NUD_STALE, + NEIGH_UPDATE_F_WEAK_OVERRIDE| + NEIGH_UPDATE_F_OVERRIDE| + NEIGH_UPDATE_F_OVERRIDE_ISROUTER); + neigh_release(neigh); + } +out: + in6_dev_put(idev); +} + static void ndisc_router_discovery(struct sk_buff *skb) { struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw; - struct neighbour *neigh; + struct neighbour *neigh = NULL; struct inet6_dev *in6_dev; - struct rt6_info *rt; + struct rt6_info *rt = NULL; int lifetime; struct ndisc_options ndopts; int optlen; + unsigned int pref = 0; __u8 * opt = (__u8 *)(ra_msg + 1); @@ -988,12 +1123,27 @@ static void ndisc_router_discovery(struct sk_buff *skb) (ra_msg->icmph.icmp6_addrconf_other ? IF_RA_OTHERCONF : 0); + if (!in6_dev->cnf.accept_ra_defrtr) + goto skip_defrtr; + lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime); +#ifdef CONFIG_IPV6_ROUTER_PREF + pref = ra_msg->icmph.icmp6_router_pref; + /* 10b is handled as if it were 00b (medium) */ + if (pref == ICMPV6_ROUTER_PREF_INVALID || + in6_dev->cnf.accept_ra_rtr_pref) + pref = ICMPV6_ROUTER_PREF_MEDIUM; +#endif + rt = rt6_get_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); + if (rt) + neigh = rt->rt6i_nexthop; + if (rt && lifetime == 0) { - ip6_del_rt(rt, NULL, NULL); + neigh_clone(neigh); + ip6_del_rt(rt); rt = NULL; } @@ -1001,7 +1151,7 @@ static void ndisc_router_discovery(struct sk_buff *skb) ND_PRINTK3(KERN_DEBUG "ICMPv6 RA: adding default router.\n"); - rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); + rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev, pref); if (rt == NULL) { ND_PRINTK0(KERN_ERR "ICMPv6 RA: %s() failed to add default route.\n", @@ -1020,20 +1170,20 @@ static void ndisc_router_discovery(struct sk_buff *skb) return; } neigh->flags |= NTF_ROUTER; - - /* - * If we where using an "all destinations on link" route - * delete it - */ - - rt6_purge_dflt_routers(RTF_ALLONLINK); + } else if (rt) { + rt->rt6i_flags |= (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref); } if (rt) rt->rt6i_expires = jiffies + (HZ * lifetime); - if (ra_msg->icmph.icmp6_hop_limit) + if (ra_msg->icmph.icmp6_hop_limit) { in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit; + if (rt) + rt->u.dst.metrics[RTAX_HOPLIMIT-1] = ra_msg->icmph.icmp6_hop_limit; + } + +skip_defrtr: /* * Update Reachable Time and Retrans Timer @@ -1072,13 +1222,15 @@ static void ndisc_router_discovery(struct sk_buff *skb) * Process options. */ - if (rt && (neigh = rt->rt6i_nexthop) != NULL) { + if (!neigh) + neigh = __neigh_lookup(&nd_tbl, &skb->nh.ipv6h->saddr, + skb->dev, 1); + if (neigh) { 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)) { + 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; @@ -1091,7 +1243,21 @@ static void ndisc_router_discovery(struct sk_buff *skb) NEIGH_UPDATE_F_ISROUTER); } - if (ndopts.nd_opts_pi) { +#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, + &skb->nh.ipv6h->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; @@ -1101,10 +1267,11 @@ static void ndisc_router_discovery(struct sk_buff *skb) } if (ndopts.nd_opts_mtu) { + __be32 n; u32 mtu; - memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu)); - mtu = ntohl(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 @@ -1127,6 +1294,8 @@ static void ndisc_router_discovery(struct sk_buff *skb) out: if (rt) dst_release(&rt->u.dst); + else if (neigh) + neigh_release(neigh); in6_dev_put(in6_dev); } @@ -1141,7 +1310,6 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) 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 @@ -1168,7 +1336,7 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) return; } - if (ipv6_addr_cmp(dest, target) == 0) { + if (ipv6_addr_equal(dest, target)) { on_link = 1; } else if (!(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) { ND_PRINTK2(KERN_WARNING @@ -1196,9 +1364,9 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) 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)) { + 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); @@ -1208,7 +1376,8 @@ static void ndisc_redirect_rcv(struct sk_buff *skb) neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1); if (neigh) { - rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, lladdr, + rt6_redirect(dest, &skb->nh.ipv6h->daddr, + &skb->nh.ipv6h->saddr, neigh, lladdr, on_link); neigh_release(neigh); } @@ -1233,6 +1402,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, int rd_len; int err; int hlen; + u8 ha_buf[MAX_ADDR_LEN], *ha = NULL; dev = skb->dev; @@ -1243,18 +1413,23 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, return; } - ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr); + if (!ipv6_addr_equal(&skb->nh.ipv6h->daddr, target) && + !(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) { + ND_PRINTK2(KERN_WARNING + "ICMPv6 Redirect: target address is not link-local.\n"); + return; + } - rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev->ifindex, 1); - if (rt == NULL) + ndisc_flow_init(&fl, NDISC_REDIRECT, &saddr_buf, &skb->nh.ipv6h->saddr, + dev->ifindex); + + dst = ip6_route_output(NULL, &fl); + if (dst == NULL) return; - dst = &rt->u.dst; err = xfrm_lookup(&dst, &fl, NULL, 0); - if (err) { - dst_release(dst); + if (err) return; - } rt = (struct rt6_info *) dst; @@ -1270,16 +1445,14 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, } 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; - } + 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, @@ -1287,7 +1460,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, rd_len &= ~0x7; len += rd_len; - buff = sock_alloc_send_skb(sk, MAX_HEADER + len + LL_RESERVED_SPACE(dev), + 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 @@ -1324,8 +1499,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, * include target_address option */ - if (dev->addr_len) - opt = ndisc_fill_option(opt, ND_OPT_TARGET_LL_ADDR, neigh->ha, dev->addr_len); + 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. @@ -1344,7 +1520,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, buff->dst = dst; idev = in6_dev_get(dst->dev); - IP6_INC_STATS(IPSTATS_MIB_OUTREQUESTS); + IP6_INC_STATS(idev, 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); @@ -1357,7 +1533,7 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, static void pndisc_redo(struct sk_buff *skb) { - ndisc_rcv(skb); + ndisc_recv_ns(skb); kfree_skb(skb); } @@ -1386,6 +1562,8 @@ int ndisc_rcv(struct sk_buff *skb) return 0; } + memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb)); + switch (msg->icmph.icmp6_type) { case NDISC_NEIGHBOUR_SOLICITATION: ndisc_recv_ns(skb); @@ -1395,6 +1573,10 @@ int ndisc_rcv(struct sk_buff *skb) ndisc_recv_na(skb); break; + case NDISC_ROUTER_SOLICITATION: + ndisc_recv_rs(skb); + break; + case NDISC_ROUTER_ADVERTISEMENT: ndisc_router_discovery(skb); break; @@ -1414,11 +1596,11 @@ static int ndisc_netdev_event(struct notifier_block *this, unsigned long event, switch (event) { case NETDEV_CHANGEADDR: neigh_changeaddr(&nd_tbl, dev); - fib6_run_gc(0); + fib6_run_gc(~0UL); break; case NETDEV_DOWN: neigh_ifdown(&nd_tbl, dev); - fib6_run_gc(0); + fib6_run_gc(~0UL); break; default: break; @@ -1432,18 +1614,103 @@ static struct notifier_block ndisc_netdev_notifier = { }; #ifdef CONFIG_SYSCTL +static 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 (write && dev && (idev = in6_dev_get(dev)) != NULL) { + if (ctl->ctl_name == NET_NEIGH_RETRANS_TIME || + ctl->ctl_name == NET_NEIGH_REACHABLE_TIME) + ndisc_warn_deprecated_sysctl(ctl, "syscall", dev ? dev->name : "default"); + + switch (ctl->ctl_name) { + case NET_NEIGH_RETRANS_TIME: + ret = proc_dointvec(ctl, write, filp, buffer, lenp, ppos); + break; + case NET_NEIGH_REACHABLE_TIME: + ret = proc_dointvec_jiffies(ctl, write, + filp, buffer, lenp, ppos); + break; + case NET_NEIGH_RETRANS_TIME_MS: + case NET_NEIGH_REACHABLE_TIME_MS: + ret = proc_dointvec_ms_jiffies(ctl, write, + filp, buffer, lenp, ppos); + break; + default: + ret = -1; + } + + if (write && 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 proc_dointvec(ctl, write, filp, buffer, lenp, ppos); + 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; +} + #endif int __init ndisc_init(struct net_proto_family *ops) @@ -1477,7 +1744,9 @@ int __init ndisc_init(struct net_proto_family *ops) #ifdef CONFIG_SYSCTL neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6, NET_IPV6_NEIGH, - "ipv6", &ndisc_ifinfo_sysctl_change); + "ipv6", + &ndisc_ifinfo_sysctl_change, + &ndisc_ifinfo_sysctl_strategy); #endif register_netdevice_notifier(&ndisc_netdev_notifier); @@ -1486,6 +1755,7 @@ int __init ndisc_init(struct net_proto_family *ops) void ndisc_cleanup(void) { + unregister_netdevice_notifier(&ndisc_netdev_notifier); #ifdef CONFIG_SYSCTL neigh_sysctl_unregister(&nd_tbl.parms); #endif