diff options
Diffstat (limited to 'net/ipv6/addrconf.c')
-rw-r--r-- | net/ipv6/addrconf.c | 875 |
1 files changed, 654 insertions, 221 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index c66902f13..c4faba4b7 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -5,7 +5,7 @@ * Authors: * Pedro Roque <roque@di.fc.ul.pt> * - * $Id: addrconf.c,v 1.30 1997/12/09 17:12:47 freitag Exp $ + * $Id: addrconf.c,v 1.32 1997/12/27 20:41:18 kuznet Exp $ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -35,6 +35,9 @@ #include <linux/route.h> #include <linux/inetdevice.h> #include <linux/init.h> +#ifdef CONFIG_SYSCTL +#include <linux/sysctl.h> +#endif #include <linux/proc_fs.h> #include <net/sock.h> @@ -47,6 +50,7 @@ #include <net/addrconf.h> #include <net/ip.h> #include <linux/if_tunnel.h> +#include <linux/rtnetlink.h> #include <asm/uaccess.h> @@ -59,20 +63,20 @@ #define ADBG(x) #endif -/* - * Configured unicast address list - */ -struct inet6_ifaddr *inet6_addr_lst[IN6_ADDR_HSIZE]; +#ifdef CONFIG_SYSCTL +static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p); +static void addrconf_sysctl_unregister(struct ipv6_devconf *p); +#endif /* - * Hash list of configured multicast addresses + * Configured unicast address list */ -struct ifmcaddr6 *inet6_mcast_lst[IN6_ADDR_HSIZE]; +static struct inet6_ifaddr *inet6_addr_lst[IN6_ADDR_HSIZE]; /* * AF_INET6 device list */ -struct inet6_dev *inet6_dev_lst[IN6_ADDR_HSIZE]; +static struct inet6_dev *inet6_dev_lst[IN6_ADDR_HSIZE]; static atomic_t addr_list_lock = ATOMIC_INIT(0); @@ -83,12 +87,41 @@ static struct timer_list addr_chk_timer = { 0, 0, addrconf_verify }; -static int addrconf_ifdown(struct device *dev); +static int addrconf_ifdown(struct device *dev, int how); static void addrconf_dad_start(struct inet6_ifaddr *ifp); static void addrconf_dad_timer(unsigned long data); static void addrconf_dad_completed(struct inet6_ifaddr *ifp); static void addrconf_rs_timer(unsigned long data); +static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa); + +struct ipv6_devconf ipv6_devconf = +{ + 0, /* forwarding */ + IPV6_DEFAULT_HOPLIMIT, /* hop limit */ + 576, /* mtu */ + 1, /* accept RAs */ + 1, /* accept redirects */ + 1, /* autoconfiguration */ + 1, /* dad transmits */ + MAX_RTR_SOLICITATIONS, /* router solicits */ + RTR_SOLICITATION_INTERVAL, /* rtr solicit interval */ + MAX_RTR_SOLICITATION_DELAY, /* rtr solicit delay */ +}; + +static struct ipv6_devconf ipv6_devconf_dflt = +{ + 0, /* forwarding */ + IPV6_DEFAULT_HOPLIMIT, /* hop limit */ + 576, /* mtu */ + 1, /* accept RAs */ + 1, /* accept redirects */ + 1, /* autoconfiguration */ + 1, /* dad transmits */ + MAX_RTR_SOLICITATIONS, /* router solicits */ + RTR_SOLICITATION_INTERVAL, /* rtr solicit interval */ + MAX_RTR_SOLICITATION_DELAY, /* rtr solicit delay */ +}; int ipv6_addr_type(struct in6_addr *addr) { @@ -151,12 +184,27 @@ static struct inet6_dev * ipv6_add_dev(struct device *dev) struct inet6_dev *ndev, **bptr, *iter; int hash; + if (dev->mtu < 576) + return NULL; + ndev = kmalloc(sizeof(struct inet6_dev), gfp_any()); if (ndev) { memset(ndev, 0, sizeof(struct inet6_dev)); ndev->dev = dev; + memcpy(&ndev->cnf, &ipv6_devconf_dflt, sizeof(ndev->cnf)); + ndev->cnf.mtu6 = dev->mtu; + ndev->cnf.sysctl = NULL; + ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl); + if (ndev->nd_parms == NULL) { + kfree(ndev); + return NULL; + } +#ifdef CONFIG_SYSCTL + neigh_sysctl_register(dev, ndev->nd_parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6"); + addrconf_sysctl_register(ndev, &ndev->cnf); +#endif hash = ipv6_devindex_hash(dev->ifindex); bptr = &inet6_dev_lst[hash]; iter = *bptr; @@ -165,34 +213,35 @@ static struct inet6_dev * ipv6_add_dev(struct device *dev) bptr = &iter->next; *bptr = ndev; + } return ndev; } -void addrconf_forwarding_on(void) +static struct inet6_dev * ipv6_find_idev(struct device *dev) { struct inet6_dev *idev; - int i; - for (i = 0; i < IN6_ADDR_HSIZE; i++) { - for (idev = inet6_dev_lst[i]; idev; idev = idev->next) { -#if ACONF_DEBUG >= 2 - printk(KERN_DEBUG "dev %s\n", idev->dev->name); -#endif + if ((idev = ipv6_get_idev(dev)) == NULL) { + idev = ipv6_add_dev(dev); + if (idev == NULL) + return NULL; + } + if (dev->flags&IFF_UP) + ipv6_mc_up(idev); + return idev; +} - if (idev->dev->type == ARPHRD_ETHER) { - struct in6_addr maddr; +static void addrconf_forward_change(struct inet6_dev *idev) +{ + int i; -#if ACONF_DEBUG >= 2 - printk(KERN_DEBUG "joining all-routers\n"); -#endif - idev->router = 1; + if (idev) + return; - /* Wrong. It is user level function. */ - ipv6_addr_all_routers(&maddr); - ipv6_dev_mc_inc(idev->dev, &maddr); - } - } + for (i = 0; i < IN6_ADDR_HSIZE; i++) { + for (idev = inet6_dev_lst[i]; idev; idev = idev->next) + idev->cnf.forwarding = ipv6_devconf.forwarding; } } @@ -244,11 +293,13 @@ struct inet6_ifaddr * ipv6_add_addr(struct inet6_dev *idev, return ifa; } -void ipv6_del_addr(struct inet6_ifaddr *ifp) +static void ipv6_del_addr(struct inet6_ifaddr *ifp) { struct inet6_ifaddr *iter, **back; int hash; + ipv6_ifa_notify(RTM_DELADDR, ifp); + if (atomic_read(&addr_list_lock)) { ifp->flags |= ADDR_INVALID; return; @@ -399,33 +450,75 @@ struct inet6_ifaddr * ipv6_get_lladdr(struct device *dev) * to the host. */ -struct inet6_ifaddr * ipv6_chk_addr(struct in6_addr *addr) +struct inet6_ifaddr * ipv6_chk_addr(struct in6_addr *addr, struct device *dev, int nd) { struct inet6_ifaddr * ifp; u8 hash; + unsigned flags = 0; + + if (!nd) + flags |= DAD_STATUS|ADDR_INVALID; atomic_inc(&addr_list_lock); hash = ipv6_addr_hash(addr); for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) { - if (ipv6_addr_cmp(&ifp->addr, addr) == 0) - break; + if (ipv6_addr_cmp(&ifp->addr, addr) == 0 && !(ifp->flags&flags)) { + if (dev == NULL || ifp->idev->dev == dev || + !(ifp->scope&(IFA_LINK|IFA_HOST))) + break; + } } atomic_dec(&addr_list_lock); - return ifp; + return ifp; +} + +void addrconf_dad_failure(struct inet6_ifaddr *ifp) +{ + printk(KERN_INFO "%s: duplicate address detected!\n", ifp->idev->dev->name); + del_timer(&ifp->timer); + ipv6_del_addr(ifp); } + /* Join to solicited addr multicast group. */ static void addrconf_join_solict(struct device *dev, struct in6_addr *addr) { struct in6_addr maddr; - addrconf_addr_solict_mult(addr, &maddr); + if (dev->flags&(IFF_LOOPBACK|IFF_NOARP)) + return; + +#ifndef CONFIG_IPV6_NO_PB + addrconf_addr_solict_mult_old(addr, &maddr); + ipv6_dev_mc_inc(dev, &maddr); +#endif +#ifdef CONFIG_IPV6_EUI64 + addrconf_addr_solict_mult_new(addr, &maddr); ipv6_dev_mc_inc(dev, &maddr); +#endif } +static void addrconf_leave_solict(struct device *dev, struct in6_addr *addr) +{ + struct in6_addr maddr; + + if (dev->flags&(IFF_LOOPBACK|IFF_NOARP)) + return; + +#ifndef CONFIG_IPV6_NO_PB + addrconf_addr_solict_mult_old(addr, &maddr); + ipv6_dev_mc_dec(dev, &maddr); +#endif +#ifdef CONFIG_IPV6_EUI64 + addrconf_addr_solict_mult_new(addr, &maddr); + ipv6_dev_mc_dec(dev, &maddr); +#endif +} + + #ifdef CONFIG_IPV6_EUI64 static int ipv6_generate_eui64(u8 *eui, struct device *dev) { @@ -462,6 +555,7 @@ addrconf_prefix_route(struct in6_addr *pfx, int plen, struct device *dev, rtmsg.rtmsg_ifindex = dev->ifindex; rtmsg.rtmsg_info = info; rtmsg.rtmsg_flags = RTF_UP|RTF_ADDRCONF; + rtmsg.rtmsg_type = RTMSG_NEWROUTE; /* Prevent useless cloning on PtP SIT. This thing is done here expecting that the whole @@ -469,12 +563,8 @@ addrconf_prefix_route(struct in6_addr *pfx, int plen, struct device *dev, */ if (dev->type == ARPHRD_SIT && (dev->flags&IFF_POINTOPOINT)) rtmsg.rtmsg_flags |= RTF_NONEXTHOP; - rtmsg.rtmsg_type = RTMSG_NEWROUTE; ip6_route_add(&rtmsg, &err); - - if (err) - printk(KERN_DEBUG "IPv6: error %d adding prefix route\n", err); } /* Create "default" multicast route to the interface */ @@ -482,7 +572,6 @@ addrconf_prefix_route(struct in6_addr *pfx, int plen, struct device *dev, static void addrconf_add_mroute(struct device *dev) { struct in6_rtmsg rtmsg; - struct rt6_info *rt; int err; memset(&rtmsg, 0, sizeof(rtmsg)); @@ -493,25 +582,12 @@ static void addrconf_add_mroute(struct device *dev) rtmsg.rtmsg_ifindex = dev->ifindex; rtmsg.rtmsg_flags = RTF_UP|RTF_ADDRCONF; rtmsg.rtmsg_type = RTMSG_NEWROUTE; - - rt = ip6_route_add(&rtmsg, &err); - - /* - * Pedro makes interesting thing here, he attached - * fake nexthop to multicast route. - * It is trick to avoid cloning, ugly, but efficient. --ANK - */ - - if (err) - printk(KERN_DEBUG "IPv6: error %d adding mroute\n", err); - else - rt->rt6i_nexthop = ndisc_get_neigh(dev, &rtmsg.rtmsg_dst); + ip6_route_add(&rtmsg, &err); } static void sit_route_add(struct device *dev) { struct in6_rtmsg rtmsg; - struct rt6_info *rt; int err; memset(&rtmsg, 0, sizeof(rtmsg)); @@ -521,19 +597,10 @@ static void sit_route_add(struct device *dev) /* prefix length - 96 bytes "::d.d.d.d" */ rtmsg.rtmsg_dst_len = 96; - rtmsg.rtmsg_flags = RTF_UP; + rtmsg.rtmsg_flags = RTF_UP|RTF_NONEXTHOP; rtmsg.rtmsg_ifindex = dev->ifindex; - rt = ip6_route_add(&rtmsg, &err); - - /* See comment in addrconf_add_mroute. - * It is the same trick, but to avoid cloning for direct - * sit routes i.e. IPv4 comaptible destinations. - */ - if (err) - printk(KERN_DEBUG "sit_route_add: error %d in route_add\n", err); - else - rt->rt6i_nexthop = ndisc_get_neigh(dev, &rtmsg.rtmsg_dst); + ip6_route_add(&rtmsg, &err); } static void addrconf_add_lroute(struct device *dev) @@ -546,24 +613,16 @@ static void addrconf_add_lroute(struct device *dev) static struct inet6_dev *addrconf_add_dev(struct device *dev) { - struct in6_addr maddr; struct inet6_dev *idev; - if ((idev = ipv6_get_idev(dev)) == NULL) { - idev = ipv6_add_dev(dev); - if (idev == NULL) - return NULL; - } + if ((idev = ipv6_find_idev(dev)) == NULL) + return NULL; /* Add default multicast route */ addrconf_add_mroute(dev); /* Add link local route */ addrconf_add_lroute(dev); - - /* Join to all nodes multicast group. */ - ipv6_addr_all_nodes(&maddr); - ipv6_dev_mc_inc(dev, &maddr); return idev; } @@ -575,6 +634,12 @@ void addrconf_prefix_rcv(struct device *dev, u8 *opt, int len) __u32 prefered_lft; int addr_type; unsigned long rt_expires; + struct inet6_dev *in6_dev = ipv6_get_idev(dev); + + if (in6_dev == NULL) { + printk(KERN_DEBUG "addrconf: device %s not configured\n", dev->name); + return; + } pinfo = (struct prefix_info *) opt; @@ -613,9 +678,15 @@ void addrconf_prefix_rcv(struct device *dev, u8 *opt, int len) * 2) Configure prefixes with the auto flag set */ - rt_expires = jiffies + valid_lft * HZ; - if (rt_expires < jiffies) - rt_expires = ~0; + /* Avoid arithemtic overflow. Really, we could + save rt_expires in seconds, likely valid_lft, + but it would require division in fib gc, that it + not good. + */ + if (valid_lft >= 0x7FFFFFFF/HZ) + rt_expires = 0; + else + rt_expires = jiffies + valid_lft * HZ; rt = rt6_lookup(&pinfo->prefix, NULL, dev, RTF_LINKRT); @@ -633,7 +704,7 @@ void addrconf_prefix_rcv(struct device *dev, u8 *opt, int len) /* Try to figure out our local address for this prefix */ - if (pinfo->autoconf && ipv6_config.autoconf) { + if (pinfo->autoconf && in6_dev->cnf.autoconf) { struct inet6_ifaddr * ifp; struct in6_addr addr; int plen; @@ -660,18 +731,12 @@ void addrconf_prefix_rcv(struct device *dev, u8 *opt, int len) return; ok: - ifp = ipv6_chk_addr(&addr); + ifp = ipv6_chk_addr(&addr, dev, 1); - if (ifp == NULL && valid_lft) { - struct inet6_dev *in6_dev = ipv6_get_idev(dev); + if ((ifp == NULL || (ifp->flags&ADDR_INVALID)) && valid_lft) { - if (in6_dev == NULL) { - printk(KERN_DEBUG "addrconf: device %s not configured\n", dev->name); - return; - } - - ifp = ipv6_add_addr(in6_dev, &addr, - addr_type & IPV6_ADDR_SCOPE_MASK); + if (ifp == NULL) + ifp = ipv6_add_addr(in6_dev, &addr, addr_type & IPV6_ADDR_SCOPE_MASK); if (ifp == NULL) return; @@ -687,9 +752,14 @@ ok: } if (ifp) { + int event = 0; ifp->valid_lft = valid_lft; ifp->prefered_lft = prefered_lft; ifp->tstamp = jiffies; + if (ifp->flags & ADDR_INVALID) + event = RTM_NEWADDR; + ifp->flags &= ~(ADDR_DEPRECATED|ADDR_INVALID); + ipv6_ifa_notify(event, ifp); } } } @@ -705,25 +775,26 @@ int addrconf_set_dstaddr(void *arg) struct device *dev; int err = -EINVAL; - if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) { - err = -EFAULT; + rtnl_lock(); + + err = -EFAULT; + if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) goto err_exit; - } dev = dev_get_by_index(ireq.ifr6_ifindex); - if (dev == NULL) { - err = -ENODEV; + err = -ENODEV; + if (dev == NULL) goto err_exit; - } if (dev->type == ARPHRD_SIT) { struct ifreq ifr; mm_segment_t oldfs; struct ip_tunnel_parm p; + err = -EADDRNOTAVAIL; if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4)) - return -EADDRNOTAVAIL; + goto err_exit; memset(&p, 0, sizeof(p)); p.iph.daddr = ireq.ifr6_addr.s6_addr32[3]; @@ -747,27 +818,21 @@ int addrconf_set_dstaddr(void *arg) } err_exit: + rtnl_unlock(); return err; } /* * Manual configuration of address on an interface */ -int addrconf_add_ifaddr(void *arg) +static int inet6_addr_add(int ifindex, struct in6_addr *pfx, int plen) { - struct inet6_dev *idev; - struct in6_ifreq ireq; struct inet6_ifaddr *ifp; + struct inet6_dev *idev; struct device *dev; int scope; - if (!suser()) - return -EPERM; - - if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) - return -EFAULT; - - if ((dev = dev_get_by_index(ireq.ifr6_ifindex)) == NULL) + if ((dev = dev_get_by_index(ifindex)) == NULL) return -ENODEV; if (!(dev->flags&IFF_UP)) @@ -776,49 +841,83 @@ int addrconf_add_ifaddr(void *arg) if ((idev = addrconf_add_dev(dev)) == NULL) return -ENOBUFS; - scope = ipv6_addr_scope(&ireq.ifr6_addr); + scope = ipv6_addr_scope(pfx); - if((ifp = ipv6_add_addr(idev, &ireq.ifr6_addr, scope)) == NULL) + if ((ifp = ipv6_add_addr(idev, pfx, scope)) == NULL) return -ENOMEM; - ifp->prefix_len = ireq.ifr6_prefixlen; + ifp->prefix_len = plen; ifp->flags |= ADDR_PERMANENT; addrconf_dad_start(ifp); return 0; } -int addrconf_del_ifaddr(void *arg) +static int inet6_addr_del(int ifindex, struct in6_addr *pfx, int plen) { - struct in6_ifreq ireq; struct inet6_ifaddr *ifp; + struct inet6_dev *idev; struct device *dev; int scope; - struct inet6_dev *idev; - - if (!suser()) - return -EPERM; - if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) - return -EFAULT; - - if ((dev = dev_get_by_index(ireq.ifr6_ifindex)) == NULL) + if ((dev = dev_get_by_index(ifindex)) == NULL) return -ENODEV; if ((idev = ipv6_get_idev(dev)) == NULL) return -ENXIO; - scope = ipv6_addr_scope(&ireq.ifr6_addr); + scope = ipv6_addr_scope(pfx); - for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) { - if (ifp->scope == scope && - (!memcmp(&ireq.ifr6_addr, &ifp->addr, sizeof(struct in6_addr)))) { + for (ifp = idev->addr_list; ifp; ifp=ifp->if_next) { + if (ifp->scope == scope && ifp->prefix_len == plen && + (!memcmp(pfx, &ifp->addr, sizeof(struct in6_addr)))) { ipv6_del_addr(ifp); - break; + + /* If the last address is deleted administratively, + disable IPv6 on this interface. + */ + + if (idev->addr_list == NULL) + addrconf_ifdown(idev->dev, 1); + return 0; } } + return -EADDRNOTAVAIL; +} - return 0; + +int addrconf_add_ifaddr(void *arg) +{ + struct in6_ifreq ireq; + int err; + + if (!suser()) + return -EPERM; + + if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) + return -EFAULT; + + rtnl_lock(); + err = inet6_addr_add(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen); + rtnl_unlock(); + return err; +} + +int addrconf_del_ifaddr(void *arg) +{ + struct in6_ifreq ireq; + int err; + + if (!suser()) + return -EPERM; + + if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) + return -EFAULT; + + rtnl_lock(); + err = inet6_addr_del(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen); + rtnl_unlock(); + return err; } static void sit_add_v4_addrs(struct inet6_dev *idev) @@ -843,7 +942,7 @@ static void sit_add_v4_addrs(struct inet6_dev *idev) if (ifp) { ifp->flags |= ADDR_PERMANENT; ifp->prefix_len = 128; - ip6_rt_addr_add(&ifp->addr, idev->dev); + ipv6_ifa_notify(RTM_NEWADDR, ifp); } return; } @@ -876,7 +975,7 @@ static void sit_add_v4_addrs(struct inet6_dev *idev) else ifp->prefix_len = 96; ifp->flags |= ADDR_PERMANENT; - ip6_rt_addr_add(&ifp->addr, dev); + ipv6_ifa_notify(RTM_NEWADDR, ifp); } } } @@ -887,16 +986,13 @@ static void init_loopback(struct device *dev) struct in6_addr addr; struct inet6_dev *idev; struct inet6_ifaddr * ifp; - int err; /* ::1 */ memset(&addr, 0, sizeof(struct in6_addr)); addr.s6_addr[15] = 1; - idev = ipv6_add_dev(dev); - - if (idev == NULL) { + if ((idev = ipv6_find_idev(dev)) == NULL) { printk(KERN_DEBUG "init loopback: add_dev failed\n"); return; } @@ -909,10 +1005,9 @@ static void init_loopback(struct device *dev) } ifp->flags |= ADDR_PERMANENT; + ifp->prefix_len = 128; - err = ip6_rt_addr_add(&addr, dev); - if (err) - printk(KERN_DEBUG "init_loopback: error in route_add\n"); + ipv6_ifa_notify(RTM_NEWADDR, ifp); } static void addrconf_add_linklocal(struct inet6_dev *idev, struct in6_addr *addr) @@ -932,7 +1027,6 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, struct in6_addr *addr static void addrconf_dev_config(struct device *dev) { struct in6_addr addr; - struct in6_addr maddr; struct inet6_dev * idev; if (dev->type != ARPHRD_ETHER) { @@ -964,17 +1058,6 @@ static void addrconf_dev_config(struct device *dev) dev->dev_addr, dev->addr_len); addrconf_add_linklocal(idev, &addr); #endif - - if (ipv6_config.forwarding) { - idev->router = 1; - - /* It is wrong. - It is routing daemon or radvd that must make it, - rather than kernel. - */ - ipv6_addr_all_routers(&maddr); - ipv6_dev_mc_inc(dev, &maddr); - } } static void addrconf_sit_config(struct device *dev) @@ -987,8 +1070,7 @@ static void addrconf_sit_config(struct device *dev) * our v4 addrs in the tunnel */ - idev = ipv6_add_dev(dev); - if (idev == NULL) { + if ((idev = ipv6_find_idev(dev)) == NULL) { printk(KERN_DEBUG "init sit: add_dev failed\n"); return; } @@ -1026,78 +1108,99 @@ int addrconf_notify(struct notifier_block *this, unsigned long event, break; }; +#ifdef CONFIG_IPV6_NETLINK rt6_sndmsg(RTMSG_NEWDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0); +#endif break; + case NETDEV_CHANGEMTU: + /* BUGGG... Should scan FIB to change pmtu on routes. --ANK */ + if (dev->mtu >= 576) + break; + + /* MTU falled under 576. Stop IPv6 on this interface. */ + case NETDEV_DOWN: + case NETDEV_UNREGISTER: /* - * Remove all addresses from this interface - * and take the interface out of the list. + * Remove all addresses from this interface. */ - if (addrconf_ifdown(dev) == 0) { -#if 0 - rt6_ifdown(dev); -#endif + if (addrconf_ifdown(dev, event != NETDEV_DOWN) == 0) { +#ifdef CONFIG_IPV6_NETLINK rt6_sndmsg(RTMSG_DELDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0); +#endif } break; + case NETDEV_CHANGE: + break; }; - + return NOTIFY_OK; } -static int addrconf_ifdown(struct device *dev) +static int addrconf_ifdown(struct device *dev, int how) { struct inet6_dev *idev, **bidev; struct inet6_ifaddr *ifa, **bifa; int i, hash; - start_bh_atomic(); + rt6_ifdown(dev); + neigh_ifdown(&nd_tbl, dev); - hash = ipv6_devindex_hash(dev->ifindex); - bidev = &inet6_dev_lst[hash]; + idev = ipv6_get_idev(dev); + if (idev == NULL) + return -ENODEV; - for (idev = inet6_dev_lst[hash]; idev; idev = idev->next) { - if (idev->dev == dev) { - *bidev = idev->next; - break; - } - bidev = &idev->next; - } + start_bh_atomic(); - if (idev == NULL) { - end_bh_atomic(); + /* Discard multicast list */ - printk(KERN_DEBUG "addrconf_ifdown: invalid device %p\n",dev); - return -ENODEV; - } + if (how == 1) + ipv6_mc_destroy_dev(idev); + else + ipv6_mc_down(idev); - /* - * FIXME: clear multicast group membership - */ + /* Discard address list */ + + idev->addr_list = NULL; /* - * clean addr_list + * Clean addresses hash table */ for (i=0; i<16; i++) { bifa = &inet6_addr_lst[i]; - for (ifa=inet6_addr_lst[i]; ifa; ) { + while ((ifa = *bifa) != NULL) { if (ifa->idev == idev) { *bifa = ifa->lst_next; del_timer(&ifa->timer); + ipv6_ifa_notify(RTM_DELADDR, ifa); kfree(ifa); - ifa = *bifa; continue; } bifa = &ifa->lst_next; - ifa = *bifa; } } - kfree(idev); + /* Delete device from device hash table (if unregistered) */ + + if (how == 1) { + hash = ipv6_devindex_hash(dev->ifindex); + + for (bidev = &inet6_dev_lst[hash]; (idev=*bidev) != NULL; bidev = &idev->next) { + if (idev->dev == dev) { + *bidev = idev->next; + neigh_parms_release(&nd_tbl, idev->nd_parms); +#ifdef CONFIG_SYSCTL + addrconf_sysctl_unregister(&idev->cnf); +#endif + kfree(idev); + break; + } + } + } end_bh_atomic(); return 0; } @@ -1109,7 +1212,7 @@ static void addrconf_rs_timer(unsigned long data) ifp = (struct inet6_ifaddr *) data; - if (ipv6_config.forwarding) + if (ifp->idev->cnf.forwarding) return; if (ifp->idev->if_flags & IF_RA_RCVD) { @@ -1120,19 +1223,16 @@ static void addrconf_rs_timer(unsigned long data) return; } - if (ifp->probes++ <= ipv6_config.rtr_solicits) { + if (ifp->probes++ <= ifp->idev->cnf.rtr_solicits) { struct in6_addr all_routers; - ipv6_addr_set(&all_routers, - __constant_htonl(0xff020000U), 0, 0, - __constant_htonl(0x2U)); + ipv6_addr_all_routers(&all_routers); - ndisc_send_rs(ifp->idev->dev, &ifp->addr, - &all_routers); + ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers); ifp->timer.function = addrconf_rs_timer; ifp->timer.expires = (jiffies + - ipv6_config.rtr_solicit_interval); + ifp->idev->cnf.rtr_solicit_interval); add_timer(&ifp->timer); } else { struct in6_rtmsg rtmsg; @@ -1158,7 +1258,6 @@ static void addrconf_rs_timer(unsigned long data) */ static void addrconf_dad_start(struct inet6_ifaddr *ifp) { - static int rand_seed = 1; struct device *dev; unsigned long rand_num; @@ -1177,15 +1276,12 @@ static void addrconf_dad_start(struct inet6_ifaddr *ifp) return; } - if (rand_seed) { - rand_seed = 0; - nd_rand_seed = ifp->addr.s6_addr32[3]; - } + net_srandom(ifp->addr.s6_addr32[3]); - ifp->probes = ipv6_config.dad_transmits; + ifp->probes = ifp->idev->cnf.dad_transmits; ifp->flags |= DAD_INCOMPLETE; - rand_num = ipv6_random() % ipv6_config.rtr_solicit_delay; + rand_num = net_random() % ifp->idev->cnf.rtr_solicit_delay; ifp->timer.function = addrconf_dad_timer; ifp->timer.expires = jiffies + rand_num; @@ -1215,11 +1311,16 @@ static void addrconf_dad_timer(unsigned long data) /* send a neighbour solicitation for our addr */ memset(&unspec, 0, sizeof(unspec)); - addrconf_addr_solict_mult(&ifp->addr, &mcaddr); - +#ifdef CONFIG_IPV6_EUI64 + addrconf_addr_solict_mult_new(&ifp->addr, &mcaddr); ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec); +#endif +#ifndef CONFIG_IPV6_NO_PB + addrconf_addr_solict_mult_old(&ifp->addr, &mcaddr); + ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec); +#endif - ifp->timer.expires = jiffies + ipv6_config.rtr_solicit_interval; + ifp->timer.expires = jiffies + ifp->idev->cnf.rtr_solicit_interval; add_timer(&ifp->timer); } @@ -1231,20 +1332,18 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp) * Configure the address for reception. Now it is valid. */ - ip6_rt_addr_add(&ifp->addr, dev); + ipv6_ifa_notify(RTM_NEWADDR, ifp); /* If added prefix is link local and forwarding is off, start sending router solicitations. */ - if (ipv6_config.forwarding == 0 && + if (ifp->idev->cnf.forwarding == 0 && (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) == 0 && (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL)) { struct in6_addr all_routers; - ipv6_addr_set(&all_routers, - __constant_htonl(0xff020000U), 0, 0, - __constant_htonl(0x2U)); + ipv6_addr_all_routers(&all_routers); /* * If a host as already performed a random delay @@ -1256,7 +1355,7 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp) ifp->probes = 1; ifp->timer.function = addrconf_rs_timer; ifp->timer.expires = (jiffies + - ipv6_config.rtr_solicit_interval); + ifp->idev->cnf.rtr_solicit_interval); ifp->idev->if_flags |= IF_RS_SENT; add_timer(&ifp->timer); } @@ -1319,52 +1418,365 @@ void addrconf_verify(unsigned long foo) for (i=0; i < IN6_ADDR_HSIZE; i++) { for (ifp=inet6_addr_lst[i]; ifp;) { + if (ifp->flags & ADDR_INVALID) { + struct inet6_ifaddr *bp = ifp; + ifp= ifp->lst_next; + ipv6_del_addr(bp); + continue; + } if (!(ifp->flags & ADDR_PERMANENT)) { struct inet6_ifaddr *bp; unsigned long age; age = (now - ifp->tstamp) / HZ; - if (age > ifp->prefered_lft) - ifp->flags |= ADDR_DEPRECATED; - bp = ifp; - ifp=ifp->lst_next; + ifp= ifp->lst_next; if (age > bp->valid_lft) ipv6_del_addr(bp); + else if (age > bp->prefered_lft) { + bp->flags |= ADDR_DEPRECATED; + ipv6_ifa_notify(0, bp); + } continue; } - ifp=ifp->lst_next; + ifp = ifp->lst_next; } } addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY; - add_timer(&addr_chk_timer); + add_timer(&addr_chk_timer); } -/* - * Init / cleanup code - */ +#ifdef CONFIG_RTNETLINK -__initfunc(void addrconf_init(void)) +static int +inet6_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) { -#ifdef MODULE - struct device *dev; + struct rtattr **rta = arg; + struct ifaddrmsg *ifm = NLMSG_DATA(nlh); + struct in6_addr *pfx; + + pfx = NULL; + if (rta[IFA_ADDRESS-1]) { + if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx)) + return -EINVAL; + pfx = RTA_DATA(rta[IFA_ADDRESS-1]); + } + if (rta[IFA_LOCAL-1]) { + if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx))) + return -EINVAL; + pfx = RTA_DATA(rta[IFA_LOCAL-1]); + } + + return inet6_addr_del(ifm->ifa_index, pfx, ifm->ifa_prefixlen); +} + +static int +inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) +{ + struct rtattr **rta = arg; + struct ifaddrmsg *ifm = NLMSG_DATA(nlh); + struct in6_addr *pfx; + + pfx = NULL; + if (rta[IFA_ADDRESS-1]) { + if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx)) + return -EINVAL; + pfx = RTA_DATA(rta[IFA_ADDRESS-1]); + } + if (rta[IFA_LOCAL-1]) { + if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx))) + return -EINVAL; + pfx = RTA_DATA(rta[IFA_LOCAL-1]); + } + + return inet6_addr_add(ifm->ifa_index, pfx, ifm->ifa_prefixlen); +} + +static int inet6_fill_ifaddr(struct sk_buff *skb, struct inet6_ifaddr *ifa, + pid_t pid, u32 seq, int event) +{ + struct ifaddrmsg *ifm; + struct nlmsghdr *nlh; + struct ifa_cacheinfo ci; + unsigned char *b = skb->tail; + + nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm)); + ifm = NLMSG_DATA(nlh); + ifm->ifa_family = AF_INET6; + ifm->ifa_prefixlen = ifa->prefix_len; + ifm->ifa_flags = ifa->flags & ~ADDR_INVALID; + ifm->ifa_scope = RT_SCOPE_UNIVERSE; + if (ifa->scope&IFA_HOST) + ifm->ifa_scope = RT_SCOPE_HOST; + else if (ifa->scope&IFA_LINK) + ifm->ifa_scope = RT_SCOPE_LINK; + else if (ifa->scope&IFA_SITE) + ifm->ifa_scope = RT_SCOPE_SITE; + ifm->ifa_index = ifa->idev->dev->ifindex; + RTA_PUT(skb, IFA_ADDRESS, 16, &ifa->addr); + if (!(ifa->flags&IFA_F_PERMANENT)) { + ci.ifa_prefered = ifa->prefered_lft; + ci.ifa_valid = ifa->valid_lft; + if (ci.ifa_prefered != 0xFFFFFFFF) { + long tval = (jiffies - ifa->tstamp)/HZ; + ci.ifa_prefered -= tval; + if (ci.ifa_valid != 0xFFFFFFFF) + ci.ifa_valid -= tval; + } + RTA_PUT(skb, IFA_CACHEINFO, sizeof(ci), &ci); + } + nlh->nlmsg_len = skb->tail - b; + return skb->len; + +nlmsg_failure: +rtattr_failure: + skb_trim(skb, b - skb->data); + return -1; +} + +static int inet6_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb) +{ + int idx, ip_idx; + int s_idx, s_ip_idx; + struct inet6_ifaddr *ifa; + + s_idx = cb->args[0]; + s_ip_idx = ip_idx = cb->args[1]; + + for (idx=0; idx < IN6_ADDR_HSIZE; idx++) { + if (idx < s_idx) + continue; + if (idx > s_idx) + s_ip_idx = 0; + start_bh_atomic(); + for (ifa=inet6_addr_lst[idx], ip_idx = 0; ifa; + ifa = ifa->lst_next, ip_idx++) { + if (ip_idx < s_ip_idx) + continue; + if (inet6_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid, + cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0) { + end_bh_atomic(); + goto done; + } + } + end_bh_atomic(); + } +done: + cb->args[0] = idx; + cb->args[1] = ip_idx; + + return skb->len; +} + +static void inet6_ifa_notify(int event, struct inet6_ifaddr *ifa) +{ + struct sk_buff *skb; + int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128); + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, ENOBUFS); + return; + } + if (inet6_fill_ifaddr(skb, ifa, 0, 0, event) < 0) { + kfree_skb(skb); + netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, EINVAL); + return; + } + NETLINK_CB(skb).dst_groups = RTMGRP_IPV6_IFADDR; + netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV6_IFADDR, GFP_ATOMIC); +} + +static struct rtnetlink_link inet6_rtnetlink_table[RTM_MAX-RTM_BASE+1] = +{ + { NULL, NULL, }, + { NULL, NULL, }, + { NULL, NULL, }, + { NULL, NULL, }, + + { inet6_rtm_newaddr, NULL, }, + { inet6_rtm_deladdr, NULL, }, + { NULL, inet6_dump_ifaddr, }, + { NULL, NULL, }, + + { inet6_rtm_newroute, NULL, }, + { inet6_rtm_delroute, NULL, }, + { NULL, inet6_dump_fib, }, + { NULL, NULL, }, +}; #endif - /* - * init address and device hash lists - */ +static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp) +{ +#ifdef CONFIG_RTNETLINK + inet6_ifa_notify(event ? : RTM_NEWADDR, ifp); +#endif + switch (event) { + case RTM_NEWADDR: + ip6_rt_addr_add(&ifp->addr, ifp->idev->dev); + break; + case RTM_DELADDR: + start_bh_atomic(); + addrconf_leave_solict(ifp->idev->dev, &ifp->addr); + if (ipv6_chk_addr(&ifp->addr, ifp->idev->dev, 0) == NULL) + ip6_rt_addr_del(&ifp->addr, ifp->idev->dev); + end_bh_atomic(); + break; + } +} - memset(inet6_addr_lst, 0, IN6_ADDR_HSIZE * sizeof(struct inet6_ifaddr *)); +#ifdef CONFIG_SYSCTL - memset(inet6_mcast_lst, 0, IN6_ADDR_HSIZE * sizeof(struct ifmcaddr6 *)); +static +int addrconf_sysctl_forward(ctl_table *ctl, int write, struct file * filp, + void *buffer, size_t *lenp) +{ + int *valp = ctl->data; + int val = *valp; + int ret; - memset(inet6_dev_lst, 0, IN6_ADDR_HSIZE * sizeof(struct inet6_dev *)); + ret = proc_dointvec(ctl, write, filp, buffer, lenp); + if (write && *valp != val && valp != &ipv6_devconf_dflt.forwarding) { + struct inet6_dev *idev = NULL; + + if (valp != &ipv6_devconf.forwarding) { + struct device *dev = dev_get_by_index(ctl->ctl_name); + if (dev) + idev = ipv6_get_idev(dev); + if (idev == NULL) + return ret; + } else + ipv6_devconf_dflt.forwarding = ipv6_devconf.forwarding; + + addrconf_forward_change(idev); + + if (*valp) + rt6_purge_dflt_routers(0); + } + + return ret; +} + +static struct addrconf_sysctl_table +{ + struct ctl_table_header *sysctl_header; + ctl_table addrconf_vars[11]; + ctl_table addrconf_dev[2]; + ctl_table addrconf_conf_dir[2]; + ctl_table addrconf_proto_dir[2]; + ctl_table addrconf_root_dir[2]; +} addrconf_sysctl = { + NULL, + {{NET_IPV6_FORWARDING, "forwarding", + &ipv6_devconf.forwarding, sizeof(int), 0644, NULL, + &addrconf_sysctl_forward}, + + {NET_IPV6_HOP_LIMIT, "hop_limit", + &ipv6_devconf.hop_limit, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_MTU, "mtu", + &ipv6_devconf.mtu6, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_ACCEPT_RA, "accept_ra", + &ipv6_devconf.accept_ra, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_ACCEPT_REDIRECTS, "accept_redirects", + &ipv6_devconf.accept_redirects, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_AUTOCONF, "autoconf", + &ipv6_devconf.autoconf, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_DAD_TRANSMITS, "dad_transmits", + &ipv6_devconf.dad_transmits, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_RTR_SOLICITS, "router_solicitations", + &ipv6_devconf.rtr_solicits, sizeof(int), 0644, NULL, + &proc_dointvec}, + + {NET_IPV6_RTR_SOLICIT_INTERVAL, "router_solicitation_interval", + &ipv6_devconf.rtr_solicit_interval, sizeof(int), 0644, NULL, + &proc_dointvec_jiffies}, + + {NET_IPV6_RTR_SOLICIT_DELAY, "router_solicitation_delay", + &ipv6_devconf.rtr_solicit_delay, sizeof(int), 0644, NULL, + &proc_dointvec_jiffies}, + + {0}}, + + {{NET_PROTO_CONF_ALL, "all", NULL, 0, 0555, addrconf_sysctl.addrconf_vars},{0}}, + {{NET_IPV6_CONF, "conf", NULL, 0, 0555, addrconf_sysctl.addrconf_dev},{0}}, + {{NET_IPV6, "ipv6", NULL, 0, 0555, addrconf_sysctl.addrconf_conf_dir},{0}}, + {{CTL_NET, "net", NULL, 0, 0555, addrconf_sysctl.addrconf_proto_dir},{0}} +}; + +static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p) +{ + int i; + struct device *dev = idev ? idev->dev : NULL; + struct addrconf_sysctl_table *t; + + t = kmalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) + return; + memcpy(t, &addrconf_sysctl, sizeof(*t)); + for (i=0; i<sizeof(t->addrconf_vars)/sizeof(t->addrconf_vars[0])-1; i++) { + t->addrconf_vars[i].data += (char*)p - (char*)&ipv6_devconf; + t->addrconf_vars[i].de = NULL; + } + if (dev) { + t->addrconf_dev[0].procname = dev->name; + t->addrconf_dev[0].ctl_name = dev->ifindex; + } else { + t->addrconf_dev[0].procname = "default"; + t->addrconf_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT; + } + t->addrconf_dev[0].child = t->addrconf_vars; + t->addrconf_dev[0].de = NULL; + t->addrconf_conf_dir[0].child = t->addrconf_dev; + t->addrconf_conf_dir[0].de = NULL; + t->addrconf_proto_dir[0].child = t->addrconf_conf_dir; + t->addrconf_proto_dir[0].de = NULL; + t->addrconf_root_dir[0].child = t->addrconf_proto_dir; + t->addrconf_root_dir[0].de = NULL; + + t->sysctl_header = register_sysctl_table(t->addrconf_root_dir, 0); + if (t->sysctl_header == NULL) + kfree(t); +} + +static void addrconf_sysctl_unregister(struct ipv6_devconf *p) +{ + if (p->sysctl) { + struct addrconf_sysctl_table *t = p->sysctl; + p->sysctl = NULL; + unregister_sysctl_table(t->sysctl_header); + kfree(t); + } +} + + +#endif + +/* + * Init / cleanup code + */ + +__initfunc(void addrconf_init(void)) +{ #ifdef MODULE + struct device *dev; + /* This takes sense only during module load. */ for (dev = dev_base; dev; dev = dev->next) { @@ -1390,6 +1802,14 @@ __initfunc(void addrconf_init(void)) addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY; add_timer(&addr_chk_timer); +#ifdef CONFIG_RTNETLINK + rtnetlink_links[AF_INET6] = inet6_rtnetlink_table; +#endif +#ifdef CONFIG_SYSCTL + addrconf_sysctl.sysctl_header = + register_sysctl_table(addrconf_sysctl.addrconf_root_dir, 0); + addrconf_sysctl_register(NULL, &ipv6_devconf_dflt); +#endif } #ifdef MODULE @@ -1399,6 +1819,14 @@ void addrconf_cleanup(void) struct inet6_ifaddr *ifa; int i; +#ifdef CONFIG_RTNETLINK + rtnetlink_links[AF_INET6] = NULL; +#endif +#ifdef CONFIG_SYSCTL + addrconf_sysctl_unregister(&ipv6_devconf_dflt); + addrconf_sysctl_unregister(&ipv6_devconf); +#endif + del_timer(&addr_chk_timer); /* @@ -1409,10 +1837,11 @@ void addrconf_cleanup(void) struct inet6_dev *next; for (idev = inet6_dev_lst[i]; idev; idev = next) { next = idev->next; - addrconf_ifdown(idev->dev); + addrconf_ifdown(idev->dev, 1); } } + start_bh_atomic(); /* * clean addr_list */ @@ -1423,9 +1852,13 @@ void addrconf_cleanup(void) bifa = ifa; ifa = ifa->lst_next; - kfree(bifa); + printk(KERN_DEBUG "bug: IPv6 address leakage detected: ifa=%p\n", bifa); + /* Do not free it; something is wrong. + Now we can investigate it with debugger. + */ } } + end_bh_atomic(); #ifdef CONFIG_PROC_FS proc_net_unregister(iface_proc_entry.low_ino); |