vserver 2.0 rc7
[linux-2.6.git] / net / ipv6 / ndisc.c
index 0b45f8d..7c291f4 100644 (file)
@@ -156,14 +156,13 @@ 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_MAX];
 };
 
 #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]
 
@@ -183,6 +182,11 @@ static int ndisc_addr_option_pad(unsigned short type)
        }
 }
 
+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)
 {
@@ -267,6 +271,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) {
@@ -439,7 +454,7 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
 
        if (inc_opt) {
                if (dev->addr_len)
-                       len += NDISC_OPT_SPACE(dev->addr_len);
+                       len += ndisc_opt_addr_space(dev);
                else
                        inc_opt = 0;
        }
@@ -532,7 +547,7 @@ void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
        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),
                                  1, &err);
@@ -608,7 +623,7 @@ void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
 
        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),
                                  1, &err);
@@ -704,7 +719,6 @@ 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;
@@ -741,10 +755,8 @@ static void ndisc_recv_ns(struct sk_buff *skb)
        }
 
        if (ndopts.nd_opts_src_lladdr) {
-               lladdr = (u8*)(ndopts.nd_opts_src_lladdr + 1) +
-                       ndisc_addr_option_pad(dev->type);
-               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;
@@ -867,7 +879,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;
@@ -899,10 +910,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) +
-                       ndisc_addr_option_pad(dev->type);
-               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;
@@ -963,7 +972,6 @@ static void ndisc_recv_rs(struct sk_buff *skb)
        struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
        struct ndisc_options ndopts;
        u8 *lladdr = NULL;
-       int lladdrlen = 0;
 
        if (skb->len < sizeof(*rs_msg))
                return;
@@ -994,10 +1002,9 @@ static void ndisc_recv_rs(struct sk_buff *skb)
        }
 
        if (ndopts.nd_opts_src_lladdr) {
-               lladdr = (u8 *)(ndopts.nd_opts_src_lladdr + 1) +
-                       ndisc_addr_option_pad(skb->dev->type);
-               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)
                        goto out;
        }
 
@@ -1121,8 +1128,11 @@ static void ndisc_router_discovery(struct sk_buff *skb)
        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;
+       }
 
        /*
         *      Update Reachable Time and Retrans Timer
@@ -1166,12 +1176,10 @@ static void ndisc_router_discovery(struct sk_buff *skb)
                                       skb->dev, 1);
        if (neigh) {
                u8 *lladdr = NULL;
-               int lladdrlen;
                if (ndopts.nd_opts_src_lladdr) {
-                       lladdr = (u8*)((ndopts.nd_opts_src_lladdr)+1) +
-                               ndisc_addr_option_pad(skb->dev->type);
-                       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;
@@ -1236,7 +1244,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
@@ -1291,10 +1298,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) +
-                       ndisc_addr_option_pad(skb->dev->type);
-               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);
@@ -1329,6 +1335,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;
 
@@ -1341,10 +1348,9 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
 
        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)
+       dst = ip6_route_output(NULL, &fl);
+       if (dst == NULL)
                return;
-       dst = &rt->u.dst;
 
        err = xfrm_lookup(&dst, &fl, NULL, 0);
        if (err) {
@@ -1366,16 +1372,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,
@@ -1420,8 +1424,8 @@ void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
         *      include target_address option
         */
 
-       if (dev->addr_len)
-               opt = ndisc_fill_addr_option(opt, ND_OPT_TARGET_LL_ADDR, neigh->ha,
+       if (ha)
+               opt = ndisc_fill_addr_option(opt, ND_OPT_TARGET_LL_ADDR, ha,
                                             dev->addr_len, dev->type);
 
        /*
@@ -1515,11 +1519,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;
@@ -1533,18 +1537,106 @@ 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 (ctl->ctl_name == NET_NEIGH_RETRANS_TIME ||
+           ctl->ctl_name == NET_NEIGH_REACHABLE_TIME)
+               ndisc_warn_deprecated_sysctl(ctl, "syscall", dev ? dev->name : "default");
 
-       if (write && dev && (idev = in6_dev_get(dev)) != NULL) {
+       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 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,
+                                       void **context)
+{
+       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,
+                                    context);
+               break;
+       case NET_NEIGH_RETRANS_TIME_MS:
+       case NET_NEIGH_REACHABLE_TIME_MS:
+                ret = sysctl_ms_jiffies(ctl, name, nlen,
+                                        oldval, oldlenp, newval, newlen,
+                                        context);
+                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 proc_dointvec(ctl, write, filp, buffer, lenp, ppos);
+
+       return ret;
 }
+
 #endif
 
 int __init ndisc_init(struct net_proto_family *ops)
@@ -1578,7 +1670,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);