Commit 453c5e30 authored by David Stevens's avatar David Stevens Committed by David S. Miller

[IPV6]: Add anycast support.

parent 6d6ce30e
......@@ -65,6 +65,8 @@ struct ipv6_mreq {
int ipv6mr_ifindex;
};
#define ipv6mr_acaddr ipv6mr_multiaddr
struct in6_flowlabel_req
{
struct in6_addr flr_dst;
......@@ -166,6 +168,8 @@ struct in6_flowlabel_req
#define IPV6_MTU 24
#define IPV6_RECVERR 25
#define IPV6_V6ONLY 26
#define IPV6_JOIN_ANYCAST 27
#define IPV6_LEAVE_ANYCAST 28
/* IPV6_MTU_DISCOVER values */
#define IPV6_PMTUDISC_DONT 0
......
......@@ -172,6 +172,7 @@ struct ipv6_pinfo {
ipv6only:1;
struct ipv6_mc_socklist *ipv6_mc_list;
struct ipv6_ac_socklist *ipv6_ac_list;
struct ipv6_fl_socklist *ipv6_fl_list;
__u32 dst_cookie;
......
......@@ -469,6 +469,10 @@ extern struct net_device *dev_getbyhwaddr(unsigned short type, char *hwaddr);
extern void dev_add_pack(struct packet_type *pt);
extern void dev_remove_pack(struct packet_type *pt);
extern int dev_get(const char *name);
extern struct net_device *dev_get_by_flags(unsigned short flags,
unsigned short mask);
extern struct net_device *__dev_get_by_flags(unsigned short flags,
unsigned short mask);
extern struct net_device *dev_get_by_name(const char *name);
extern struct net_device *__dev_get_by_name(const char *name);
extern struct net_device *dev_alloc(const char *name, int *err);
......
......@@ -63,7 +63,15 @@ extern struct inet6_ifaddr * ipv6_get_ifaddr(struct in6_addr *addr,
extern int ipv6_get_saddr(struct dst_entry *dst,
struct in6_addr *daddr,
struct in6_addr *saddr);
extern int ipv6_dev_get_saddr(struct net_device *dev,
struct in6_addr *daddr,
struct in6_addr *saddr,
int onlink);
extern int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *);
extern void addrconf_join_solict(struct net_device *dev,
struct in6_addr *addr);
extern void addrconf_leave_solict(struct net_device *dev,
struct in6_addr *addr);
/*
* multicast prototypes (mcast.c)
......@@ -93,6 +101,26 @@ extern int ipv6_chk_mcast_addr(struct net_device *dev,
extern void addrconf_prefix_rcv(struct net_device *dev,
u8 *opt, int len);
/*
* anycast prototypes (anycast.c)
*/
extern int ipv6_sock_ac_join(struct sock *sk,
int ifindex,
struct in6_addr *addr);
extern int ipv6_sock_ac_drop(struct sock *sk,
int ifindex,
struct in6_addr *addr);
extern void ipv6_sock_ac_close(struct sock *sk);
extern int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex);
extern int ipv6_dev_ac_inc(struct net_device *dev,
struct in6_addr *addr);
extern int ipv6_dev_ac_dec(struct net_device *dev,
struct in6_addr *addr);
extern int ipv6_chk_acast_addr(struct net_device *dev,
struct in6_addr *addr);
/* Device notifier */
extern int register_inet6addr_notifier(struct notifier_block *nb);
extern int unregister_inet6addr_notifier(struct notifier_block *nb);
......
......@@ -75,6 +75,25 @@ struct ifmcaddr6
spinlock_t mca_lock;
};
/* Anycast stuff */
struct ipv6_ac_socklist
{
struct in6_addr acl_addr;
int acl_ifindex;
struct ipv6_ac_socklist *acl_next;
};
struct ifacaddr6
{
struct in6_addr aca_addr;
struct inet6_dev *aca_idev;
struct ifacaddr6 *aca_next;
int aca_users;
atomic_t aca_refcnt;
spinlock_t aca_lock;
};
#define IFA_HOST IPV6_ADDR_LOOPBACK
#define IFA_LINK IPV6_ADDR_LINKLOCAL
#define IFA_SITE IPV6_ADDR_SITELOCAL
......@@ -108,6 +127,7 @@ struct inet6_dev
struct inet6_ifaddr *addr_list;
struct ifmcaddr6 *mc_list;
struct ifacaddr6 *ac_list;
rwlock_t lock;
atomic_t refcnt;
__u32 if_flags;
......
......@@ -546,6 +546,50 @@ struct net_device *dev_getbyhwaddr(unsigned short type, char *ha)
return dev;
}
/**
* dev_get_by_flags - find any device with given flags
* @if_flags: IFF_* values
* @mask: bitmask of bits in if_flags to check
*
* Search for any interface with the given flags. Returns NULL if a device
* is not found or a pointer to the device. The device returned has
* had a reference added and the pointer is safe until the user calls
* dev_put to indicate they have finished with it.
*/
struct net_device * dev_get_by_flags(unsigned short if_flags, unsigned short mask)
{
struct net_device *dev;
read_lock(&dev_base_lock);
dev = __dev_get_by_flags(if_flags, mask);
if (dev)
dev_hold(dev);
read_unlock(&dev_base_lock);
return dev;
}
/**
* __dev_get_by_flags - find any device with given flags
* @if_flags: IFF_* values
* @mask: bitmask of bits in if_flags to check
*
* Search for any interface with the given flags. Returns NULL if a device
* is not found or a pointer to the device. The caller must hold either
* the RTNL semaphore or @dev_base_lock.
*/
struct net_device *__dev_get_by_flags(unsigned short if_flags, unsigned short mask)
{
struct net_device *dev;
for (dev = dev_base; dev != NULL; dev = dev->next) {
if (((dev->flags ^ if_flags) & mask) == 0)
return dev;
}
return NULL;
}
/**
* dev_alloc_name - allocate a name for a device
* @dev: device
......
......@@ -4,7 +4,7 @@
obj-$(CONFIG_IPV6) += ipv6.o
ipv6-objs := af_inet6.o ip6_output.o ip6_input.o addrconf.o sit.o \
ipv6-objs := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \
route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \
protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
......
......@@ -174,21 +174,15 @@ const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
int ipv6_addr_type(struct in6_addr *addr)
{
int type;
u32 st;
st = addr->s6_addr32[0];
/* Consider all addresses with the first three bits different of
000 and 111 as unicasts.
*/
if ((st & htonl(0xE0000000)) != htonl(0x00000000) &&
(st & htonl(0xE0000000)) != htonl(0xE0000000))
return IPV6_ADDR_UNICAST;
if ((st & htonl(0xFF000000)) == htonl(0xFF000000)) {
int type = IPV6_ADDR_MULTICAST;
if ((st & __constant_htonl(0xFF000000)) == __constant_htonl(0xFF000000)) {
type = IPV6_ADDR_MULTICAST;
switch((st & htonl(0x00FF0000))) {
switch((st & __constant_htonl(0x00FF0000))) {
case __constant_htonl(0x00010000):
type |= IPV6_ADDR_LOOPBACK;
break;
......@@ -203,29 +197,53 @@ int ipv6_addr_type(struct in6_addr *addr)
};
return type;
}
/* check for reserved anycast addresses */
if ((st & htonl(0xFFC00000)) == htonl(0xFE800000))
return (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_UNICAST);
if ((st & __constant_htonl(0xE0000000)) &&
((addr->s6_addr32[2] == __constant_htonl(0xFDFFFFFF) &&
(addr->s6_addr32[3] | __constant_htonl(0x7F)) == (u32)~0) ||
(addr->s6_addr32[2] == 0 && addr->s6_addr32[3] == 0)))
type = IPV6_ADDR_ANYCAST;
else
type = IPV6_ADDR_UNICAST;
if ((st & htonl(0xFFC00000)) == htonl(0xFEC00000))
return (IPV6_ADDR_SITELOCAL | IPV6_ADDR_UNICAST);
/* Consider all addresses with the first three bits different of
000 and 111 as finished.
*/
if ((st & __constant_htonl(0xE0000000)) != __constant_htonl(0x00000000) &&
(st & __constant_htonl(0xE0000000)) != __constant_htonl(0xE0000000))
return type;
if ((st & __constant_htonl(0xFFC00000)) == __constant_htonl(0xFE800000))
return (IPV6_ADDR_LINKLOCAL | type);
if ((st & __constant_htonl(0xFFC00000)) == __constant_htonl(0xFEC00000))
return (IPV6_ADDR_SITELOCAL | type);
if ((addr->s6_addr32[0] | addr->s6_addr32[1]) == 0) {
if (addr->s6_addr32[2] == 0) {
if (addr->s6_addr32[3] == 0)
if (addr->in6_u.u6_addr32[3] == 0)
return IPV6_ADDR_ANY;
if (addr->s6_addr32[3] == htonl(0x00000001))
return (IPV6_ADDR_LOOPBACK | IPV6_ADDR_UNICAST);
if (addr->s6_addr32[3] == __constant_htonl(0x00000001))
return (IPV6_ADDR_LOOPBACK | type);
return (IPV6_ADDR_COMPATv4 | IPV6_ADDR_UNICAST);
return (IPV6_ADDR_COMPATv4 | type);
}
if (addr->s6_addr32[2] == htonl(0x0000ffff))
if (addr->s6_addr32[2] == __constant_htonl(0x0000ffff))
return IPV6_ADDR_MAPPED;
}
st &= __constant_htonl(0xFF000000);
if (st == 0)
return IPV6_ADDR_RESERVED;
st &= __constant_htonl(0xFE000000);
if (st == __constant_htonl(0x02000000))
return IPV6_ADDR_RESERVED; /* for NSAP */
if (st == __constant_htonl(0x04000000))
return IPV6_ADDR_RESERVED; /* for IPX */
return type;
}
static void addrconf_del_timer(struct inet6_ifaddr *ifp)
......@@ -261,7 +279,6 @@ static void addrconf_mod_timer(struct inet6_ifaddr *ifp,
add_timer(&ifp->timer);
}
/* Nobody refers to this device, we may destroy it. */
void in6_dev_finish_destroy(struct inet6_dev *idev)
......@@ -358,24 +375,91 @@ static struct inet6_dev * ipv6_find_idev(struct net_device *dev)
return idev;
}
void ipv6_addr_prefix(struct in6_addr *prefix,
struct in6_addr *addr, int prefix_len)
{
unsigned long mask;
int ncopy, nbits;
memset(prefix, 0, sizeof(*prefix));
if (prefix_len <= 0)
return;
if (prefix_len > 128)
prefix_len = 128;
ncopy = prefix_len / 32;
switch (ncopy) {
case 4: prefix->s6_addr32[3] = addr->s6_addr32[3];
case 3: prefix->s6_addr32[2] = addr->s6_addr32[2];
case 2: prefix->s6_addr32[1] = addr->s6_addr32[1];
case 1: prefix->s6_addr32[0] = addr->s6_addr32[0];
case 0: break;
}
nbits = prefix_len % 32;
if (nbits == 0)
return;
mask = ~((1 << (32 - nbits)) - 1);
mask = htonl(mask);
prefix->s6_addr32[ncopy] = addr->s6_addr32[ncopy] & mask;
}
static void dev_forward_change(struct inet6_dev *idev)
{
struct net_device *dev;
struct inet6_ifaddr *ifa;
struct in6_addr addr;
if (!idev)
return;
dev = idev->dev;
if (dev && (dev->flags & IFF_MULTICAST)) {
ipv6_addr_all_routers(&addr);
if (idev->cnf.forwarding)
ipv6_dev_mc_inc(dev, &addr);
else
ipv6_dev_mc_dec(dev, &addr);
}
for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) {
ipv6_addr_prefix(&addr, &ifa->addr, ifa->prefix_len);
if (addr.s6_addr32[0] == 0 && addr.s6_addr32[1] == 0 &&
addr.s6_addr32[2] == 0 && addr.s6_addr32[3] == 0)
continue;
if (idev->cnf.forwarding)
ipv6_dev_ac_inc(idev->dev, &addr);
else
ipv6_dev_ac_dec(idev->dev, &addr);
}
}
static void addrconf_forward_change(struct inet6_dev *idev)
{
struct net_device *dev;
if (idev)
if (idev) {
dev_forward_change(idev);
return;
}
read_lock(&dev_base_lock);
for (dev=dev_base; dev; dev=dev->next) {
read_lock(&addrconf_lock);
idev = __in6_dev_get(dev);
if (idev)
if (idev) {
idev->cnf.forwarding = ipv6_devconf.forwarding;
dev_forward_change(idev);
}
read_unlock(&addrconf_lock);
}
read_unlock(&dev_base_lock);
}
/* Nobody refers to this ifaddr, destroy it */
void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
......@@ -658,30 +742,20 @@ static int inline ipv6_saddr_pref(const struct inet6_ifaddr *ifp, u8 invpref)
#define IPV6_GET_SADDR_MAXSCORE(score) (score)
#endif
int ipv6_get_saddr(struct dst_entry *dst,
struct in6_addr *daddr, struct in6_addr *saddr)
int ipv6_dev_get_saddr(struct net_device *dev,
struct in6_addr *daddr, struct in6_addr *saddr, int onlink)
{
int scope;
struct inet6_ifaddr *ifp = NULL;
struct inet6_ifaddr *match = NULL;
struct net_device *dev = NULL;
struct inet6_dev *idev;
struct rt6_info *rt;
int scope;
int err;
int hiscore = -1, score;
rt = (struct rt6_info *) dst;
if (rt)
dev = rt->rt6i_dev;
if (!onlink)
scope = ipv6_addr_scope(daddr);
if (rt && (rt->rt6i_flags & RTF_ALLONLINK)) {
/*
* route for the "all destinations on link" rule
* when no routers are present
*/
else
scope = IFA_LINK;
}
/*
* known dev
......@@ -782,6 +856,24 @@ int ipv6_get_saddr(struct dst_entry *dst,
return err;
}
int ipv6_get_saddr(struct dst_entry *dst,
struct in6_addr *daddr, struct in6_addr *saddr)
{
struct rt6_info *rt;
struct net_device *dev = NULL;
int onlink;
rt = (struct rt6_info *) dst;
if (rt)
dev = rt->rt6i_dev;
onlink = (rt && (rt->rt6i_flags & RTF_ALLONLINK));
return ipv6_dev_get_saddr(dev, daddr, saddr, onlink);
}
int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr)
{
struct inet6_dev *idev;
......@@ -889,7 +981,7 @@ void addrconf_dad_failure(struct inet6_ifaddr *ifp)
/* Join to solicited addr multicast group. */
static void addrconf_join_solict(struct net_device *dev, struct in6_addr *addr)
void addrconf_join_solict(struct net_device *dev, struct in6_addr *addr)
{
struct in6_addr maddr;
......@@ -900,7 +992,7 @@ static void addrconf_join_solict(struct net_device *dev, struct in6_addr *addr)
ipv6_dev_mc_inc(dev, &maddr);
}
static void addrconf_leave_solict(struct net_device *dev, struct in6_addr *addr)
void addrconf_leave_solict(struct net_device *dev, struct in6_addr *addr)
{
struct in6_addr maddr;
......@@ -1937,6 +2029,15 @@ static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
addrconf_mod_timer(ifp, AC_RS, ifp->idev->cnf.rtr_solicit_interval);
spin_unlock_bh(&ifp->lock);
}
if (ifp->idev->cnf.forwarding) {
struct in6_addr addr;
ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
if (addr.s6_addr32[0] || addr.s6_addr32[1] ||
addr.s6_addr32[2] || addr.s6_addr32[3])
ipv6_dev_ac_inc(ifp->idev->dev, &addr);
}
}
#ifdef CONFIG_PROC_FS
......@@ -2267,6 +2368,14 @@ static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
break;
case RTM_DELADDR:
addrconf_leave_solict(ifp->idev->dev, &ifp->addr);
if (ifp->idev->cnf.forwarding) {
struct in6_addr addr;
ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
if (addr.s6_addr32[0] || addr.s6_addr32[1] ||
addr.s6_addr32[2] || addr.s6_addr32[3])
ipv6_dev_ac_dec(ifp->idev->dev, &addr);
}
if (!ipv6_chk_addr(&ifp->addr, NULL))
ip6_rt_addr_del(&ifp->addr, ifp->idev->dev);
break;
......@@ -2289,11 +2398,7 @@ int addrconf_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
struct inet6_dev *idev = NULL;
if (valp != &ipv6_devconf.forwarding) {
struct net_device *dev = dev_get_by_index(ctl->ctl_name);
if (dev) {
idev = in6_dev_get(dev);
dev_put(dev);
}
idev = (struct inet6_dev *)ctl->extra1;
if (idev == NULL)
return ret;
} else
......@@ -2303,8 +2408,6 @@ int addrconf_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
if (*valp)
rt6_purge_dflt_routers(0);
if (idev)
in6_dev_put(idev);
}
return ret;
......@@ -2491,6 +2594,7 @@ static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf
for (i=0; t->addrconf_vars[i].data; i++) {
t->addrconf_vars[i].data += (char*)p - (char*)&ipv6_devconf;
t->addrconf_vars[i].de = NULL;
t->addrconf_vars[i].extra1 = idev; /* embedded; no ref */
}
if (dev) {
t->addrconf_dev[0].procname = dev->name;
......
......@@ -74,6 +74,7 @@ MODULE_PARM(unloadable, "i");
/* IPv6 procfs goodies... */
#ifdef CONFIG_PROC_FS
extern int anycast6_get_info(char *, char **, off_t, int);
extern int raw6_get_info(char *, char **, off_t, int);
extern int tcp6_get_info(char *, char **, off_t, int);
extern int udp6_get_info(char *, char **, off_t, int);
......@@ -381,6 +382,9 @@ int inet6_release(struct socket *sock)
/* Free mc lists */
ipv6_sock_mc_close(sk);
/* Free ac lists */
ipv6_sock_ac_close(sk);
return inet_release(sock);
}
......@@ -785,6 +789,8 @@ static int __init inet6_init(void)
goto proc_sockstat6_fail;
if (!proc_net_create("snmp6", 0, afinet6_get_snmp))
goto proc_snmp6_fail;
if (!proc_net_create("anycast6", 0, anycast6_get_info))
goto proc_anycast6_fail;
#endif
ipv6_netdev_notif_init();
ipv6_packet_init();
......@@ -800,6 +806,8 @@ static int __init inet6_init(void)
return 0;
#ifdef CONFIG_PROC_FS
proc_anycast6_fail:
proc_net_remove("anycast6");
proc_snmp6_fail:
proc_net_remove("sockstat6");
proc_sockstat6_fail:
......@@ -837,6 +845,7 @@ static void inet6_exit(void)
proc_net_remove("udp6");
proc_net_remove("sockstat6");
proc_net_remove("snmp6");
proc_net_remove("anycast6");
#endif
/* Cleanup code parts. */
sit_cleanup();
......
/*
* Anycast support for IPv6
* Linux INET6 implementation
*
* Authors:
* David L Stevens (dlstevens@us.ibm.com)
*
* based heavily on net/ipv6/mcast.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/random.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/route.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <net/sock.h>
#include <net/snmp.h>
#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/if_inet6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/checksum.h>
/* Big ac list lock for all the sockets */
static rwlock_t ipv6_sk_ac_lock = RW_LOCK_UNLOCKED;
/* XXX ip6_addr_match() and ip6_onlink() really belong in net/core.c */
static int
ip6_addr_match(struct in6_addr *addr1, struct in6_addr *addr2, int prefix)
{
__u32 mask;
int i;
if (prefix > 128 || prefix < 0)
return 0;
if (prefix == 0)
return 1;
for (i=0; i<4; ++i) {
if (prefix >= 32)
mask = ~0;
else
mask = htonl(~0 << (32 - prefix));
if ((addr1->s6_addr32[i] ^ addr2->s6_addr32[i]) & mask)
return 0;
prefix -= 32;
if (prefix <= 0)
break;
}
return 1;
}
static int
ip6_onlink(struct in6_addr *addr, struct net_device *dev)
{
struct inet6_dev *idev;
struct inet6_ifaddr *ifa;
int onlink;
onlink = 0;
read_lock(&addrconf_lock);
idev = __in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) {
onlink = ip6_addr_match(addr, &ifa->addr,
ifa->prefix_len);
if (onlink)
break;
}
read_unlock_bh(&idev->lock);
}
read_unlock(&addrconf_lock);
return onlink;
}
/*
* socket join an anycast group
*/
int ipv6_sock_ac_join(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev = NULL;
struct inet6_dev *idev;
struct ipv6_ac_socklist *pac;
int ishost = !ipv6_devconf.forwarding;
int err = 0;
if (ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST)
return -EINVAL;
pac = sock_kmalloc(sk, sizeof(struct ipv6_ac_socklist), GFP_KERNEL);
if (pac == NULL)
return -ENOMEM;
pac->acl_next = NULL;
ipv6_addr_copy(&pac->acl_addr, addr);
if (ifindex == 0) {
struct rt6_info *rt;
rt = rt6_lookup(addr, NULL, 0, 0);
if (rt) {
dev = rt->rt6i_dev;
dev_hold(dev);
dst_release(&rt->u.dst);
} else if (ishost) {
sock_kfree_s(sk, pac, sizeof(*pac));
return -EADDRNOTAVAIL;
} else {
/* router, no matching interface: just pick one */
dev = dev_get_by_flags(IFF_UP, IFF_UP|IFF_LOOPBACK);
}
} else
dev = dev_get_by_index(ifindex);
if (dev == NULL) {
sock_kfree_s(sk, pac, sizeof(*pac));
return -ENODEV;
}
idev = in6_dev_get(dev);
if (!idev) {
sock_kfree_s(sk, pac, sizeof(*pac));
dev_put(dev);
if (ifindex)
return -ENODEV;
else
return -EADDRNOTAVAIL;
}
/* reset ishost, now that we have a specific device */
ishost = !idev->cnf.forwarding;
in6_dev_put(idev);
pac->acl_ifindex = dev->ifindex;
/* XXX
* For hosts, allow link-local or matching prefix anycasts.
* This obviates the need for propagating anycast routes while
* still allowing some non-router anycast participation.
*
* allow anyone to join anycasts that don't require a special route
* and can't be spoofs of unicast addresses (reserved anycast only)
*/
if (!ip6_onlink(addr, dev)) {
if (ishost)
err = -EADDRNOTAVAIL;
else if (!capable(CAP_NET_ADMIN))
err = -EPERM;
if (err) {
sock_kfree_s(sk, pac, sizeof(*pac));
dev_put(dev);
return err;
}
} else if (!(ipv6_addr_type(addr) & IPV6_ADDR_ANYCAST) &&
!capable(CAP_NET_ADMIN))
return -EPERM;
err = ipv6_dev_ac_inc(dev, addr);
if (err) {
sock_kfree_s(sk, pac, sizeof(*pac));
dev_put(dev);
return err;
}
write_lock_bh(&ipv6_sk_ac_lock);
pac->acl_next = np->ipv6_ac_list;
np->ipv6_ac_list = pac;
write_unlock_bh(&ipv6_sk_ac_lock);
dev_put(dev);
return 0;
}
/*
* socket leave an anycast group
*/
int ipv6_sock_ac_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev;
struct ipv6_ac_socklist *pac, *prev_pac;
write_lock_bh(&ipv6_sk_ac_lock);
prev_pac = 0;
for (pac = np->ipv6_ac_list; pac; pac = pac->acl_next) {
if ((ifindex == 0 || pac->acl_ifindex == ifindex) &&
ipv6_addr_cmp(&pac->acl_addr, addr) == 0)
break;
prev_pac = pac;
}
if (!pac) {
write_unlock_bh(&ipv6_sk_ac_lock);
return -ENOENT;
}
if (prev_pac)
prev_pac->acl_next = pac->acl_next;
else
np->ipv6_ac_list = pac->acl_next;
write_unlock_bh(&ipv6_sk_ac_lock);
dev = dev_get_by_index(pac->acl_ifindex);
if (dev) {
ipv6_dev_ac_dec(dev, &pac->acl_addr);
dev_put(dev);
}
sock_kfree_s(sk, pac, sizeof(*pac));
return 0;
}
void ipv6_sock_ac_close(struct sock *sk)
{
struct ipv6_pinfo *np = inet6_sk(sk);
struct net_device *dev = 0;
struct ipv6_ac_socklist *pac;
int prev_index;
write_lock_bh(&ipv6_sk_ac_lock);
pac = np->ipv6_ac_list;
np->ipv6_ac_list = 0;
write_unlock_bh(&ipv6_sk_ac_lock);
prev_index = 0;
while (pac) {
struct ipv6_ac_socklist *next = pac->acl_next;
if (pac->acl_ifindex != prev_index) {
if (dev)
dev_put(dev);
dev = dev_get_by_index(pac->acl_ifindex);
prev_index = pac->acl_ifindex;
}
if (dev)
ipv6_dev_ac_dec(dev, &pac->acl_addr);
sock_kfree_s(sk, pac, sizeof(*pac));
pac = next;
}
if (dev)
dev_put(dev);
}
int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex)
{
struct ipv6_ac_socklist *pac;
struct ipv6_pinfo *np = inet6_sk(sk);
int found;
found = 0;
read_lock(&ipv6_sk_ac_lock);
for (pac=np->ipv6_ac_list; pac; pac=pac->acl_next) {
if (ifindex && pac->acl_ifindex != ifindex)
continue;
found = ipv6_addr_cmp(&pac->acl_addr, addr) == 0;
if (found)
break;
}
read_unlock(&ipv6_sk_ac_lock);
return found;
}
static void aca_put(struct ifacaddr6 *ac)
{
if (atomic_dec_and_test(&ac->aca_refcnt)) {
in6_dev_put(ac->aca_idev);
kfree(ac);
}
}
/*
* device anycast group inc (add if not found)
*/
int ipv6_dev_ac_inc(struct net_device *dev, struct in6_addr *addr)
{
struct ifacaddr6 *aca;
struct inet6_dev *idev;
idev = in6_dev_get(dev);
if (idev == NULL)
return -EINVAL;
write_lock_bh(&idev->lock);
if (idev->dead) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return -ENODEV;
}
for (aca = idev->ac_list; aca; aca = aca->aca_next) {
if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0) {
aca->aca_users++;
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return 0;
}
}
/*
* not found: create a new one.
*/
aca = kmalloc(sizeof(struct ifacaddr6), GFP_ATOMIC);
if (aca == NULL) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return -ENOMEM;
}
memset(aca, 0, sizeof(struct ifacaddr6));
ipv6_addr_copy(&aca->aca_addr, addr);
aca->aca_idev = idev;
aca->aca_users = 1;
atomic_set(&aca->aca_refcnt, 2);
aca->aca_lock = SPIN_LOCK_UNLOCKED;
aca->aca_next = idev->ac_list;
idev->ac_list = aca;
write_unlock_bh(&idev->lock);
ip6_rt_addr_add(&aca->aca_addr, dev);
addrconf_join_solict(dev, &aca->aca_addr);
aca_put(aca);
return 0;
}
/*
* device anycast group decrement
*/
int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr)
{
struct inet6_dev *idev;
struct ifacaddr6 *aca, *prev_aca;
idev = in6_dev_get(dev);
if (idev == NULL)
return -ENODEV;
write_lock_bh(&idev->lock);
prev_aca = 0;
for (aca = idev->ac_list; aca; aca = aca->aca_next) {
if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0)
break;
prev_aca = aca;
}
if (!aca) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return -ENOENT;
}
if (--aca->aca_users > 0) {
write_unlock_bh(&idev->lock);
in6_dev_put(idev);
return 0;
}
if (prev_aca)
prev_aca->aca_next = aca->aca_next;
else
idev->ac_list = aca->aca_next;
write_unlock_bh(&idev->lock);
addrconf_leave_solict(dev, &aca->aca_addr);
ip6_rt_addr_del(&aca->aca_addr, dev);
aca_put(aca);
in6_dev_put(idev);
return 0;
}
/*
* check if the interface has this anycast address
*/
static int ipv6_chk_acast_dev(struct net_device *dev, struct in6_addr *addr)
{
struct inet6_dev *idev;
struct ifacaddr6 *aca;
idev = in6_dev_get(dev);
if (idev) {
read_lock_bh(&idev->lock);
for (aca = idev->ac_list; aca; aca = aca->aca_next)
if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0)
break;
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
return aca != 0;
}
return 0;
}
/*
* check if given interface (or any, if dev==0) has this anycast address
*/
int ipv6_chk_acast_addr(struct net_device *dev, struct in6_addr *addr)
{
if (dev)
return ipv6_chk_acast_dev(dev, addr);
read_lock(&dev_base_lock);
for (dev=dev_base; dev; dev=dev->next)
if (ipv6_chk_acast_dev(dev, addr))
break;
read_unlock(&dev_base_lock);
return dev != 0;
}
#ifdef CONFIG_PROC_FS
int anycast6_get_info(char *buffer, char **start, off_t offset, int length)
{
off_t pos=0, begin=0;
struct ifacaddr6 *im;
int len=0;
struct net_device *dev;
read_lock(&dev_base_lock);
for (dev = dev_base; dev; dev = dev->next) {
struct inet6_dev *idev;
if ((idev = in6_dev_get(dev)) == NULL)
continue;
read_lock_bh(&idev->lock);
for (im = idev->ac_list; im; im = im->aca_next) {
int i;
len += sprintf(buffer+len,"%-4d %-15s ", dev->ifindex, dev->name);
for (i=0; i<16; i++)
len += sprintf(buffer+len, "%02x", im->aca_addr.s6_addr[i]);
len += sprintf(buffer+len, " %5d\n", im->aca_users);
pos=begin+len;
if (pos < offset) {
len=0;
begin=pos;
}
if (pos > offset+length) {
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
goto done;
}
}
read_unlock_bh(&idev->lock);
in6_dev_put(idev);
}
done:
read_unlock(&dev_base_lock);
*start=buffer+(offset-begin);
len-=(offset-begin);
if(len>length)
len=length;
if (len<0)
len=0;
return len;
}
#endif
......@@ -369,7 +369,8 @@ static void icmpv6_echo_reply(struct sk_buff *skb)
saddr = &skb->nh.ipv6h->daddr;
if (ipv6_addr_type(saddr) & IPV6_ADDR_MULTICAST)
if (ipv6_addr_type(saddr) & IPV6_ADDR_MULTICAST ||
ipv6_chk_acast_addr(0, saddr))
saddr = NULL;
msg.icmph.icmp6_type = ICMPV6_ECHO_REPLY;
......
......@@ -358,6 +358,24 @@ int ipv6_setsockopt(struct sock *sk, int level, int optname, char *optval,
retv = ipv6_sock_mc_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_multiaddr);
break;
}
case IPV6_JOIN_ANYCAST:
case IPV6_LEAVE_ANYCAST:
{
struct ipv6_mreq mreq;
if (optlen != sizeof(struct ipv6_mreq))
goto e_inval;
retv = -EFAULT;
if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
break;
if (optname == IPV6_JOIN_ANYCAST)
retv = ipv6_sock_ac_join(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
else
retv = ipv6_sock_ac_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
break;
}
case IPV6_ROUTER_ALERT:
retv = ip6_ra_control(sk, val, NULL);
break;
......
......@@ -413,10 +413,13 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
struct in6_addr *daddr, struct in6_addr *solicited_addr,
int router, int solicited, int override, int inc_opt)
{
static struct in6_addr tmpaddr;
struct inet6_ifaddr *ifp;
struct flowi fl;
struct rt6_info *rt = NULL;
struct dst_entry* dst;
struct sock *sk = ndisc_socket->sk;
struct in6_addr *src_addr;
struct nd_msg *msg;
int len;
struct sk_buff *skb;
......@@ -428,7 +431,18 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
if (!rt)
return;
ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, solicited_addr, daddr);
/* for anycast or proxy, solicited_addr != src_addr */
ifp = ipv6_get_ifaddr(solicited_addr, dev);
if (ifp) {
src_addr = solicited_addr;
in6_ifa_put(ifp);
} else {
if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr, 0))
return;
src_addr = &tmpaddr;
}
ndisc_flow_init(&fl, NDISC_NEIGHBOUR_ADVERTISEMENT, src_addr, daddr);
ndisc_rt_init(rt, dev, neigh);
dst = (struct dst_entry*)rt;
......@@ -456,7 +470,7 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
}
skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
ip6_nd_hdr(sk, skb, dev, solicited_addr, daddr, IPPROTO_ICMPV6, len);
ip6_nd_hdr(sk, skb, dev, src_addr, daddr, IPPROTO_ICMPV6, len);
skb->h.raw = (unsigned char*) msg = (struct nd_msg *) skb_put(skb, len);
......@@ -470,13 +484,13 @@ static void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
msg->icmph.icmp6_override = !!override;
/* Set the target address. */
ipv6_addr_copy(&msg->target, solicited_addr);
ipv6_addr_copy(&msg->target, src_addr);
if (inc_opt)
ndisc_fill_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len);
/* checksum */
msg->icmph.icmp6_cksum = csum_ipv6_magic(solicited_addr, daddr, len,
msg->icmph.icmp6_cksum = csum_ipv6_magic(src_addr, daddr, len,
IPPROTO_ICMPV6,
csum_partial((__u8 *) msg,
len, 0));
......@@ -793,6 +807,50 @@ void ndisc_recv_ns(struct sk_buff *skb)
}
}
in6_ifa_put(ifp);
} else if (ipv6_chk_acast_addr(dev, &msg->target)) {
struct inet6_dev *idev = in6_dev_get(dev);
int addr_type = ipv6_addr_type(saddr);
/* anycast */
if (!idev) {
/* XXX: count this drop? */
return 0;
}
if (addr_type == IPV6_ADDR_ANY) {
struct in6_addr maddr;
ipv6_addr_all_nodes(&maddr);
ndisc_send_na(dev, NULL, &maddr, &msg->target,
idev->cnf.forwarding, 0, 0, 1);
in6_dev_put(idev);
return 0;
}
if (addr_type & IPV6_ADDR_UNICAST) {
int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
if (inc)
nd_tbl.stats.rcv_probes_mcast++;
else
nd_tbl.stats.rcv_probes_ucast++;
/*
* update / create cache entry
* for the source adddress
*/
neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, skb->dev);
if (neigh || !dev->hard_header) {
ndisc_send_na(dev, neigh, saddr,
&msg->target,
idev->cnf.forwarding, 1, 0, inc);
if (neigh)
neigh_release(neigh);
}
}
in6_dev_put(idev);
} else {
struct inet6_dev *in6_dev = in6_dev_get(dev);
int addr_type = ipv6_addr_type(saddr);
......
......@@ -547,6 +547,8 @@ EXPORT_SYMBOL(register_netdevice);
EXPORT_SYMBOL(unregister_netdevice);
EXPORT_SYMBOL(netdev_state_change);
EXPORT_SYMBOL(dev_new_index);
EXPORT_SYMBOL(dev_get_by_flags);
EXPORT_SYMBOL(__dev_get_by_flags);
EXPORT_SYMBOL(dev_get_by_index);
EXPORT_SYMBOL(__dev_get_by_index);
EXPORT_SYMBOL(dev_get_by_name);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment