Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

43
net/decnet/Kconfig Normal file
View file

@ -0,0 +1,43 @@
#
# DECnet configuration
#
config DECNET
tristate "DECnet Support"
---help---
The DECnet networking protocol was used in many products made by
Digital (now Compaq). It provides reliable stream and sequenced
packet communications over which run a variety of services similar
to those which run over TCP/IP.
To find some tools to use with the kernel layer support, please
look at Patrick Caulfield's web site:
<http://linux-decnet.sourceforge.net/>.
More detailed documentation is available in
<file:Documentation/networking/decnet.txt>.
Be sure to say Y to "/proc file system support" and "Sysctl support"
below when using DECnet, since you will need sysctl support to aid
in configuration at run time.
The DECnet code is also available as a module ( = code which can be
inserted in and removed from the running kernel whenever you want).
The module is called decnet.
config DECNET_ROUTER
bool "DECnet: router support"
depends on DECNET
select FIB_RULES
---help---
Add support for turning your DECnet Endnode into a level 1 or 2
router. This is an experimental, but functional option. If you
do say Y here, then make sure that you also say Y to "Kernel/User
network link driver", "Routing messages" and "Network packet
filtering". The first two are required to allow configuration via
rtnetlink (you will need Alexey Kuznetsov's iproute2 package
from <ftp://ftp.tux.org/pub/net/ip-routing/>). The "Network packet
filtering" option will be required for the forthcoming routing daemon
to work.
See <file:Documentation/networking/decnet.txt> for more information.

10
net/decnet/Makefile Normal file
View file

@ -0,0 +1,10 @@
obj-$(CONFIG_DECNET) += decnet.o
decnet-y := af_decnet.o dn_nsp_in.o dn_nsp_out.o \
dn_route.o dn_dev.o dn_neigh.o dn_timer.o
decnet-$(CONFIG_DECNET_ROUTER) += dn_fib.o dn_rules.o dn_table.o
decnet-y += sysctl_net_decnet.o
obj-$(CONFIG_NETFILTER) += netfilter/

8
net/decnet/README Normal file
View file

@ -0,0 +1,8 @@
Linux DECnet Project
======================
The documentation for this kernel subsystem is available in the
Documentation/networking subdirectory of this distribution and also
on line at http://www.chygwyn.com/DECnet/
Steve Whitehouse <SteveW@ACM.org>

41
net/decnet/TODO Normal file
View file

@ -0,0 +1,41 @@
Steve's quick list of things that need finishing off:
[they are in no particular order and range from the trivial to the long winded]
o Proper timeouts on each neighbour (in routing mode) rather than
just the 60 second On-Ethernet cache value.
o Support for X.25 linklayer
o Support for DDCMP link layer
o The DDCMP device itself
o PPP support (rfc1762)
o Lots of testing with real applications
o Verify errors etc. against POSIX 1003.1g (draft)
o Using send/recvmsg() to get at connect/disconnect data (POSIX 1003.1g)
[maybe this should be done at socket level... the control data in the
send/recvmsg() calls should simply be a vector of set/getsockopt()
calls]
o check MSG_CTRUNC is set where it should be.
o Find all the commonality between DECnet and IPv4 routing code and extract
it into a small library of routines. [probably a project for 2.7.xx]
o Add perfect socket hashing - an idea suggested by Paul Koning. Currently
we have a half-way house scheme which seems to work reasonably well, but
the full scheme is still worth implementing, its not not top of my list
right now.
o Add session control message flow control
o Add NSP message flow control
o DECnet sendpages() function
o AIO for DECnet

2420
net/decnet/af_decnet.c Normal file

File diff suppressed because it is too large Load diff

1445
net/decnet/dn_dev.c Normal file

File diff suppressed because it is too large Load diff

790
net/decnet/dn_fib.c Normal file
View file

@ -0,0 +1,790 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Forwarding Information Base (Glue/Info List)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
*
*
* Changes:
* Alexey Kuznetsov : SMP locking changes
* Steve Whitehouse : Rewrote it... Well to be more correct, I
* copied most of it from the ipv4 fib code.
* Steve Whitehouse : Updated it in style and fixed a few bugs
* which were fixed in the ipv4 code since
* this code was copied from it.
*
*/
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/slab.h>
#include <linux/sockios.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <asm/uaccess.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/fib_rules.h>
#include <net/dn.h>
#include <net/dn_route.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
#define RT_MIN_TABLE 1
#define for_fib_info() { struct dn_fib_info *fi;\
for(fi = dn_fib_info_list; fi; fi = fi->fib_next)
#define endfor_fib_info() }
#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define endfor_nexthops(fi) }
static DEFINE_SPINLOCK(dn_fib_multipath_lock);
static struct dn_fib_info *dn_fib_info_list;
static DEFINE_SPINLOCK(dn_fib_info_lock);
static struct
{
int error;
u8 scope;
} dn_fib_props[RTN_MAX+1] = {
[RTN_UNSPEC] = { .error = 0, .scope = RT_SCOPE_NOWHERE },
[RTN_UNICAST] = { .error = 0, .scope = RT_SCOPE_UNIVERSE },
[RTN_LOCAL] = { .error = 0, .scope = RT_SCOPE_HOST },
[RTN_BROADCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_ANYCAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_MULTICAST] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
[RTN_BLACKHOLE] = { .error = -EINVAL, .scope = RT_SCOPE_UNIVERSE },
[RTN_UNREACHABLE] = { .error = -EHOSTUNREACH, .scope = RT_SCOPE_UNIVERSE },
[RTN_PROHIBIT] = { .error = -EACCES, .scope = RT_SCOPE_UNIVERSE },
[RTN_THROW] = { .error = -EAGAIN, .scope = RT_SCOPE_UNIVERSE },
[RTN_NAT] = { .error = 0, .scope = RT_SCOPE_NOWHERE },
[RTN_XRESOLVE] = { .error = -EINVAL, .scope = RT_SCOPE_NOWHERE },
};
static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force);
static int dn_fib_sync_up(struct net_device *dev);
void dn_fib_free_info(struct dn_fib_info *fi)
{
if (fi->fib_dead == 0) {
printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n");
return;
}
change_nexthops(fi) {
if (nh->nh_dev)
dev_put(nh->nh_dev);
nh->nh_dev = NULL;
} endfor_nexthops(fi);
kfree(fi);
}
void dn_fib_release_info(struct dn_fib_info *fi)
{
spin_lock(&dn_fib_info_lock);
if (fi && --fi->fib_treeref == 0) {
if (fi->fib_next)
fi->fib_next->fib_prev = fi->fib_prev;
if (fi->fib_prev)
fi->fib_prev->fib_next = fi->fib_next;
if (fi == dn_fib_info_list)
dn_fib_info_list = fi->fib_next;
fi->fib_dead = 1;
dn_fib_info_put(fi);
}
spin_unlock(&dn_fib_info_lock);
}
static inline int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi)
{
const struct dn_fib_nh *onh = ofi->fib_nh;
for_nexthops(fi) {
if (nh->nh_oif != onh->nh_oif ||
nh->nh_gw != onh->nh_gw ||
nh->nh_scope != onh->nh_scope ||
nh->nh_weight != onh->nh_weight ||
((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
return -1;
onh++;
} endfor_nexthops(fi);
return 0;
}
static inline struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi)
{
for_fib_info() {
if (fi->fib_nhs != nfi->fib_nhs)
continue;
if (nfi->fib_protocol == fi->fib_protocol &&
nfi->fib_prefsrc == fi->fib_prefsrc &&
nfi->fib_priority == fi->fib_priority &&
memcmp(nfi->fib_metrics, fi->fib_metrics, sizeof(fi->fib_metrics)) == 0 &&
((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
(nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0))
return fi;
} endfor_fib_info();
return NULL;
}
static int dn_fib_count_nhs(const struct nlattr *attr)
{
struct rtnexthop *nhp = nla_data(attr);
int nhs = 0, nhlen = nla_len(attr);
while(nhlen >= (int)sizeof(struct rtnexthop)) {
if ((nhlen -= nhp->rtnh_len) < 0)
return 0;
nhs++;
nhp = RTNH_NEXT(nhp);
}
return nhs;
}
static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct nlattr *attr,
const struct rtmsg *r)
{
struct rtnexthop *nhp = nla_data(attr);
int nhlen = nla_len(attr);
change_nexthops(fi) {
int attrlen = nhlen - sizeof(struct rtnexthop);
if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
return -EINVAL;
nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
nh->nh_oif = nhp->rtnh_ifindex;
nh->nh_weight = nhp->rtnh_hops + 1;
if (attrlen) {
struct nlattr *gw_attr;
gw_attr = nla_find((struct nlattr *) (nhp + 1), attrlen, RTA_GATEWAY);
nh->nh_gw = gw_attr ? nla_get_le16(gw_attr) : 0;
}
nhp = RTNH_NEXT(nhp);
} endfor_nexthops(fi);
return 0;
}
static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh)
{
int err;
if (nh->nh_gw) {
struct flowidn fld;
struct dn_fib_res res;
if (nh->nh_flags&RTNH_F_ONLINK) {
struct net_device *dev;
if (r->rtm_scope >= RT_SCOPE_LINK)
return -EINVAL;
if (dnet_addr_type(nh->nh_gw) != RTN_UNICAST)
return -EINVAL;
if ((dev = __dev_get_by_index(&init_net, nh->nh_oif)) == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
dev_hold(dev);
nh->nh_scope = RT_SCOPE_LINK;
return 0;
}
memset(&fld, 0, sizeof(fld));
fld.daddr = nh->nh_gw;
fld.flowidn_oif = nh->nh_oif;
fld.flowidn_scope = r->rtm_scope + 1;
if (fld.flowidn_scope < RT_SCOPE_LINK)
fld.flowidn_scope = RT_SCOPE_LINK;
if ((err = dn_fib_lookup(&fld, &res)) != 0)
return err;
err = -EINVAL;
if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)
goto out;
nh->nh_scope = res.scope;
nh->nh_oif = DN_FIB_RES_OIF(res);
nh->nh_dev = DN_FIB_RES_DEV(res);
if (nh->nh_dev == NULL)
goto out;
dev_hold(nh->nh_dev);
err = -ENETDOWN;
if (!(nh->nh_dev->flags & IFF_UP))
goto out;
err = 0;
out:
dn_fib_res_put(&res);
return err;
} else {
struct net_device *dev;
if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
return -EINVAL;
dev = __dev_get_by_index(&init_net, nh->nh_oif);
if (dev == NULL || dev->dn_ptr == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
dev_hold(nh->nh_dev);
nh->nh_scope = RT_SCOPE_HOST;
}
return 0;
}
struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct nlattr *attrs[],
const struct nlmsghdr *nlh, int *errp)
{
int err;
struct dn_fib_info *fi = NULL;
struct dn_fib_info *ofi;
int nhs = 1;
if (r->rtm_type > RTN_MAX)
goto err_inval;
if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
goto err_inval;
if (attrs[RTA_MULTIPATH] &&
(nhs = dn_fib_count_nhs(attrs[RTA_MULTIPATH])) == 0)
goto err_inval;
fi = kzalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL);
err = -ENOBUFS;
if (fi == NULL)
goto failure;
fi->fib_protocol = r->rtm_protocol;
fi->fib_nhs = nhs;
fi->fib_flags = r->rtm_flags;
if (attrs[RTA_PRIORITY])
fi->fib_priority = nla_get_u32(attrs[RTA_PRIORITY]);
if (attrs[RTA_METRICS]) {
struct nlattr *attr;
int rem;
nla_for_each_nested(attr, attrs[RTA_METRICS], rem) {
int type = nla_type(attr);
if (type) {
if (type > RTAX_MAX || nla_len(attr) < 4)
goto err_inval;
fi->fib_metrics[type-1] = nla_get_u32(attr);
}
}
}
if (attrs[RTA_PREFSRC])
fi->fib_prefsrc = nla_get_le16(attrs[RTA_PREFSRC]);
if (attrs[RTA_MULTIPATH]) {
if ((err = dn_fib_get_nhs(fi, attrs[RTA_MULTIPATH], r)) != 0)
goto failure;
if (attrs[RTA_OIF] &&
fi->fib_nh->nh_oif != nla_get_u32(attrs[RTA_OIF]))
goto err_inval;
if (attrs[RTA_GATEWAY] &&
fi->fib_nh->nh_gw != nla_get_le16(attrs[RTA_GATEWAY]))
goto err_inval;
} else {
struct dn_fib_nh *nh = fi->fib_nh;
if (attrs[RTA_OIF])
nh->nh_oif = nla_get_u32(attrs[RTA_OIF]);
if (attrs[RTA_GATEWAY])
nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]);
nh->nh_flags = r->rtm_flags;
nh->nh_weight = 1;
}
if (r->rtm_type == RTN_NAT) {
if (!attrs[RTA_GATEWAY] || nhs != 1 || attrs[RTA_OIF])
goto err_inval;
fi->fib_nh->nh_gw = nla_get_le16(attrs[RTA_GATEWAY]);
goto link_it;
}
if (dn_fib_props[r->rtm_type].error) {
if (attrs[RTA_GATEWAY] || attrs[RTA_OIF] || attrs[RTA_MULTIPATH])
goto err_inval;
goto link_it;
}
if (r->rtm_scope > RT_SCOPE_HOST)
goto err_inval;
if (r->rtm_scope == RT_SCOPE_HOST) {
struct dn_fib_nh *nh = fi->fib_nh;
/* Local address is added */
if (nhs != 1 || nh->nh_gw)
goto err_inval;
nh->nh_scope = RT_SCOPE_NOWHERE;
nh->nh_dev = dev_get_by_index(&init_net, fi->fib_nh->nh_oif);
err = -ENODEV;
if (nh->nh_dev == NULL)
goto failure;
} else {
change_nexthops(fi) {
if ((err = dn_fib_check_nh(r, fi, nh)) != 0)
goto failure;
} endfor_nexthops(fi)
}
if (fi->fib_prefsrc) {
if (r->rtm_type != RTN_LOCAL || !attrs[RTA_DST] ||
fi->fib_prefsrc != nla_get_le16(attrs[RTA_DST]))
if (dnet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
goto err_inval;
}
link_it:
if ((ofi = dn_fib_find_info(fi)) != NULL) {
fi->fib_dead = 1;
dn_fib_free_info(fi);
ofi->fib_treeref++;
return ofi;
}
fi->fib_treeref++;
atomic_inc(&fi->fib_clntref);
spin_lock(&dn_fib_info_lock);
fi->fib_next = dn_fib_info_list;
fi->fib_prev = NULL;
if (dn_fib_info_list)
dn_fib_info_list->fib_prev = fi;
dn_fib_info_list = fi;
spin_unlock(&dn_fib_info_lock);
return fi;
err_inval:
err = -EINVAL;
failure:
*errp = err;
if (fi) {
fi->fib_dead = 1;
dn_fib_free_info(fi);
}
return NULL;
}
int dn_fib_semantic_match(int type, struct dn_fib_info *fi, const struct flowidn *fld, struct dn_fib_res *res)
{
int err = dn_fib_props[type].error;
if (err == 0) {
if (fi->fib_flags & RTNH_F_DEAD)
return 1;
res->fi = fi;
switch (type) {
case RTN_NAT:
DN_FIB_RES_RESET(*res);
atomic_inc(&fi->fib_clntref);
return 0;
case RTN_UNICAST:
case RTN_LOCAL:
for_nexthops(fi) {
if (nh->nh_flags & RTNH_F_DEAD)
continue;
if (!fld->flowidn_oif ||
fld->flowidn_oif == nh->nh_oif)
break;
}
if (nhsel < fi->fib_nhs) {
res->nh_sel = nhsel;
atomic_inc(&fi->fib_clntref);
return 0;
}
endfor_nexthops(fi);
res->fi = NULL;
return 1;
default:
net_err_ratelimited("DECnet: impossible routing event : dn_fib_semantic_match type=%d\n",
type);
res->fi = NULL;
return -EINVAL;
}
}
return err;
}
void dn_fib_select_multipath(const struct flowidn *fld, struct dn_fib_res *res)
{
struct dn_fib_info *fi = res->fi;
int w;
spin_lock_bh(&dn_fib_multipath_lock);
if (fi->fib_power <= 0) {
int power = 0;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
power += nh->nh_weight;
nh->nh_power = nh->nh_weight;
}
} endfor_nexthops(fi);
fi->fib_power = power;
if (power < 0) {
spin_unlock_bh(&dn_fib_multipath_lock);
res->nh_sel = 0;
return;
}
}
w = jiffies % fi->fib_power;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
if ((w -= nh->nh_power) <= 0) {
nh->nh_power--;
fi->fib_power--;
res->nh_sel = nhsel;
spin_unlock_bh(&dn_fib_multipath_lock);
return;
}
}
} endfor_nexthops(fi);
res->nh_sel = 0;
spin_unlock_bh(&dn_fib_multipath_lock);
}
static inline u32 rtm_get_table(struct nlattr *attrs[], u8 table)
{
if (attrs[RTA_TABLE])
table = nla_get_u32(attrs[RTA_TABLE]);
return table;
}
static int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct net *net = sock_net(skb->sk);
struct dn_fib_table *tb;
struct rtmsg *r = nlmsg_data(nlh);
struct nlattr *attrs[RTA_MAX+1];
int err;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!net_eq(net, &init_net))
return -EINVAL;
err = nlmsg_parse(nlh, sizeof(*r), attrs, RTA_MAX, rtm_dn_policy);
if (err < 0)
return err;
tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 0);
if (!tb)
return -ESRCH;
return tb->delete(tb, r, attrs, nlh, &NETLINK_CB(skb));
}
static int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct net *net = sock_net(skb->sk);
struct dn_fib_table *tb;
struct rtmsg *r = nlmsg_data(nlh);
struct nlattr *attrs[RTA_MAX+1];
int err;
if (!netlink_capable(skb, CAP_NET_ADMIN))
return -EPERM;
if (!net_eq(net, &init_net))
return -EINVAL;
err = nlmsg_parse(nlh, sizeof(*r), attrs, RTA_MAX, rtm_dn_policy);
if (err < 0)
return err;
tb = dn_fib_get_table(rtm_get_table(attrs, r->rtm_table), 1);
if (!tb)
return -ENOBUFS;
return tb->insert(tb, r, attrs, nlh, &NETLINK_CB(skb));
}
static void fib_magic(int cmd, int type, __le16 dst, int dst_len, struct dn_ifaddr *ifa)
{
struct dn_fib_table *tb;
struct {
struct nlmsghdr nlh;
struct rtmsg rtm;
} req;
struct {
struct nlattr hdr;
__le16 dst;
} dst_attr = {
.dst = dst,
};
struct {
struct nlattr hdr;
__le16 prefsrc;
} prefsrc_attr = {
.prefsrc = ifa->ifa_local,
};
struct {
struct nlattr hdr;
u32 oif;
} oif_attr = {
.oif = ifa->ifa_dev->dev->ifindex,
};
struct nlattr *attrs[RTA_MAX+1] = {
[RTA_DST] = (struct nlattr *) &dst_attr,
[RTA_PREFSRC] = (struct nlattr * ) &prefsrc_attr,
[RTA_OIF] = (struct nlattr *) &oif_attr,
};
memset(&req.rtm, 0, sizeof(req.rtm));
if (type == RTN_UNICAST)
tb = dn_fib_get_table(RT_MIN_TABLE, 1);
else
tb = dn_fib_get_table(RT_TABLE_LOCAL, 1);
if (tb == NULL)
return;
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = cmd;
req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;
req.nlh.nlmsg_pid = 0;
req.nlh.nlmsg_seq = 0;
req.rtm.rtm_dst_len = dst_len;
req.rtm.rtm_table = tb->n;
req.rtm.rtm_protocol = RTPROT_KERNEL;
req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST);
req.rtm.rtm_type = type;
if (cmd == RTM_NEWROUTE)
tb->insert(tb, &req.rtm, attrs, &req.nlh, NULL);
else
tb->delete(tb, &req.rtm, attrs, &req.nlh, NULL);
}
static void dn_fib_add_ifaddr(struct dn_ifaddr *ifa)
{
fib_magic(RTM_NEWROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
#if 0
if (!(dev->flags&IFF_UP))
return;
/* In the future, we will want to add default routes here */
#endif
}
static void dn_fib_del_ifaddr(struct dn_ifaddr *ifa)
{
int found_it = 0;
struct net_device *dev;
struct dn_dev *dn_db;
struct dn_ifaddr *ifa2;
ASSERT_RTNL();
/* Scan device list */
rcu_read_lock();
for_each_netdev_rcu(&init_net, dev) {
dn_db = rcu_dereference(dev->dn_ptr);
if (dn_db == NULL)
continue;
for (ifa2 = rcu_dereference(dn_db->ifa_list);
ifa2 != NULL;
ifa2 = rcu_dereference(ifa2->ifa_next)) {
if (ifa2->ifa_local == ifa->ifa_local) {
found_it = 1;
break;
}
}
}
rcu_read_unlock();
if (found_it == 0) {
fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 16, ifa);
if (dnet_addr_type(ifa->ifa_local) != RTN_LOCAL) {
if (dn_fib_sync_down(ifa->ifa_local, NULL, 0))
dn_fib_flush();
}
}
}
static void dn_fib_disable_addr(struct net_device *dev, int force)
{
if (dn_fib_sync_down(0, dev, force))
dn_fib_flush();
dn_rt_cache_flush(0);
neigh_ifdown(&dn_neigh_table, dev);
}
static int dn_fib_dnaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct dn_ifaddr *ifa = (struct dn_ifaddr *)ptr;
switch (event) {
case NETDEV_UP:
dn_fib_add_ifaddr(ifa);
dn_fib_sync_up(ifa->ifa_dev->dev);
dn_rt_cache_flush(-1);
break;
case NETDEV_DOWN:
dn_fib_del_ifaddr(ifa);
if (ifa->ifa_dev && ifa->ifa_dev->ifa_list == NULL) {
dn_fib_disable_addr(ifa->ifa_dev->dev, 1);
} else {
dn_rt_cache_flush(-1);
}
break;
}
return NOTIFY_DONE;
}
static int dn_fib_sync_down(__le16 local, struct net_device *dev, int force)
{
int ret = 0;
int scope = RT_SCOPE_NOWHERE;
if (force)
scope = -1;
for_fib_info() {
/*
* This makes no sense for DECnet.... we will almost
* certainly have more than one local address the same
* over all our interfaces. It needs thinking about
* some more.
*/
if (local && fi->fib_prefsrc == local) {
fi->fib_flags |= RTNH_F_DEAD;
ret++;
} else if (dev && fi->fib_nhs) {
int dead = 0;
change_nexthops(fi) {
if (nh->nh_flags&RTNH_F_DEAD)
dead++;
else if (nh->nh_dev == dev &&
nh->nh_scope != scope) {
spin_lock_bh(&dn_fib_multipath_lock);
nh->nh_flags |= RTNH_F_DEAD;
fi->fib_power -= nh->nh_power;
nh->nh_power = 0;
spin_unlock_bh(&dn_fib_multipath_lock);
dead++;
}
} endfor_nexthops(fi)
if (dead == fi->fib_nhs) {
fi->fib_flags |= RTNH_F_DEAD;
ret++;
}
}
} endfor_fib_info();
return ret;
}
static int dn_fib_sync_up(struct net_device *dev)
{
int ret = 0;
if (!(dev->flags&IFF_UP))
return 0;
for_fib_info() {
int alive = 0;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
alive++;
continue;
}
if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
continue;
if (nh->nh_dev != dev || dev->dn_ptr == NULL)
continue;
alive++;
spin_lock_bh(&dn_fib_multipath_lock);
nh->nh_power = 0;
nh->nh_flags &= ~RTNH_F_DEAD;
spin_unlock_bh(&dn_fib_multipath_lock);
} endfor_nexthops(fi);
if (alive > 0) {
fi->fib_flags &= ~RTNH_F_DEAD;
ret++;
}
} endfor_fib_info();
return ret;
}
static struct notifier_block dn_fib_dnaddr_notifier = {
.notifier_call = dn_fib_dnaddr_event,
};
void __exit dn_fib_cleanup(void)
{
dn_fib_table_cleanup();
dn_fib_rules_cleanup();
unregister_dnaddr_notifier(&dn_fib_dnaddr_notifier);
}
void __init dn_fib_init(void)
{
dn_fib_table_init();
dn_fib_rules_init();
register_dnaddr_notifier(&dn_fib_dnaddr_notifier);
rtnl_register(PF_DECnet, RTM_NEWROUTE, dn_fib_rtm_newroute, NULL, NULL);
rtnl_register(PF_DECnet, RTM_DELROUTE, dn_fib_rtm_delroute, NULL, NULL);
}

603
net/decnet/dn_neigh.c Normal file
View file

@ -0,0 +1,603 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Neighbour Functions (Adjacency Database and
* On-Ethernet Cache)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
*
*
* Changes:
* Steve Whitehouse : Fixed router listing routine
* Steve Whitehouse : Added error_report functions
* Steve Whitehouse : Added default router detection
* Steve Whitehouse : Hop counts in outgoing messages
* Steve Whitehouse : Fixed src/dst in outgoing messages so
* forwarding now stands a good chance of
* working.
* Steve Whitehouse : Fixed neighbour states (for now anyway).
* Steve Whitehouse : Made error_report functions dummies. This
* is not the right place to return skbs.
* Steve Whitehouse : Convert to seq_file
*
*/
#include <linux/net.h>
#include <linux/module.h>
#include <linux/socket.h>
#include <linux/if_arp.h>
#include <linux/slab.h>
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/netfilter_decnet.h>
#include <linux/spinlock.h>
#include <linux/seq_file.h>
#include <linux/rcupdate.h>
#include <linux/jhash.h>
#include <linux/atomic.h>
#include <net/net_namespace.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_neigh.h>
#include <net/dn_route.h>
static int dn_neigh_construct(struct neighbour *);
static void dn_long_error_report(struct neighbour *, struct sk_buff *);
static void dn_short_error_report(struct neighbour *, struct sk_buff *);
static int dn_long_output(struct neighbour *, struct sk_buff *);
static int dn_short_output(struct neighbour *, struct sk_buff *);
static int dn_phase3_output(struct neighbour *, struct sk_buff *);
/*
* For talking to broadcast devices: Ethernet & PPP
*/
static const struct neigh_ops dn_long_ops = {
.family = AF_DECnet,
.error_report = dn_long_error_report,
.output = dn_long_output,
.connected_output = dn_long_output,
};
/*
* For talking to pointopoint and multidrop devices: DDCMP and X.25
*/
static const struct neigh_ops dn_short_ops = {
.family = AF_DECnet,
.error_report = dn_short_error_report,
.output = dn_short_output,
.connected_output = dn_short_output,
};
/*
* For talking to DECnet phase III nodes
*/
static const struct neigh_ops dn_phase3_ops = {
.family = AF_DECnet,
.error_report = dn_short_error_report, /* Can use short version here */
.output = dn_phase3_output,
.connected_output = dn_phase3_output,
};
static u32 dn_neigh_hash(const void *pkey,
const struct net_device *dev,
__u32 *hash_rnd)
{
return jhash_2words(*(__u16 *)pkey, 0, hash_rnd[0]);
}
struct neigh_table dn_neigh_table = {
.family = PF_DECnet,
.entry_size = NEIGH_ENTRY_SIZE(sizeof(struct dn_neigh)),
.key_len = sizeof(__le16),
.hash = dn_neigh_hash,
.constructor = dn_neigh_construct,
.id = "dn_neigh_cache",
.parms ={
.tbl = &dn_neigh_table,
.reachable_time = 30 * HZ,
.data = {
[NEIGH_VAR_MCAST_PROBES] = 0,
[NEIGH_VAR_UCAST_PROBES] = 0,
[NEIGH_VAR_APP_PROBES] = 0,
[NEIGH_VAR_RETRANS_TIME] = 1 * HZ,
[NEIGH_VAR_BASE_REACHABLE_TIME] = 30 * HZ,
[NEIGH_VAR_DELAY_PROBE_TIME] = 5 * HZ,
[NEIGH_VAR_GC_STALETIME] = 60 * HZ,
[NEIGH_VAR_QUEUE_LEN_BYTES] = 64*1024,
[NEIGH_VAR_PROXY_QLEN] = 0,
[NEIGH_VAR_ANYCAST_DELAY] = 0,
[NEIGH_VAR_PROXY_DELAY] = 0,
[NEIGH_VAR_LOCKTIME] = 1 * HZ,
},
},
.gc_interval = 30 * HZ,
.gc_thresh1 = 128,
.gc_thresh2 = 512,
.gc_thresh3 = 1024,
};
static int dn_neigh_construct(struct neighbour *neigh)
{
struct net_device *dev = neigh->dev;
struct dn_neigh *dn = (struct dn_neigh *)neigh;
struct dn_dev *dn_db;
struct neigh_parms *parms;
rcu_read_lock();
dn_db = rcu_dereference(dev->dn_ptr);
if (dn_db == NULL) {
rcu_read_unlock();
return -EINVAL;
}
parms = dn_db->neigh_parms;
if (!parms) {
rcu_read_unlock();
return -EINVAL;
}
__neigh_parms_put(neigh->parms);
neigh->parms = neigh_parms_clone(parms);
if (dn_db->use_long)
neigh->ops = &dn_long_ops;
else
neigh->ops = &dn_short_ops;
rcu_read_unlock();
if (dn->flags & DN_NDFLAG_P3)
neigh->ops = &dn_phase3_ops;
neigh->nud_state = NUD_NOARP;
neigh->output = neigh->ops->connected_output;
if ((dev->type == ARPHRD_IPGRE) || (dev->flags & IFF_POINTOPOINT))
memcpy(neigh->ha, dev->broadcast, dev->addr_len);
else if ((dev->type == ARPHRD_ETHER) || (dev->type == ARPHRD_LOOPBACK))
dn_dn2eth(neigh->ha, dn->addr);
else {
net_dbg_ratelimited("Trying to create neigh for hw %d\n",
dev->type);
return -EINVAL;
}
/*
* Make an estimate of the remote block size by assuming that its
* two less then the device mtu, which it true for ethernet (and
* other things which support long format headers) since there is
* an extra length field (of 16 bits) which isn't part of the
* ethernet headers and which the DECnet specs won't admit is part
* of the DECnet routing headers either.
*
* If we over estimate here its no big deal, the NSP negotiations
* will prevent us from sending packets which are too large for the
* remote node to handle. In any case this figure is normally updated
* by a hello message in most cases.
*/
dn->blksize = dev->mtu - 2;
return 0;
}
static void dn_long_error_report(struct neighbour *neigh, struct sk_buff *skb)
{
printk(KERN_DEBUG "dn_long_error_report: called\n");
kfree_skb(skb);
}
static void dn_short_error_report(struct neighbour *neigh, struct sk_buff *skb)
{
printk(KERN_DEBUG "dn_short_error_report: called\n");
kfree_skb(skb);
}
static int dn_neigh_output_packet(struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct dn_route *rt = (struct dn_route *)dst;
struct neighbour *neigh = rt->n;
struct net_device *dev = neigh->dev;
char mac_addr[ETH_ALEN];
unsigned int seq;
int err;
dn_dn2eth(mac_addr, rt->rt_local_src);
do {
seq = read_seqbegin(&neigh->ha_lock);
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, mac_addr, skb->len);
} while (read_seqretry(&neigh->ha_lock, seq));
if (err >= 0)
err = dev_queue_xmit(skb);
else {
kfree_skb(skb);
err = -EINVAL;
}
return err;
}
static int dn_long_output(struct neighbour *neigh, struct sk_buff *skb)
{
struct net_device *dev = neigh->dev;
int headroom = dev->hard_header_len + sizeof(struct dn_long_packet) + 3;
unsigned char *data;
struct dn_long_packet *lp;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
if (skb_headroom(skb) < headroom) {
struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
if (skb2 == NULL) {
net_crit_ratelimited("dn_long_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
consume_skb(skb);
skb = skb2;
net_info_ratelimited("dn_long_output: Increasing headroom\n");
}
data = skb_push(skb, sizeof(struct dn_long_packet) + 3);
lp = (struct dn_long_packet *)(data+3);
*((__le16 *)data) = cpu_to_le16(skb->len - 2);
*(data + 2) = 1 | DN_RT_F_PF; /* Padding */
lp->msgflg = DN_RT_PKT_LONG|(cb->rt_flags&(DN_RT_F_IE|DN_RT_F_RQR|DN_RT_F_RTS));
lp->d_area = lp->d_subarea = 0;
dn_dn2eth(lp->d_id, cb->dst);
lp->s_area = lp->s_subarea = 0;
dn_dn2eth(lp->s_id, cb->src);
lp->nl2 = 0;
lp->visit_ct = cb->hops & 0x3f;
lp->s_class = 0;
lp->pt = 0;
skb_reset_network_header(skb);
return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, skb, NULL,
neigh->dev, dn_neigh_output_packet);
}
static int dn_short_output(struct neighbour *neigh, struct sk_buff *skb)
{
struct net_device *dev = neigh->dev;
int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
struct dn_short_packet *sp;
unsigned char *data;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
if (skb_headroom(skb) < headroom) {
struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
if (skb2 == NULL) {
net_crit_ratelimited("dn_short_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
consume_skb(skb);
skb = skb2;
net_info_ratelimited("dn_short_output: Increasing headroom\n");
}
data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
*((__le16 *)data) = cpu_to_le16(skb->len - 2);
sp = (struct dn_short_packet *)(data+2);
sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
sp->dstnode = cb->dst;
sp->srcnode = cb->src;
sp->forward = cb->hops & 0x3f;
skb_reset_network_header(skb);
return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, skb, NULL,
neigh->dev, dn_neigh_output_packet);
}
/*
* Phase 3 output is the same is short output, execpt that
* it clears the area bits before transmission.
*/
static int dn_phase3_output(struct neighbour *neigh, struct sk_buff *skb)
{
struct net_device *dev = neigh->dev;
int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
struct dn_short_packet *sp;
unsigned char *data;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
if (skb_headroom(skb) < headroom) {
struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
if (skb2 == NULL) {
net_crit_ratelimited("dn_phase3_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
consume_skb(skb);
skb = skb2;
net_info_ratelimited("dn_phase3_output: Increasing headroom\n");
}
data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
*((__le16 *)data) = cpu_to_le16(skb->len - 2);
sp = (struct dn_short_packet *)(data + 2);
sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
sp->dstnode = cb->dst & cpu_to_le16(0x03ff);
sp->srcnode = cb->src & cpu_to_le16(0x03ff);
sp->forward = cb->hops & 0x3f;
skb_reset_network_header(skb);
return NF_HOOK(NFPROTO_DECNET, NF_DN_POST_ROUTING, skb, NULL,
neigh->dev, dn_neigh_output_packet);
}
/*
* Unfortunately, the neighbour code uses the device in its hash
* function, so we don't get any advantage from it. This function
* basically does a neigh_lookup(), but without comparing the device
* field. This is required for the On-Ethernet cache
*/
/*
* Pointopoint link receives a hello message
*/
void dn_neigh_pointopoint_hello(struct sk_buff *skb)
{
kfree_skb(skb);
}
/*
* Ethernet router hello message received
*/
int dn_neigh_router_hello(struct sk_buff *skb)
{
struct rtnode_hello_message *msg = (struct rtnode_hello_message *)skb->data;
struct neighbour *neigh;
struct dn_neigh *dn;
struct dn_dev *dn_db;
__le16 src;
src = dn_eth2dn(msg->id);
neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
dn = (struct dn_neigh *)neigh;
if (neigh) {
write_lock(&neigh->lock);
neigh->used = jiffies;
dn_db = rcu_dereference(neigh->dev->dn_ptr);
if (!(neigh->nud_state & NUD_PERMANENT)) {
neigh->updated = jiffies;
if (neigh->dev->type == ARPHRD_ETHER)
memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);
dn->blksize = le16_to_cpu(msg->blksize);
dn->priority = msg->priority;
dn->flags &= ~DN_NDFLAG_P3;
switch (msg->iinfo & DN_RT_INFO_TYPE) {
case DN_RT_INFO_L1RT:
dn->flags &=~DN_NDFLAG_R2;
dn->flags |= DN_NDFLAG_R1;
break;
case DN_RT_INFO_L2RT:
dn->flags |= DN_NDFLAG_R2;
}
}
/* Only use routers in our area */
if ((le16_to_cpu(src)>>10) == (le16_to_cpu((decnet_address))>>10)) {
if (!dn_db->router) {
dn_db->router = neigh_clone(neigh);
} else {
if (msg->priority > ((struct dn_neigh *)dn_db->router)->priority)
neigh_release(xchg(&dn_db->router, neigh_clone(neigh)));
}
}
write_unlock(&neigh->lock);
neigh_release(neigh);
}
kfree_skb(skb);
return 0;
}
/*
* Endnode hello message received
*/
int dn_neigh_endnode_hello(struct sk_buff *skb)
{
struct endnode_hello_message *msg = (struct endnode_hello_message *)skb->data;
struct neighbour *neigh;
struct dn_neigh *dn;
__le16 src;
src = dn_eth2dn(msg->id);
neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
dn = (struct dn_neigh *)neigh;
if (neigh) {
write_lock(&neigh->lock);
neigh->used = jiffies;
if (!(neigh->nud_state & NUD_PERMANENT)) {
neigh->updated = jiffies;
if (neigh->dev->type == ARPHRD_ETHER)
memcpy(neigh->ha, &eth_hdr(skb)->h_source, ETH_ALEN);
dn->flags &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
dn->blksize = le16_to_cpu(msg->blksize);
dn->priority = 0;
}
write_unlock(&neigh->lock);
neigh_release(neigh);
}
kfree_skb(skb);
return 0;
}
static char *dn_find_slot(char *base, int max, int priority)
{
int i;
unsigned char *min = NULL;
base += 6; /* skip first id */
for(i = 0; i < max; i++) {
if (!min || (*base < *min))
min = base;
base += 7; /* find next priority */
}
if (!min)
return NULL;
return (*min < priority) ? (min - 6) : NULL;
}
struct elist_cb_state {
struct net_device *dev;
unsigned char *ptr;
unsigned char *rs;
int t, n;
};
static void neigh_elist_cb(struct neighbour *neigh, void *_info)
{
struct elist_cb_state *s = _info;
struct dn_neigh *dn;
if (neigh->dev != s->dev)
return;
dn = (struct dn_neigh *) neigh;
if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
return;
if (s->t == s->n)
s->rs = dn_find_slot(s->ptr, s->n, dn->priority);
else
s->t++;
if (s->rs == NULL)
return;
dn_dn2eth(s->rs, dn->addr);
s->rs += 6;
*(s->rs) = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
*(s->rs) |= dn->priority;
s->rs++;
}
int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n)
{
struct elist_cb_state state;
state.dev = dev;
state.t = 0;
state.n = n;
state.ptr = ptr;
state.rs = ptr;
neigh_for_each(&dn_neigh_table, neigh_elist_cb, &state);
return state.t;
}
#ifdef CONFIG_PROC_FS
static inline void dn_neigh_format_entry(struct seq_file *seq,
struct neighbour *n)
{
struct dn_neigh *dn = (struct dn_neigh *) n;
char buf[DN_ASCBUF_LEN];
read_lock(&n->lock);
seq_printf(seq, "%-7s %s%s%s %02x %02d %07ld %-8s\n",
dn_addr2asc(le16_to_cpu(dn->addr), buf),
(dn->flags&DN_NDFLAG_R1) ? "1" : "-",
(dn->flags&DN_NDFLAG_R2) ? "2" : "-",
(dn->flags&DN_NDFLAG_P3) ? "3" : "-",
dn->n.nud_state,
atomic_read(&dn->n.refcnt),
dn->blksize,
(dn->n.dev) ? dn->n.dev->name : "?");
read_unlock(&n->lock);
}
static int dn_neigh_seq_show(struct seq_file *seq, void *v)
{
if (v == SEQ_START_TOKEN) {
seq_puts(seq, "Addr Flags State Use Blksize Dev\n");
} else {
dn_neigh_format_entry(seq, v);
}
return 0;
}
static void *dn_neigh_seq_start(struct seq_file *seq, loff_t *pos)
{
return neigh_seq_start(seq, pos, &dn_neigh_table,
NEIGH_SEQ_NEIGH_ONLY);
}
static const struct seq_operations dn_neigh_seq_ops = {
.start = dn_neigh_seq_start,
.next = neigh_seq_next,
.stop = neigh_seq_stop,
.show = dn_neigh_seq_show,
};
static int dn_neigh_seq_open(struct inode *inode, struct file *file)
{
return seq_open_net(inode, file, &dn_neigh_seq_ops,
sizeof(struct neigh_seq_state));
}
static const struct file_operations dn_neigh_seq_fops = {
.owner = THIS_MODULE,
.open = dn_neigh_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release_net,
};
#endif
void __init dn_neigh_init(void)
{
neigh_table_init(&dn_neigh_table);
proc_create("decnet_neigh", S_IRUGO, init_net.proc_net,
&dn_neigh_seq_fops);
}
void __exit dn_neigh_cleanup(void)
{
remove_proc_entry("decnet_neigh", init_net.proc_net);
neigh_table_clear(&dn_neigh_table);
}

916
net/decnet/dn_nsp_in.c Normal file
View file

@ -0,0 +1,916 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Network Services Protocol (Input)
*
* Author: Eduardo Marcelo Serrat <emserrat@geocities.com>
*
* Changes:
*
* Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from
* original dn_nsp.c.
* Steve Whitehouse: Updated to work with my new routing architecture.
* Steve Whitehouse: Add changes from Eduardo Serrat's patches.
* Steve Whitehouse: Put all ack handling code in a common routine.
* Steve Whitehouse: Put other common bits into dn_nsp_rx()
* Steve Whitehouse: More checks on skb->len to catch bogus packets
* Fixed various race conditions and possible nasties.
* Steve Whitehouse: Now handles returned conninit frames.
* David S. Miller: New socket locking
* Steve Whitehouse: Fixed lockup when socket filtering was enabled.
* Paul Koning: Fix to push CC sockets into RUN when acks are
* received.
* Steve Whitehouse:
* Patrick Caulfield: Checking conninits for correctness & sending of error
* responses.
* Steve Whitehouse: Added backlog congestion level return codes.
* Patrick Caulfield:
* Steve Whitehouse: Added flow control support (outbound)
* Steve Whitehouse: Prepare for nonlinear skbs
*/
/******************************************************************************
(c) 1995-1998 E.M. Serrat emserrat@geocities.com
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
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*******************************************************************************/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/inet.h>
#include <linux/route.h>
#include <linux/slab.h>
#include <net/sock.h>
#include <net/tcp_states.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/termios.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/netfilter_decnet.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/dn.h>
#include <net/dn_nsp.h>
#include <net/dn_dev.h>
#include <net/dn_route.h>
extern int decnet_log_martians;
static void dn_log_martian(struct sk_buff *skb, const char *msg)
{
if (decnet_log_martians) {
char *devname = skb->dev ? skb->dev->name : "???";
struct dn_skb_cb *cb = DN_SKB_CB(skb);
net_info_ratelimited("DECnet: Martian packet (%s) dev=%s src=0x%04hx dst=0x%04hx srcport=0x%04hx dstport=0x%04hx\n",
msg, devname,
le16_to_cpu(cb->src),
le16_to_cpu(cb->dst),
le16_to_cpu(cb->src_port),
le16_to_cpu(cb->dst_port));
}
}
/*
* For this function we've flipped the cross-subchannel bit
* if the message is an otherdata or linkservice message. Thus
* we can use it to work out what to update.
*/
static void dn_ack(struct sock *sk, struct sk_buff *skb, unsigned short ack)
{
struct dn_scp *scp = DN_SK(sk);
unsigned short type = ((ack >> 12) & 0x0003);
int wakeup = 0;
switch (type) {
case 0: /* ACK - Data */
if (dn_after(ack, scp->ackrcv_dat)) {
scp->ackrcv_dat = ack & 0x0fff;
wakeup |= dn_nsp_check_xmit_queue(sk, skb,
&scp->data_xmit_queue,
ack);
}
break;
case 1: /* NAK - Data */
break;
case 2: /* ACK - OtherData */
if (dn_after(ack, scp->ackrcv_oth)) {
scp->ackrcv_oth = ack & 0x0fff;
wakeup |= dn_nsp_check_xmit_queue(sk, skb,
&scp->other_xmit_queue,
ack);
}
break;
case 3: /* NAK - OtherData */
break;
}
if (wakeup && !sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
}
/*
* This function is a universal ack processor.
*/
static int dn_process_ack(struct sock *sk, struct sk_buff *skb, int oth)
{
__le16 *ptr = (__le16 *)skb->data;
int len = 0;
unsigned short ack;
if (skb->len < 2)
return len;
if ((ack = le16_to_cpu(*ptr)) & 0x8000) {
skb_pull(skb, 2);
ptr++;
len += 2;
if ((ack & 0x4000) == 0) {
if (oth)
ack ^= 0x2000;
dn_ack(sk, skb, ack);
}
}
if (skb->len < 2)
return len;
if ((ack = le16_to_cpu(*ptr)) & 0x8000) {
skb_pull(skb, 2);
len += 2;
if ((ack & 0x4000) == 0) {
if (oth)
ack ^= 0x2000;
dn_ack(sk, skb, ack);
}
}
return len;
}
/**
* dn_check_idf - Check an image data field format is correct.
* @pptr: Pointer to pointer to image data
* @len: Pointer to length of image data
* @max: The maximum allowed length of the data in the image data field
* @follow_on: Check that this many bytes exist beyond the end of the image data
*
* Returns: 0 if ok, -1 on error
*/
static inline int dn_check_idf(unsigned char **pptr, int *len, unsigned char max, unsigned char follow_on)
{
unsigned char *ptr = *pptr;
unsigned char flen = *ptr++;
(*len)--;
if (flen > max)
return -1;
if ((flen + follow_on) > *len)
return -1;
*len -= flen;
*pptr = ptr + flen;
return 0;
}
/*
* Table of reason codes to pass back to node which sent us a badly
* formed message, plus text messages for the log. A zero entry in
* the reason field means "don't reply" otherwise a disc init is sent with
* the specified reason code.
*/
static struct {
unsigned short reason;
const char *text;
} ci_err_table[] = {
{ 0, "CI: Truncated message" },
{ NSP_REASON_ID, "CI: Destination username error" },
{ NSP_REASON_ID, "CI: Destination username type" },
{ NSP_REASON_US, "CI: Source username error" },
{ 0, "CI: Truncated at menuver" },
{ 0, "CI: Truncated before access or user data" },
{ NSP_REASON_IO, "CI: Access data format error" },
{ NSP_REASON_IO, "CI: User data format error" }
};
/*
* This function uses a slightly different lookup method
* to find its sockets, since it searches on object name/number
* rather than port numbers. Various tests are done to ensure that
* the incoming data is in the correct format before it is queued to
* a socket.
*/
static struct sock *dn_find_listener(struct sk_buff *skb, unsigned short *reason)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct nsp_conn_init_msg *msg = (struct nsp_conn_init_msg *)skb->data;
struct sockaddr_dn dstaddr;
struct sockaddr_dn srcaddr;
unsigned char type = 0;
int dstlen;
int srclen;
unsigned char *ptr;
int len;
int err = 0;
unsigned char menuver;
memset(&dstaddr, 0, sizeof(struct sockaddr_dn));
memset(&srcaddr, 0, sizeof(struct sockaddr_dn));
/*
* 1. Decode & remove message header
*/
cb->src_port = msg->srcaddr;
cb->dst_port = msg->dstaddr;
cb->services = msg->services;
cb->info = msg->info;
cb->segsize = le16_to_cpu(msg->segsize);
if (!pskb_may_pull(skb, sizeof(*msg)))
goto err_out;
skb_pull(skb, sizeof(*msg));
len = skb->len;
ptr = skb->data;
/*
* 2. Check destination end username format
*/
dstlen = dn_username2sockaddr(ptr, len, &dstaddr, &type);
err++;
if (dstlen < 0)
goto err_out;
err++;
if (type > 1)
goto err_out;
len -= dstlen;
ptr += dstlen;
/*
* 3. Check source end username format
*/
srclen = dn_username2sockaddr(ptr, len, &srcaddr, &type);
err++;
if (srclen < 0)
goto err_out;
len -= srclen;
ptr += srclen;
err++;
if (len < 1)
goto err_out;
menuver = *ptr;
ptr++;
len--;
/*
* 4. Check that optional data actually exists if menuver says it does
*/
err++;
if ((menuver & (DN_MENUVER_ACC | DN_MENUVER_USR)) && (len < 1))
goto err_out;
/*
* 5. Check optional access data format
*/
err++;
if (menuver & DN_MENUVER_ACC) {
if (dn_check_idf(&ptr, &len, 39, 1))
goto err_out;
if (dn_check_idf(&ptr, &len, 39, 1))
goto err_out;
if (dn_check_idf(&ptr, &len, 39, (menuver & DN_MENUVER_USR) ? 1 : 0))
goto err_out;
}
/*
* 6. Check optional user data format
*/
err++;
if (menuver & DN_MENUVER_USR) {
if (dn_check_idf(&ptr, &len, 16, 0))
goto err_out;
}
/*
* 7. Look up socket based on destination end username
*/
return dn_sklist_find_listener(&dstaddr);
err_out:
dn_log_martian(skb, ci_err_table[err].text);
*reason = ci_err_table[err].reason;
return NULL;
}
static void dn_nsp_conn_init(struct sock *sk, struct sk_buff *skb)
{
if (sk_acceptq_is_full(sk)) {
kfree_skb(skb);
return;
}
sk->sk_ack_backlog++;
skb_queue_tail(&sk->sk_receive_queue, skb);
sk->sk_state_change(sk);
}
static void dn_nsp_conn_conf(struct sock *sk, struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct dn_scp *scp = DN_SK(sk);
unsigned char *ptr;
if (skb->len < 4)
goto out;
ptr = skb->data;
cb->services = *ptr++;
cb->info = *ptr++;
cb->segsize = le16_to_cpu(*(__le16 *)ptr);
if ((scp->state == DN_CI) || (scp->state == DN_CD)) {
scp->persist = 0;
scp->addrrem = cb->src_port;
sk->sk_state = TCP_ESTABLISHED;
scp->state = DN_RUN;
scp->services_rem = cb->services;
scp->info_rem = cb->info;
scp->segsize_rem = cb->segsize;
if ((scp->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
scp->max_window = decnet_no_fc_max_cwnd;
if (skb->len > 0) {
u16 dlen = *skb->data;
if ((dlen <= 16) && (dlen <= skb->len)) {
scp->conndata_in.opt_optl = cpu_to_le16(dlen);
skb_copy_from_linear_data_offset(skb, 1,
scp->conndata_in.opt_data, dlen);
}
}
dn_nsp_send_link(sk, DN_NOCHANGE, 0);
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
}
out:
kfree_skb(skb);
}
static void dn_nsp_conn_ack(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
if (scp->state == DN_CI) {
scp->state = DN_CD;
scp->persist = 0;
}
kfree_skb(skb);
}
static void dn_nsp_disc_init(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
struct dn_skb_cb *cb = DN_SKB_CB(skb);
unsigned short reason;
if (skb->len < 2)
goto out;
reason = le16_to_cpu(*(__le16 *)skb->data);
skb_pull(skb, 2);
scp->discdata_in.opt_status = cpu_to_le16(reason);
scp->discdata_in.opt_optl = 0;
memset(scp->discdata_in.opt_data, 0, 16);
if (skb->len > 0) {
u16 dlen = *skb->data;
if ((dlen <= 16) && (dlen <= skb->len)) {
scp->discdata_in.opt_optl = cpu_to_le16(dlen);
skb_copy_from_linear_data_offset(skb, 1, scp->discdata_in.opt_data, dlen);
}
}
scp->addrrem = cb->src_port;
sk->sk_state = TCP_CLOSE;
switch (scp->state) {
case DN_CI:
case DN_CD:
scp->state = DN_RJ;
sk->sk_err = ECONNREFUSED;
break;
case DN_RUN:
sk->sk_shutdown |= SHUTDOWN_MASK;
scp->state = DN_DN;
break;
case DN_DI:
scp->state = DN_DIC;
break;
}
if (!sock_flag(sk, SOCK_DEAD)) {
if (sk->sk_socket->state != SS_UNCONNECTED)
sk->sk_socket->state = SS_DISCONNECTING;
sk->sk_state_change(sk);
}
/*
* It appears that its possible for remote machines to send disc
* init messages with no port identifier if we are in the CI and
* possibly also the CD state. Obviously we shouldn't reply with
* a message if we don't know what the end point is.
*/
if (scp->addrrem) {
dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
}
scp->persist_fxn = dn_destroy_timer;
scp->persist = dn_nsp_persist(sk);
out:
kfree_skb(skb);
}
/*
* disc_conf messages are also called no_resources or no_link
* messages depending upon the "reason" field.
*/
static void dn_nsp_disc_conf(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
unsigned short reason;
if (skb->len != 2)
goto out;
reason = le16_to_cpu(*(__le16 *)skb->data);
sk->sk_state = TCP_CLOSE;
switch (scp->state) {
case DN_CI:
scp->state = DN_NR;
break;
case DN_DR:
if (reason == NSP_REASON_DC)
scp->state = DN_DRC;
if (reason == NSP_REASON_NL)
scp->state = DN_CN;
break;
case DN_DI:
scp->state = DN_DIC;
break;
case DN_RUN:
sk->sk_shutdown |= SHUTDOWN_MASK;
case DN_CC:
scp->state = DN_CN;
}
if (!sock_flag(sk, SOCK_DEAD)) {
if (sk->sk_socket->state != SS_UNCONNECTED)
sk->sk_socket->state = SS_DISCONNECTING;
sk->sk_state_change(sk);
}
scp->persist_fxn = dn_destroy_timer;
scp->persist = dn_nsp_persist(sk);
out:
kfree_skb(skb);
}
static void dn_nsp_linkservice(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
unsigned short segnum;
unsigned char lsflags;
signed char fcval;
int wake_up = 0;
char *ptr = skb->data;
unsigned char fctype = scp->services_rem & NSP_FC_MASK;
if (skb->len != 4)
goto out;
segnum = le16_to_cpu(*(__le16 *)ptr);
ptr += 2;
lsflags = *(unsigned char *)ptr++;
fcval = *ptr;
/*
* Here we ignore erronous packets which should really
* should cause a connection abort. It is not critical
* for now though.
*/
if (lsflags & 0xf8)
goto out;
if (seq_next(scp->numoth_rcv, segnum)) {
seq_add(&scp->numoth_rcv, 1);
switch(lsflags & 0x04) { /* FCVAL INT */
case 0x00: /* Normal Request */
switch(lsflags & 0x03) { /* FCVAL MOD */
case 0x00: /* Request count */
if (fcval < 0) {
unsigned char p_fcval = -fcval;
if ((scp->flowrem_dat > p_fcval) &&
(fctype == NSP_FC_SCMC)) {
scp->flowrem_dat -= p_fcval;
}
} else if (fcval > 0) {
scp->flowrem_dat += fcval;
wake_up = 1;
}
break;
case 0x01: /* Stop outgoing data */
scp->flowrem_sw = DN_DONTSEND;
break;
case 0x02: /* Ok to start again */
scp->flowrem_sw = DN_SEND;
dn_nsp_output(sk);
wake_up = 1;
}
break;
case 0x04: /* Interrupt Request */
if (fcval > 0) {
scp->flowrem_oth += fcval;
wake_up = 1;
}
break;
}
if (wake_up && !sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
}
dn_nsp_send_oth_ack(sk);
out:
kfree_skb(skb);
}
/*
* Copy of sock_queue_rcv_skb (from sock.h) without
* bh_lock_sock() (its already held when this is called) which
* also allows data and other data to be queued to a socket.
*/
static __inline__ int dn_queue_skb(struct sock *sk, struct sk_buff *skb, int sig, struct sk_buff_head *queue)
{
int err;
/* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
number of warnings when compiling with -W --ANK
*/
if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
(unsigned int)sk->sk_rcvbuf) {
err = -ENOMEM;
goto out;
}
err = sk_filter(sk, skb);
if (err)
goto out;
skb_set_owner_r(skb, sk);
skb_queue_tail(queue, skb);
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_data_ready(sk);
out:
return err;
}
static void dn_nsp_otherdata(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
unsigned short segnum;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
int queued = 0;
if (skb->len < 2)
goto out;
cb->segnum = segnum = le16_to_cpu(*(__le16 *)skb->data);
skb_pull(skb, 2);
if (seq_next(scp->numoth_rcv, segnum)) {
if (dn_queue_skb(sk, skb, SIGURG, &scp->other_receive_queue) == 0) {
seq_add(&scp->numoth_rcv, 1);
scp->other_report = 0;
queued = 1;
}
}
dn_nsp_send_oth_ack(sk);
out:
if (!queued)
kfree_skb(skb);
}
static void dn_nsp_data(struct sock *sk, struct sk_buff *skb)
{
int queued = 0;
unsigned short segnum;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct dn_scp *scp = DN_SK(sk);
if (skb->len < 2)
goto out;
cb->segnum = segnum = le16_to_cpu(*(__le16 *)skb->data);
skb_pull(skb, 2);
if (seq_next(scp->numdat_rcv, segnum)) {
if (dn_queue_skb(sk, skb, SIGIO, &sk->sk_receive_queue) == 0) {
seq_add(&scp->numdat_rcv, 1);
queued = 1;
}
if ((scp->flowloc_sw == DN_SEND) && dn_congested(sk)) {
scp->flowloc_sw = DN_DONTSEND;
dn_nsp_send_link(sk, DN_DONTSEND, 0);
}
}
dn_nsp_send_data_ack(sk);
out:
if (!queued)
kfree_skb(skb);
}
/*
* If one of our conninit messages is returned, this function
* deals with it. It puts the socket into the NO_COMMUNICATION
* state.
*/
static void dn_returned_conn_init(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
if (scp->state == DN_CI) {
scp->state = DN_NC;
sk->sk_state = TCP_CLOSE;
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
}
kfree_skb(skb);
}
static int dn_nsp_no_socket(struct sk_buff *skb, unsigned short reason)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
int ret = NET_RX_DROP;
/* Must not reply to returned packets */
if (cb->rt_flags & DN_RT_F_RTS)
goto out;
if ((reason != NSP_REASON_OK) && ((cb->nsp_flags & 0x0c) == 0x08)) {
switch (cb->nsp_flags & 0x70) {
case 0x10:
case 0x60: /* (Retransmitted) Connect Init */
dn_nsp_return_disc(skb, NSP_DISCINIT, reason);
ret = NET_RX_SUCCESS;
break;
case 0x20: /* Connect Confirm */
dn_nsp_return_disc(skb, NSP_DISCCONF, reason);
ret = NET_RX_SUCCESS;
break;
}
}
out:
kfree_skb(skb);
return ret;
}
static int dn_nsp_rx_packet(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct sock *sk = NULL;
unsigned char *ptr = (unsigned char *)skb->data;
unsigned short reason = NSP_REASON_NL;
if (!pskb_may_pull(skb, 2))
goto free_out;
skb_reset_transport_header(skb);
cb->nsp_flags = *ptr++;
if (decnet_debug_level & 2)
printk(KERN_DEBUG "dn_nsp_rx: Message type 0x%02x\n", (int)cb->nsp_flags);
if (cb->nsp_flags & 0x83)
goto free_out;
/*
* Filter out conninits and useless packet types
*/
if ((cb->nsp_flags & 0x0c) == 0x08) {
switch (cb->nsp_flags & 0x70) {
case 0x00: /* NOP */
case 0x70: /* Reserved */
case 0x50: /* Reserved, Phase II node init */
goto free_out;
case 0x10:
case 0x60:
if (unlikely(cb->rt_flags & DN_RT_F_RTS))
goto free_out;
sk = dn_find_listener(skb, &reason);
goto got_it;
}
}
if (!pskb_may_pull(skb, 3))
goto free_out;
/*
* Grab the destination address.
*/
cb->dst_port = *(__le16 *)ptr;
cb->src_port = 0;
ptr += 2;
/*
* If not a connack, grab the source address too.
*/
if (pskb_may_pull(skb, 5)) {
cb->src_port = *(__le16 *)ptr;
ptr += 2;
skb_pull(skb, 5);
}
/*
* Returned packets...
* Swap src & dst and look up in the normal way.
*/
if (unlikely(cb->rt_flags & DN_RT_F_RTS)) {
__le16 tmp = cb->dst_port;
cb->dst_port = cb->src_port;
cb->src_port = tmp;
tmp = cb->dst;
cb->dst = cb->src;
cb->src = tmp;
}
/*
* Find the socket to which this skb is destined.
*/
sk = dn_find_by_skb(skb);
got_it:
if (sk != NULL) {
struct dn_scp *scp = DN_SK(sk);
/* Reset backoff */
scp->nsp_rxtshift = 0;
/*
* We linearize everything except data segments here.
*/
if (cb->nsp_flags & ~0x60) {
if (unlikely(skb_linearize(skb)))
goto free_out;
}
return sk_receive_skb(sk, skb, 0);
}
return dn_nsp_no_socket(skb, reason);
free_out:
kfree_skb(skb);
return NET_RX_DROP;
}
int dn_nsp_rx(struct sk_buff *skb)
{
return NF_HOOK(NFPROTO_DECNET, NF_DN_LOCAL_IN, skb, skb->dev, NULL,
dn_nsp_rx_packet);
}
/*
* This is the main receive routine for sockets. It is called
* from the above when the socket is not busy, and also from
* sock_release() when there is a backlog queued up.
*/
int dn_nsp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
{
struct dn_scp *scp = DN_SK(sk);
struct dn_skb_cb *cb = DN_SKB_CB(skb);
if (cb->rt_flags & DN_RT_F_RTS) {
if (cb->nsp_flags == 0x18 || cb->nsp_flags == 0x68)
dn_returned_conn_init(sk, skb);
else
kfree_skb(skb);
return NET_RX_SUCCESS;
}
/*
* Control packet.
*/
if ((cb->nsp_flags & 0x0c) == 0x08) {
switch (cb->nsp_flags & 0x70) {
case 0x10:
case 0x60:
dn_nsp_conn_init(sk, skb);
break;
case 0x20:
dn_nsp_conn_conf(sk, skb);
break;
case 0x30:
dn_nsp_disc_init(sk, skb);
break;
case 0x40:
dn_nsp_disc_conf(sk, skb);
break;
}
} else if (cb->nsp_flags == 0x24) {
/*
* Special for connacks, 'cos they don't have
* ack data or ack otherdata info.
*/
dn_nsp_conn_ack(sk, skb);
} else {
int other = 1;
/* both data and ack frames can kick a CC socket into RUN */
if ((scp->state == DN_CC) && !sock_flag(sk, SOCK_DEAD)) {
scp->state = DN_RUN;
sk->sk_state = TCP_ESTABLISHED;
sk->sk_state_change(sk);
}
if ((cb->nsp_flags & 0x1c) == 0)
other = 0;
if (cb->nsp_flags == 0x04)
other = 0;
/*
* Read out ack data here, this applies equally
* to data, other data, link serivce and both
* ack data and ack otherdata.
*/
dn_process_ack(sk, skb, other);
/*
* If we've some sort of data here then call a
* suitable routine for dealing with it, otherwise
* the packet is an ack and can be discarded.
*/
if ((cb->nsp_flags & 0x0c) == 0) {
if (scp->state != DN_RUN)
goto free_out;
switch (cb->nsp_flags) {
case 0x10: /* LS */
dn_nsp_linkservice(sk, skb);
break;
case 0x30: /* OD */
dn_nsp_otherdata(sk, skb);
break;
default:
dn_nsp_data(sk, skb);
}
} else { /* Ack, chuck it out here */
free_out:
kfree_skb(skb);
}
}
return NET_RX_SUCCESS;
}

718
net/decnet/dn_nsp_out.c Normal file
View file

@ -0,0 +1,718 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Network Services Protocol (Output)
*
* Author: Eduardo Marcelo Serrat <emserrat@geocities.com>
*
* Changes:
*
* Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from
* original dn_nsp.c.
* Steve Whitehouse: Updated to work with my new routing architecture.
* Steve Whitehouse: Added changes from Eduardo Serrat's patches.
* Steve Whitehouse: Now conninits have the "return" bit set.
* Steve Whitehouse: Fixes to check alloc'd skbs are non NULL!
* Moved output state machine into one function
* Steve Whitehouse: New output state machine
* Paul Koning: Connect Confirm message fix.
* Eduardo Serrat: Fix to stop dn_nsp_do_disc() sending malformed packets.
* Steve Whitehouse: dn_nsp_output() and friends needed a spring clean
* Steve Whitehouse: Moved dn_nsp_send() in here from route.h
*/
/******************************************************************************
(c) 1995-1998 E.M. Serrat emserrat@geocities.com
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
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*******************************************************************************/
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/inet.h>
#include <linux/route.h>
#include <linux/slab.h>
#include <net/sock.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/termios.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/if_packet.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/dn.h>
#include <net/dn_nsp.h>
#include <net/dn_dev.h>
#include <net/dn_route.h>
static int nsp_backoff[NSP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 };
static void dn_nsp_send(struct sk_buff *skb)
{
struct sock *sk = skb->sk;
struct dn_scp *scp = DN_SK(sk);
struct dst_entry *dst;
struct flowidn fld;
skb_reset_transport_header(skb);
scp->stamp = jiffies;
dst = sk_dst_check(sk, 0);
if (dst) {
try_again:
skb_dst_set(skb, dst);
dst_output(skb);
return;
}
memset(&fld, 0, sizeof(fld));
fld.flowidn_oif = sk->sk_bound_dev_if;
fld.saddr = dn_saddr2dn(&scp->addr);
fld.daddr = dn_saddr2dn(&scp->peer);
dn_sk_ports_copy(&fld, scp);
fld.flowidn_proto = DNPROTO_NSP;
if (dn_route_output_sock(&sk->sk_dst_cache, &fld, sk, 0) == 0) {
dst = sk_dst_get(sk);
sk->sk_route_caps = dst->dev->features;
goto try_again;
}
sk->sk_err = EHOSTUNREACH;
if (!sock_flag(sk, SOCK_DEAD))
sk->sk_state_change(sk);
}
/*
* If sk == NULL, then we assume that we are supposed to be making
* a routing layer skb. If sk != NULL, then we are supposed to be
* creating an skb for the NSP layer.
*
* The eventual aim is for each socket to have a cached header size
* for its outgoing packets, and to set hdr from this when sk != NULL.
*/
struct sk_buff *dn_alloc_skb(struct sock *sk, int size, gfp_t pri)
{
struct sk_buff *skb;
int hdr = 64;
if ((skb = alloc_skb(size + hdr, pri)) == NULL)
return NULL;
skb->protocol = htons(ETH_P_DNA_RT);
skb->pkt_type = PACKET_OUTGOING;
if (sk)
skb_set_owner_w(skb, sk);
skb_reserve(skb, hdr);
return skb;
}
/*
* Calculate persist timer based upon the smoothed round
* trip time and the variance. Backoff according to the
* nsp_backoff[] array.
*/
unsigned long dn_nsp_persist(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
t *= nsp_backoff[scp->nsp_rxtshift];
if (t < HZ) t = HZ;
if (t > (600*HZ)) t = (600*HZ);
if (scp->nsp_rxtshift < NSP_MAXRXTSHIFT)
scp->nsp_rxtshift++;
/* printk(KERN_DEBUG "rxtshift %lu, t=%lu\n", scp->nsp_rxtshift, t); */
return t;
}
/*
* This is called each time we get an estimate for the rtt
* on the link.
*/
static void dn_nsp_rtt(struct sock *sk, long rtt)
{
struct dn_scp *scp = DN_SK(sk);
long srtt = (long)scp->nsp_srtt;
long rttvar = (long)scp->nsp_rttvar;
long delta;
/*
* If the jiffies clock flips over in the middle of timestamp
* gathering this value might turn out negative, so we make sure
* that is it always positive here.
*/
if (rtt < 0)
rtt = -rtt;
/*
* Add new rtt to smoothed average
*/
delta = ((rtt << 3) - srtt);
srtt += (delta >> 3);
if (srtt >= 1)
scp->nsp_srtt = (unsigned long)srtt;
else
scp->nsp_srtt = 1;
/*
* Add new rtt varience to smoothed varience
*/
delta >>= 1;
rttvar += ((((delta>0)?(delta):(-delta)) - rttvar) >> 2);
if (rttvar >= 1)
scp->nsp_rttvar = (unsigned long)rttvar;
else
scp->nsp_rttvar = 1;
/* printk(KERN_DEBUG "srtt=%lu rttvar=%lu\n", scp->nsp_srtt, scp->nsp_rttvar); */
}
/**
* dn_nsp_clone_and_send - Send a data packet by cloning it
* @skb: The packet to clone and transmit
* @gfp: memory allocation flag
*
* Clone a queued data or other data packet and transmit it.
*
* Returns: The number of times the packet has been sent previously
*/
static inline unsigned int dn_nsp_clone_and_send(struct sk_buff *skb,
gfp_t gfp)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct sk_buff *skb2;
int ret = 0;
if ((skb2 = skb_clone(skb, gfp)) != NULL) {
ret = cb->xmit_count;
cb->xmit_count++;
cb->stamp = jiffies;
skb2->sk = skb->sk;
dn_nsp_send(skb2);
}
return ret;
}
/**
* dn_nsp_output - Try and send something from socket queues
* @sk: The socket whose queues are to be investigated
*
* Try and send the packet on the end of the data and other data queues.
* Other data gets priority over data, and if we retransmit a packet we
* reduce the window by dividing it in two.
*
*/
void dn_nsp_output(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb;
unsigned int reduce_win = 0;
/*
* First we check for otherdata/linkservice messages
*/
if ((skb = skb_peek(&scp->other_xmit_queue)) != NULL)
reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
/*
* If we may not send any data, we don't.
* If we are still trying to get some other data down the
* channel, we don't try and send any data.
*/
if (reduce_win || (scp->flowrem_sw != DN_SEND))
goto recalc_window;
if ((skb = skb_peek(&scp->data_xmit_queue)) != NULL)
reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
/*
* If we've sent any frame more than once, we cut the
* send window size in half. There is always a minimum
* window size of one available.
*/
recalc_window:
if (reduce_win) {
scp->snd_window >>= 1;
if (scp->snd_window < NSP_MIN_WINDOW)
scp->snd_window = NSP_MIN_WINDOW;
}
}
int dn_nsp_xmit_timeout(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
dn_nsp_output(sk);
if (!skb_queue_empty(&scp->data_xmit_queue) ||
!skb_queue_empty(&scp->other_xmit_queue))
scp->persist = dn_nsp_persist(sk);
return 0;
}
static inline __le16 *dn_mk_common_header(struct dn_scp *scp, struct sk_buff *skb, unsigned char msgflag, int len)
{
unsigned char *ptr = skb_push(skb, len);
BUG_ON(len < 5);
*ptr++ = msgflag;
*((__le16 *)ptr) = scp->addrrem;
ptr += 2;
*((__le16 *)ptr) = scp->addrloc;
ptr += 2;
return (__le16 __force *)ptr;
}
static __le16 *dn_mk_ack_header(struct sock *sk, struct sk_buff *skb, unsigned char msgflag, int hlen, int other)
{
struct dn_scp *scp = DN_SK(sk);
unsigned short acknum = scp->numdat_rcv & 0x0FFF;
unsigned short ackcrs = scp->numoth_rcv & 0x0FFF;
__le16 *ptr;
BUG_ON(hlen < 9);
scp->ackxmt_dat = acknum;
scp->ackxmt_oth = ackcrs;
acknum |= 0x8000;
ackcrs |= 0x8000;
/* If this is an "other data/ack" message, swap acknum and ackcrs */
if (other) {
unsigned short tmp = acknum;
acknum = ackcrs;
ackcrs = tmp;
}
/* Set "cross subchannel" bit in ackcrs */
ackcrs |= 0x2000;
ptr = dn_mk_common_header(scp, skb, msgflag, hlen);
*ptr++ = cpu_to_le16(acknum);
*ptr++ = cpu_to_le16(ackcrs);
return ptr;
}
static __le16 *dn_nsp_mk_data_header(struct sock *sk, struct sk_buff *skb, int oth)
{
struct dn_scp *scp = DN_SK(sk);
struct dn_skb_cb *cb = DN_SKB_CB(skb);
__le16 *ptr = dn_mk_ack_header(sk, skb, cb->nsp_flags, 11, oth);
if (unlikely(oth)) {
cb->segnum = scp->numoth;
seq_add(&scp->numoth, 1);
} else {
cb->segnum = scp->numdat;
seq_add(&scp->numdat, 1);
}
*(ptr++) = cpu_to_le16(cb->segnum);
return ptr;
}
void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb,
gfp_t gfp, int oth)
{
struct dn_scp *scp = DN_SK(sk);
struct dn_skb_cb *cb = DN_SKB_CB(skb);
unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
cb->xmit_count = 0;
dn_nsp_mk_data_header(sk, skb, oth);
/*
* Slow start: If we have been idle for more than
* one RTT, then reset window to min size.
*/
if ((jiffies - scp->stamp) > t)
scp->snd_window = NSP_MIN_WINDOW;
if (oth)
skb_queue_tail(&scp->other_xmit_queue, skb);
else
skb_queue_tail(&scp->data_xmit_queue, skb);
if (scp->flowrem_sw != DN_SEND)
return;
dn_nsp_clone_and_send(skb, gfp);
}
int dn_nsp_check_xmit_queue(struct sock *sk, struct sk_buff *skb, struct sk_buff_head *q, unsigned short acknum)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb2, *n, *ack = NULL;
int wakeup = 0;
int try_retrans = 0;
unsigned long reftime = cb->stamp;
unsigned long pkttime;
unsigned short xmit_count;
unsigned short segnum;
skb_queue_walk_safe(q, skb2, n) {
struct dn_skb_cb *cb2 = DN_SKB_CB(skb2);
if (dn_before_or_equal(cb2->segnum, acknum))
ack = skb2;
/* printk(KERN_DEBUG "ack: %s %04x %04x\n", ack ? "ACK" : "SKIP", (int)cb2->segnum, (int)acknum); */
if (ack == NULL)
continue;
/* printk(KERN_DEBUG "check_xmit_queue: %04x, %d\n", acknum, cb2->xmit_count); */
/* Does _last_ packet acked have xmit_count > 1 */
try_retrans = 0;
/* Remember to wake up the sending process */
wakeup = 1;
/* Keep various statistics */
pkttime = cb2->stamp;
xmit_count = cb2->xmit_count;
segnum = cb2->segnum;
/* Remove and drop ack'ed packet */
skb_unlink(ack, q);
kfree_skb(ack);
ack = NULL;
/*
* We don't expect to see acknowledgements for packets we
* haven't sent yet.
*/
WARN_ON(xmit_count == 0);
/*
* If the packet has only been sent once, we can use it
* to calculate the RTT and also open the window a little
* further.
*/
if (xmit_count == 1) {
if (dn_equal(segnum, acknum))
dn_nsp_rtt(sk, (long)(pkttime - reftime));
if (scp->snd_window < scp->max_window)
scp->snd_window++;
}
/*
* Packet has been sent more than once. If this is the last
* packet to be acknowledged then we want to send the next
* packet in the send queue again (assumes the remote host does
* go-back-N error control).
*/
if (xmit_count > 1)
try_retrans = 1;
}
if (try_retrans)
dn_nsp_output(sk);
return wakeup;
}
void dn_nsp_send_data_ack(struct sock *sk)
{
struct sk_buff *skb = NULL;
if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
return;
skb_reserve(skb, 9);
dn_mk_ack_header(sk, skb, 0x04, 9, 0);
dn_nsp_send(skb);
}
void dn_nsp_send_oth_ack(struct sock *sk)
{
struct sk_buff *skb = NULL;
if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
return;
skb_reserve(skb, 9);
dn_mk_ack_header(sk, skb, 0x14, 9, 1);
dn_nsp_send(skb);
}
void dn_send_conn_ack (struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb = NULL;
struct nsp_conn_ack_msg *msg;
if ((skb = dn_alloc_skb(sk, 3, sk->sk_allocation)) == NULL)
return;
msg = (struct nsp_conn_ack_msg *)skb_put(skb, 3);
msg->msgflg = 0x24;
msg->dstaddr = scp->addrrem;
dn_nsp_send(skb);
}
void dn_nsp_delayed_ack(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
if (scp->ackxmt_oth != scp->numoth_rcv)
dn_nsp_send_oth_ack(sk);
if (scp->ackxmt_dat != scp->numdat_rcv)
dn_nsp_send_data_ack(sk);
}
static int dn_nsp_retrans_conn_conf(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
if (scp->state == DN_CC)
dn_send_conn_conf(sk, GFP_ATOMIC);
return 0;
}
void dn_send_conn_conf(struct sock *sk, gfp_t gfp)
{
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb = NULL;
struct nsp_conn_init_msg *msg;
__u8 len = (__u8)le16_to_cpu(scp->conndata_out.opt_optl);
if ((skb = dn_alloc_skb(sk, 50 + len, gfp)) == NULL)
return;
msg = (struct nsp_conn_init_msg *)skb_put(skb, sizeof(*msg));
msg->msgflg = 0x28;
msg->dstaddr = scp->addrrem;
msg->srcaddr = scp->addrloc;
msg->services = scp->services_loc;
msg->info = scp->info_loc;
msg->segsize = cpu_to_le16(scp->segsize_loc);
*skb_put(skb,1) = len;
if (len > 0)
memcpy(skb_put(skb, len), scp->conndata_out.opt_data, len);
dn_nsp_send(skb);
scp->persist = dn_nsp_persist(sk);
scp->persist_fxn = dn_nsp_retrans_conn_conf;
}
static __inline__ void dn_nsp_do_disc(struct sock *sk, unsigned char msgflg,
unsigned short reason, gfp_t gfp,
struct dst_entry *dst,
int ddl, unsigned char *dd, __le16 rem, __le16 loc)
{
struct sk_buff *skb = NULL;
int size = 7 + ddl + ((msgflg == NSP_DISCINIT) ? 1 : 0);
unsigned char *msg;
if ((dst == NULL) || (rem == 0)) {
net_dbg_ratelimited("DECnet: dn_nsp_do_disc: BUG! Please report this to SteveW@ACM.org rem=%u dst=%p\n",
le16_to_cpu(rem), dst);
return;
}
if ((skb = dn_alloc_skb(sk, size, gfp)) == NULL)
return;
msg = skb_put(skb, size);
*msg++ = msgflg;
*(__le16 *)msg = rem;
msg += 2;
*(__le16 *)msg = loc;
msg += 2;
*(__le16 *)msg = cpu_to_le16(reason);
msg += 2;
if (msgflg == NSP_DISCINIT)
*msg++ = ddl;
if (ddl) {
memcpy(msg, dd, ddl);
}
/*
* This doesn't go via the dn_nsp_send() function since we need
* to be able to send disc packets out which have no socket
* associations.
*/
skb_dst_set(skb, dst_clone(dst));
dst_output(skb);
}
void dn_nsp_send_disc(struct sock *sk, unsigned char msgflg,
unsigned short reason, gfp_t gfp)
{
struct dn_scp *scp = DN_SK(sk);
int ddl = 0;
if (msgflg == NSP_DISCINIT)
ddl = le16_to_cpu(scp->discdata_out.opt_optl);
if (reason == 0)
reason = le16_to_cpu(scp->discdata_out.opt_status);
dn_nsp_do_disc(sk, msgflg, reason, gfp, __sk_dst_get(sk), ddl,
scp->discdata_out.opt_data, scp->addrrem, scp->addrloc);
}
void dn_nsp_return_disc(struct sk_buff *skb, unsigned char msgflg,
unsigned short reason)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
int ddl = 0;
gfp_t gfp = GFP_ATOMIC;
dn_nsp_do_disc(NULL, msgflg, reason, gfp, skb_dst(skb), ddl,
NULL, cb->src_port, cb->dst_port);
}
void dn_nsp_send_link(struct sock *sk, unsigned char lsflags, char fcval)
{
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb;
unsigned char *ptr;
gfp_t gfp = GFP_ATOMIC;
if ((skb = dn_alloc_skb(sk, DN_MAX_NSP_DATA_HEADER + 2, gfp)) == NULL)
return;
skb_reserve(skb, DN_MAX_NSP_DATA_HEADER);
ptr = skb_put(skb, 2);
DN_SKB_CB(skb)->nsp_flags = 0x10;
*ptr++ = lsflags;
*ptr = fcval;
dn_nsp_queue_xmit(sk, skb, gfp, 1);
scp->persist = dn_nsp_persist(sk);
scp->persist_fxn = dn_nsp_xmit_timeout;
}
static int dn_nsp_retrans_conninit(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
if (scp->state == DN_CI)
dn_nsp_send_conninit(sk, NSP_RCI);
return 0;
}
void dn_nsp_send_conninit(struct sock *sk, unsigned char msgflg)
{
struct dn_scp *scp = DN_SK(sk);
struct nsp_conn_init_msg *msg;
unsigned char aux;
unsigned char menuver;
struct dn_skb_cb *cb;
unsigned char type = 1;
gfp_t allocation = (msgflg == NSP_CI) ? sk->sk_allocation : GFP_ATOMIC;
struct sk_buff *skb = dn_alloc_skb(sk, 200, allocation);
if (!skb)
return;
cb = DN_SKB_CB(skb);
msg = (struct nsp_conn_init_msg *)skb_put(skb,sizeof(*msg));
msg->msgflg = msgflg;
msg->dstaddr = 0x0000; /* Remote Node will assign it*/
msg->srcaddr = scp->addrloc;
msg->services = scp->services_loc; /* Requested flow control */
msg->info = scp->info_loc; /* Version Number */
msg->segsize = cpu_to_le16(scp->segsize_loc); /* Max segment size */
if (scp->peer.sdn_objnum)
type = 0;
skb_put(skb, dn_sockaddr2username(&scp->peer,
skb_tail_pointer(skb), type));
skb_put(skb, dn_sockaddr2username(&scp->addr,
skb_tail_pointer(skb), 2));
menuver = DN_MENUVER_ACC | DN_MENUVER_USR;
if (scp->peer.sdn_flags & SDF_PROXY)
menuver |= DN_MENUVER_PRX;
if (scp->peer.sdn_flags & SDF_UICPROXY)
menuver |= DN_MENUVER_UIC;
*skb_put(skb, 1) = menuver; /* Menu Version */
aux = scp->accessdata.acc_userl;
*skb_put(skb, 1) = aux;
if (aux > 0)
memcpy(skb_put(skb, aux), scp->accessdata.acc_user, aux);
aux = scp->accessdata.acc_passl;
*skb_put(skb, 1) = aux;
if (aux > 0)
memcpy(skb_put(skb, aux), scp->accessdata.acc_pass, aux);
aux = scp->accessdata.acc_accl;
*skb_put(skb, 1) = aux;
if (aux > 0)
memcpy(skb_put(skb, aux), scp->accessdata.acc_acc, aux);
aux = (__u8)le16_to_cpu(scp->conndata_out.opt_optl);
*skb_put(skb, 1) = aux;
if (aux > 0)
memcpy(skb_put(skb, aux), scp->conndata_out.opt_data, aux);
scp->persist = dn_nsp_persist(sk);
scp->persist_fxn = dn_nsp_retrans_conninit;
cb->rt_flags = DN_RT_F_RQR;
dn_nsp_send(skb);
}

1947
net/decnet/dn_route.c Normal file

File diff suppressed because it is too large Load diff

255
net/decnet/dn_rules.c Normal file
View file

@ -0,0 +1,255 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Forwarding Information Base (Rules)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
* Mostly copied from Alexey Kuznetsov's ipv4/fib_rules.c
*
*
* Changes:
* Steve Whitehouse <steve@chygwyn.com>
* Updated for Thomas Graf's generic rules
*
*/
#include <linux/net.h>
#include <linux/init.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/netdevice.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/export.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/fib_rules.h>
#include <net/dn.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
#include <net/dn_route.h>
static struct fib_rules_ops *dn_fib_rules_ops;
struct dn_fib_rule
{
struct fib_rule common;
unsigned char dst_len;
unsigned char src_len;
__le16 src;
__le16 srcmask;
__le16 dst;
__le16 dstmask;
__le16 srcmap;
u8 flags;
};
int dn_fib_lookup(struct flowidn *flp, struct dn_fib_res *res)
{
struct fib_lookup_arg arg = {
.result = res,
};
int err;
err = fib_rules_lookup(dn_fib_rules_ops,
flowidn_to_flowi(flp), 0, &arg);
res->r = arg.rule;
return err;
}
static int dn_fib_rule_action(struct fib_rule *rule, struct flowi *flp,
int flags, struct fib_lookup_arg *arg)
{
struct flowidn *fld = &flp->u.dn;
int err = -EAGAIN;
struct dn_fib_table *tbl;
switch(rule->action) {
case FR_ACT_TO_TBL:
break;
case FR_ACT_UNREACHABLE:
err = -ENETUNREACH;
goto errout;
case FR_ACT_PROHIBIT:
err = -EACCES;
goto errout;
case FR_ACT_BLACKHOLE:
default:
err = -EINVAL;
goto errout;
}
tbl = dn_fib_get_table(rule->table, 0);
if (tbl == NULL)
goto errout;
err = tbl->lookup(tbl, fld, (struct dn_fib_res *)arg->result);
if (err > 0)
err = -EAGAIN;
errout:
return err;
}
static const struct nla_policy dn_fib_rule_policy[FRA_MAX+1] = {
FRA_GENERIC_POLICY,
};
static int dn_fib_rule_match(struct fib_rule *rule, struct flowi *fl, int flags)
{
struct dn_fib_rule *r = (struct dn_fib_rule *)rule;
struct flowidn *fld = &fl->u.dn;
__le16 daddr = fld->daddr;
__le16 saddr = fld->saddr;
if (((saddr ^ r->src) & r->srcmask) ||
((daddr ^ r->dst) & r->dstmask))
return 0;
return 1;
}
static int dn_fib_rule_configure(struct fib_rule *rule, struct sk_buff *skb,
struct fib_rule_hdr *frh,
struct nlattr **tb)
{
int err = -EINVAL;
struct dn_fib_rule *r = (struct dn_fib_rule *)rule;
if (frh->tos)
goto errout;
if (rule->table == RT_TABLE_UNSPEC) {
if (rule->action == FR_ACT_TO_TBL) {
struct dn_fib_table *table;
table = dn_fib_empty_table();
if (table == NULL) {
err = -ENOBUFS;
goto errout;
}
rule->table = table->n;
}
}
if (frh->src_len)
r->src = nla_get_le16(tb[FRA_SRC]);
if (frh->dst_len)
r->dst = nla_get_le16(tb[FRA_DST]);
r->src_len = frh->src_len;
r->srcmask = dnet_make_mask(r->src_len);
r->dst_len = frh->dst_len;
r->dstmask = dnet_make_mask(r->dst_len);
err = 0;
errout:
return err;
}
static int dn_fib_rule_compare(struct fib_rule *rule, struct fib_rule_hdr *frh,
struct nlattr **tb)
{
struct dn_fib_rule *r = (struct dn_fib_rule *)rule;
if (frh->src_len && (r->src_len != frh->src_len))
return 0;
if (frh->dst_len && (r->dst_len != frh->dst_len))
return 0;
if (frh->src_len && (r->src != nla_get_le16(tb[FRA_SRC])))
return 0;
if (frh->dst_len && (r->dst != nla_get_le16(tb[FRA_DST])))
return 0;
return 1;
}
unsigned int dnet_addr_type(__le16 addr)
{
struct flowidn fld = { .daddr = addr };
struct dn_fib_res res;
unsigned int ret = RTN_UNICAST;
struct dn_fib_table *tb = dn_fib_get_table(RT_TABLE_LOCAL, 0);
res.r = NULL;
if (tb) {
if (!tb->lookup(tb, &fld, &res)) {
ret = res.type;
dn_fib_res_put(&res);
}
}
return ret;
}
static int dn_fib_rule_fill(struct fib_rule *rule, struct sk_buff *skb,
struct fib_rule_hdr *frh)
{
struct dn_fib_rule *r = (struct dn_fib_rule *)rule;
frh->dst_len = r->dst_len;
frh->src_len = r->src_len;
frh->tos = 0;
if ((r->dst_len &&
nla_put_le16(skb, FRA_DST, r->dst)) ||
(r->src_len &&
nla_put_le16(skb, FRA_SRC, r->src)))
goto nla_put_failure;
return 0;
nla_put_failure:
return -ENOBUFS;
}
static void dn_fib_rule_flush_cache(struct fib_rules_ops *ops)
{
dn_rt_cache_flush(-1);
}
static const struct fib_rules_ops __net_initconst dn_fib_rules_ops_template = {
.family = AF_DECnet,
.rule_size = sizeof(struct dn_fib_rule),
.addr_size = sizeof(u16),
.action = dn_fib_rule_action,
.match = dn_fib_rule_match,
.configure = dn_fib_rule_configure,
.compare = dn_fib_rule_compare,
.fill = dn_fib_rule_fill,
.default_pref = fib_default_rule_pref,
.flush_cache = dn_fib_rule_flush_cache,
.nlgroup = RTNLGRP_DECnet_RULE,
.policy = dn_fib_rule_policy,
.owner = THIS_MODULE,
.fro_net = &init_net,
};
void __init dn_fib_rules_init(void)
{
dn_fib_rules_ops =
fib_rules_register(&dn_fib_rules_ops_template, &init_net);
BUG_ON(IS_ERR(dn_fib_rules_ops));
BUG_ON(fib_default_rule_add(dn_fib_rules_ops, 0x7fff,
RT_TABLE_MAIN, 0));
}
void __exit dn_fib_rules_cleanup(void)
{
fib_rules_unregister(dn_fib_rules_ops);
rcu_barrier();
}

923
net/decnet/dn_table.c Normal file
View file

@ -0,0 +1,923 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Forwarding Information Base (Routing Tables)
*
* Author: Steve Whitehouse <SteveW@ACM.org>
* Mostly copied from the IPv4 routing code
*
*
* Changes:
*
*/
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/slab.h>
#include <linux/sockios.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <asm/uaccess.h>
#include <linux/route.h> /* RTF_xxx */
#include <net/neighbour.h>
#include <net/netlink.h>
#include <net/dst.h>
#include <net/flow.h>
#include <net/fib_rules.h>
#include <net/dn.h>
#include <net/dn_route.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
struct dn_zone
{
struct dn_zone *dz_next;
struct dn_fib_node **dz_hash;
int dz_nent;
int dz_divisor;
u32 dz_hashmask;
#define DZ_HASHMASK(dz) ((dz)->dz_hashmask)
int dz_order;
__le16 dz_mask;
#define DZ_MASK(dz) ((dz)->dz_mask)
};
struct dn_hash
{
struct dn_zone *dh_zones[17];
struct dn_zone *dh_zone_list;
};
#define dz_key_0(key) ((key).datum = 0)
#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
#define endfor_nexthops(fi) }
#define DN_MAX_DIVISOR 1024
#define DN_S_ZOMBIE 1
#define DN_S_ACCESSED 2
#define DN_FIB_SCAN(f, fp) \
for( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next)
#define DN_FIB_SCAN_KEY(f, fp, key) \
for( ; ((f) = *(fp)) != NULL && dn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next)
#define RT_TABLE_MIN 1
#define DN_FIB_TABLE_HASHSZ 256
static struct hlist_head dn_fib_table_hash[DN_FIB_TABLE_HASHSZ];
static DEFINE_RWLOCK(dn_fib_tables_lock);
static struct kmem_cache *dn_hash_kmem __read_mostly;
static int dn_fib_hash_zombies;
static inline dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz)
{
u16 h = le16_to_cpu(key.datum)>>(16 - dz->dz_order);
h ^= (h >> 10);
h ^= (h >> 6);
h &= DZ_HASHMASK(dz);
return *(dn_fib_idx_t *)&h;
}
static inline dn_fib_key_t dz_key(__le16 dst, struct dn_zone *dz)
{
dn_fib_key_t k;
k.datum = dst & DZ_MASK(dz);
return k;
}
static inline struct dn_fib_node **dn_chain_p(dn_fib_key_t key, struct dn_zone *dz)
{
return &dz->dz_hash[dn_hash(key, dz).datum];
}
static inline struct dn_fib_node *dz_chain(dn_fib_key_t key, struct dn_zone *dz)
{
return dz->dz_hash[dn_hash(key, dz).datum];
}
static inline int dn_key_eq(dn_fib_key_t a, dn_fib_key_t b)
{
return a.datum == b.datum;
}
static inline int dn_key_leq(dn_fib_key_t a, dn_fib_key_t b)
{
return a.datum <= b.datum;
}
static inline void dn_rebuild_zone(struct dn_zone *dz,
struct dn_fib_node **old_ht,
int old_divisor)
{
struct dn_fib_node *f, **fp, *next;
int i;
for(i = 0; i < old_divisor; i++) {
for(f = old_ht[i]; f; f = next) {
next = f->fn_next;
for(fp = dn_chain_p(f->fn_key, dz);
*fp && dn_key_leq((*fp)->fn_key, f->fn_key);
fp = &(*fp)->fn_next)
/* NOTHING */;
f->fn_next = *fp;
*fp = f;
}
}
}
static void dn_rehash_zone(struct dn_zone *dz)
{
struct dn_fib_node **ht, **old_ht;
int old_divisor, new_divisor;
u32 new_hashmask;
old_divisor = dz->dz_divisor;
switch (old_divisor) {
case 16:
new_divisor = 256;
new_hashmask = 0xFF;
break;
default:
printk(KERN_DEBUG "DECnet: dn_rehash_zone: BUG! %d\n",
old_divisor);
case 256:
new_divisor = 1024;
new_hashmask = 0x3FF;
break;
}
ht = kcalloc(new_divisor, sizeof(struct dn_fib_node*), GFP_KERNEL);
if (ht == NULL)
return;
write_lock_bh(&dn_fib_tables_lock);
old_ht = dz->dz_hash;
dz->dz_hash = ht;
dz->dz_hashmask = new_hashmask;
dz->dz_divisor = new_divisor;
dn_rebuild_zone(dz, old_ht, old_divisor);
write_unlock_bh(&dn_fib_tables_lock);
kfree(old_ht);
}
static void dn_free_node(struct dn_fib_node *f)
{
dn_fib_release_info(DN_FIB_INFO(f));
kmem_cache_free(dn_hash_kmem, f);
}
static struct dn_zone *dn_new_zone(struct dn_hash *table, int z)
{
int i;
struct dn_zone *dz = kzalloc(sizeof(struct dn_zone), GFP_KERNEL);
if (!dz)
return NULL;
if (z) {
dz->dz_divisor = 16;
dz->dz_hashmask = 0x0F;
} else {
dz->dz_divisor = 1;
dz->dz_hashmask = 0;
}
dz->dz_hash = kcalloc(dz->dz_divisor, sizeof(struct dn_fib_node *), GFP_KERNEL);
if (!dz->dz_hash) {
kfree(dz);
return NULL;
}
dz->dz_order = z;
dz->dz_mask = dnet_make_mask(z);
for(i = z + 1; i <= 16; i++)
if (table->dh_zones[i])
break;
write_lock_bh(&dn_fib_tables_lock);
if (i>16) {
dz->dz_next = table->dh_zone_list;
table->dh_zone_list = dz;
} else {
dz->dz_next = table->dh_zones[i]->dz_next;
table->dh_zones[i]->dz_next = dz;
}
table->dh_zones[z] = dz;
write_unlock_bh(&dn_fib_tables_lock);
return dz;
}
static int dn_fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct nlattr *attrs[], struct dn_fib_info *fi)
{
struct rtnexthop *nhp;
int nhlen;
if (attrs[RTA_PRIORITY] &&
nla_get_u32(attrs[RTA_PRIORITY]) != fi->fib_priority)
return 1;
if (attrs[RTA_OIF] || attrs[RTA_GATEWAY]) {
if ((!attrs[RTA_OIF] || nla_get_u32(attrs[RTA_OIF]) == fi->fib_nh->nh_oif) &&
(!attrs[RTA_GATEWAY] || nla_get_le16(attrs[RTA_GATEWAY]) != fi->fib_nh->nh_gw))
return 0;
return 1;
}
if (!attrs[RTA_MULTIPATH])
return 0;
nhp = nla_data(attrs[RTA_MULTIPATH]);
nhlen = nla_len(attrs[RTA_MULTIPATH]);
for_nexthops(fi) {
int attrlen = nhlen - sizeof(struct rtnexthop);
__le16 gw;
if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
return -EINVAL;
if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif)
return 1;
if (attrlen) {
struct nlattr *gw_attr;
gw_attr = nla_find((struct nlattr *) (nhp + 1), attrlen, RTA_GATEWAY);
gw = gw_attr ? nla_get_le16(gw_attr) : 0;
if (gw && gw != nh->nh_gw)
return 1;
}
nhp = RTNH_NEXT(nhp);
} endfor_nexthops(fi);
return 0;
}
static inline size_t dn_fib_nlmsg_size(struct dn_fib_info *fi)
{
size_t payload = NLMSG_ALIGN(sizeof(struct rtmsg))
+ nla_total_size(4) /* RTA_TABLE */
+ nla_total_size(2) /* RTA_DST */
+ nla_total_size(4); /* RTA_PRIORITY */
/* space for nested metrics */
payload += nla_total_size((RTAX_MAX * nla_total_size(4)));
if (fi->fib_nhs) {
/* Also handles the special case fib_nhs == 1 */
/* each nexthop is packed in an attribute */
size_t nhsize = nla_total_size(sizeof(struct rtnexthop));
/* may contain a gateway attribute */
nhsize += nla_total_size(4);
/* all nexthops are packed in a nested attribute */
payload += nla_total_size(fi->fib_nhs * nhsize);
}
return payload;
}
static int dn_fib_dump_info(struct sk_buff *skb, u32 portid, u32 seq, int event,
u32 tb_id, u8 type, u8 scope, void *dst, int dst_len,
struct dn_fib_info *fi, unsigned int flags)
{
struct rtmsg *rtm;
struct nlmsghdr *nlh;
nlh = nlmsg_put(skb, portid, seq, event, sizeof(*rtm), flags);
if (!nlh)
return -EMSGSIZE;
rtm = nlmsg_data(nlh);
rtm->rtm_family = AF_DECnet;
rtm->rtm_dst_len = dst_len;
rtm->rtm_src_len = 0;
rtm->rtm_tos = 0;
rtm->rtm_table = tb_id;
rtm->rtm_flags = fi->fib_flags;
rtm->rtm_scope = scope;
rtm->rtm_type = type;
rtm->rtm_protocol = fi->fib_protocol;
if (nla_put_u32(skb, RTA_TABLE, tb_id) < 0)
goto errout;
if (rtm->rtm_dst_len &&
nla_put(skb, RTA_DST, 2, dst) < 0)
goto errout;
if (fi->fib_priority &&
nla_put_u32(skb, RTA_PRIORITY, fi->fib_priority) < 0)
goto errout;
if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)
goto errout;
if (fi->fib_nhs == 1) {
if (fi->fib_nh->nh_gw &&
nla_put_le16(skb, RTA_GATEWAY, fi->fib_nh->nh_gw) < 0)
goto errout;
if (fi->fib_nh->nh_oif &&
nla_put_u32(skb, RTA_OIF, fi->fib_nh->nh_oif) < 0)
goto errout;
}
if (fi->fib_nhs > 1) {
struct rtnexthop *nhp;
struct nlattr *mp_head;
if (!(mp_head = nla_nest_start(skb, RTA_MULTIPATH)))
goto errout;
for_nexthops(fi) {
if (!(nhp = nla_reserve_nohdr(skb, sizeof(*nhp))))
goto errout;
nhp->rtnh_flags = nh->nh_flags & 0xFF;
nhp->rtnh_hops = nh->nh_weight - 1;
nhp->rtnh_ifindex = nh->nh_oif;
if (nh->nh_gw &&
nla_put_le16(skb, RTA_GATEWAY, nh->nh_gw) < 0)
goto errout;
nhp->rtnh_len = skb_tail_pointer(skb) - (unsigned char *)nhp;
} endfor_nexthops(fi);
nla_nest_end(skb, mp_head);
}
return nlmsg_end(skb, nlh);
errout:
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
static void dn_rtmsg_fib(int event, struct dn_fib_node *f, int z, u32 tb_id,
struct nlmsghdr *nlh, struct netlink_skb_parms *req)
{
struct sk_buff *skb;
u32 portid = req ? req->portid : 0;
int err = -ENOBUFS;
skb = nlmsg_new(dn_fib_nlmsg_size(DN_FIB_INFO(f)), GFP_KERNEL);
if (skb == NULL)
goto errout;
err = dn_fib_dump_info(skb, portid, nlh->nlmsg_seq, event, tb_id,
f->fn_type, f->fn_scope, &f->fn_key, z,
DN_FIB_INFO(f), 0);
if (err < 0) {
/* -EMSGSIZE implies BUG in dn_fib_nlmsg_size() */
WARN_ON(err == -EMSGSIZE);
kfree_skb(skb);
goto errout;
}
rtnl_notify(skb, &init_net, portid, RTNLGRP_DECnet_ROUTE, nlh, GFP_KERNEL);
return;
errout:
if (err < 0)
rtnl_set_sk_err(&init_net, RTNLGRP_DECnet_ROUTE, err);
}
static __inline__ int dn_hash_dump_bucket(struct sk_buff *skb,
struct netlink_callback *cb,
struct dn_fib_table *tb,
struct dn_zone *dz,
struct dn_fib_node *f)
{
int i, s_i;
s_i = cb->args[4];
for(i = 0; f; i++, f = f->fn_next) {
if (i < s_i)
continue;
if (f->fn_state & DN_S_ZOMBIE)
continue;
if (dn_fib_dump_info(skb, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq,
RTM_NEWROUTE,
tb->n,
(f->fn_state & DN_S_ZOMBIE) ? 0 : f->fn_type,
f->fn_scope, &f->fn_key, dz->dz_order,
f->fn_info, NLM_F_MULTI) < 0) {
cb->args[4] = i;
return -1;
}
}
cb->args[4] = i;
return skb->len;
}
static __inline__ int dn_hash_dump_zone(struct sk_buff *skb,
struct netlink_callback *cb,
struct dn_fib_table *tb,
struct dn_zone *dz)
{
int h, s_h;
s_h = cb->args[3];
for(h = 0; h < dz->dz_divisor; h++) {
if (h < s_h)
continue;
if (h > s_h)
memset(&cb->args[4], 0, sizeof(cb->args) - 4*sizeof(cb->args[0]));
if (dz->dz_hash == NULL || dz->dz_hash[h] == NULL)
continue;
if (dn_hash_dump_bucket(skb, cb, tb, dz, dz->dz_hash[h]) < 0) {
cb->args[3] = h;
return -1;
}
}
cb->args[3] = h;
return skb->len;
}
static int dn_fib_table_dump(struct dn_fib_table *tb, struct sk_buff *skb,
struct netlink_callback *cb)
{
int m, s_m;
struct dn_zone *dz;
struct dn_hash *table = (struct dn_hash *)tb->data;
s_m = cb->args[2];
read_lock(&dn_fib_tables_lock);
for(dz = table->dh_zone_list, m = 0; dz; dz = dz->dz_next, m++) {
if (m < s_m)
continue;
if (m > s_m)
memset(&cb->args[3], 0, sizeof(cb->args) - 3*sizeof(cb->args[0]));
if (dn_hash_dump_zone(skb, cb, tb, dz) < 0) {
cb->args[2] = m;
read_unlock(&dn_fib_tables_lock);
return -1;
}
}
read_unlock(&dn_fib_tables_lock);
cb->args[2] = m;
return skb->len;
}
int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
struct net *net = sock_net(skb->sk);
unsigned int h, s_h;
unsigned int e = 0, s_e;
struct dn_fib_table *tb;
int dumped = 0;
if (!net_eq(net, &init_net))
return 0;
if (nlmsg_len(cb->nlh) >= sizeof(struct rtmsg) &&
((struct rtmsg *)nlmsg_data(cb->nlh))->rtm_flags&RTM_F_CLONED)
return dn_cache_dump(skb, cb);
s_h = cb->args[0];
s_e = cb->args[1];
for (h = s_h; h < DN_FIB_TABLE_HASHSZ; h++, s_h = 0) {
e = 0;
hlist_for_each_entry(tb, &dn_fib_table_hash[h], hlist) {
if (e < s_e)
goto next;
if (dumped)
memset(&cb->args[2], 0, sizeof(cb->args) -
2 * sizeof(cb->args[0]));
if (tb->dump(tb, skb, cb) < 0)
goto out;
dumped = 1;
next:
e++;
}
}
out:
cb->args[1] = e;
cb->args[0] = h;
return skb->len;
}
static int dn_fib_table_insert(struct dn_fib_table *tb, struct rtmsg *r, struct nlattr *attrs[],
struct nlmsghdr *n, struct netlink_skb_parms *req)
{
struct dn_hash *table = (struct dn_hash *)tb->data;
struct dn_fib_node *new_f, *f, **fp, **del_fp;
struct dn_zone *dz;
struct dn_fib_info *fi;
int z = r->rtm_dst_len;
int type = r->rtm_type;
dn_fib_key_t key;
int err;
if (z > 16)
return -EINVAL;
dz = table->dh_zones[z];
if (!dz && !(dz = dn_new_zone(table, z)))
return -ENOBUFS;
dz_key_0(key);
if (attrs[RTA_DST]) {
__le16 dst = nla_get_le16(attrs[RTA_DST]);
if (dst & ~DZ_MASK(dz))
return -EINVAL;
key = dz_key(dst, dz);
}
if ((fi = dn_fib_create_info(r, attrs, n, &err)) == NULL)
return err;
if (dz->dz_nent > (dz->dz_divisor << 2) &&
dz->dz_divisor > DN_MAX_DIVISOR &&
(z==16 || (1<<z) > dz->dz_divisor))
dn_rehash_zone(dz);
fp = dn_chain_p(key, dz);
DN_FIB_SCAN(f, fp) {
if (dn_key_leq(key, f->fn_key))
break;
}
del_fp = NULL;
if (f && (f->fn_state & DN_S_ZOMBIE) &&
dn_key_eq(f->fn_key, key)) {
del_fp = fp;
fp = &f->fn_next;
f = *fp;
goto create;
}
DN_FIB_SCAN_KEY(f, fp, key) {
if (fi->fib_priority <= DN_FIB_INFO(f)->fib_priority)
break;
}
if (f && dn_key_eq(f->fn_key, key) &&
fi->fib_priority == DN_FIB_INFO(f)->fib_priority) {
struct dn_fib_node **ins_fp;
err = -EEXIST;
if (n->nlmsg_flags & NLM_F_EXCL)
goto out;
if (n->nlmsg_flags & NLM_F_REPLACE) {
del_fp = fp;
fp = &f->fn_next;
f = *fp;
goto replace;
}
ins_fp = fp;
err = -EEXIST;
DN_FIB_SCAN_KEY(f, fp, key) {
if (fi->fib_priority != DN_FIB_INFO(f)->fib_priority)
break;
if (f->fn_type == type &&
f->fn_scope == r->rtm_scope &&
DN_FIB_INFO(f) == fi)
goto out;
}
if (!(n->nlmsg_flags & NLM_F_APPEND)) {
fp = ins_fp;
f = *fp;
}
}
create:
err = -ENOENT;
if (!(n->nlmsg_flags & NLM_F_CREATE))
goto out;
replace:
err = -ENOBUFS;
new_f = kmem_cache_zalloc(dn_hash_kmem, GFP_KERNEL);
if (new_f == NULL)
goto out;
new_f->fn_key = key;
new_f->fn_type = type;
new_f->fn_scope = r->rtm_scope;
DN_FIB_INFO(new_f) = fi;
new_f->fn_next = f;
write_lock_bh(&dn_fib_tables_lock);
*fp = new_f;
write_unlock_bh(&dn_fib_tables_lock);
dz->dz_nent++;
if (del_fp) {
f = *del_fp;
write_lock_bh(&dn_fib_tables_lock);
*del_fp = f->fn_next;
write_unlock_bh(&dn_fib_tables_lock);
if (!(f->fn_state & DN_S_ZOMBIE))
dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
if (f->fn_state & DN_S_ACCESSED)
dn_rt_cache_flush(-1);
dn_free_node(f);
dz->dz_nent--;
} else {
dn_rt_cache_flush(-1);
}
dn_rtmsg_fib(RTM_NEWROUTE, new_f, z, tb->n, n, req);
return 0;
out:
dn_fib_release_info(fi);
return err;
}
static int dn_fib_table_delete(struct dn_fib_table *tb, struct rtmsg *r, struct nlattr *attrs[],
struct nlmsghdr *n, struct netlink_skb_parms *req)
{
struct dn_hash *table = (struct dn_hash*)tb->data;
struct dn_fib_node **fp, **del_fp, *f;
int z = r->rtm_dst_len;
struct dn_zone *dz;
dn_fib_key_t key;
int matched;
if (z > 16)
return -EINVAL;
if ((dz = table->dh_zones[z]) == NULL)
return -ESRCH;
dz_key_0(key);
if (attrs[RTA_DST]) {
__le16 dst = nla_get_le16(attrs[RTA_DST]);
if (dst & ~DZ_MASK(dz))
return -EINVAL;
key = dz_key(dst, dz);
}
fp = dn_chain_p(key, dz);
DN_FIB_SCAN(f, fp) {
if (dn_key_eq(f->fn_key, key))
break;
if (dn_key_leq(key, f->fn_key))
return -ESRCH;
}
matched = 0;
del_fp = NULL;
DN_FIB_SCAN_KEY(f, fp, key) {
struct dn_fib_info *fi = DN_FIB_INFO(f);
if (f->fn_state & DN_S_ZOMBIE)
return -ESRCH;
matched++;
if (del_fp == NULL &&
(!r->rtm_type || f->fn_type == r->rtm_type) &&
(r->rtm_scope == RT_SCOPE_NOWHERE || f->fn_scope == r->rtm_scope) &&
(!r->rtm_protocol ||
fi->fib_protocol == r->rtm_protocol) &&
dn_fib_nh_match(r, n, attrs, fi) == 0)
del_fp = fp;
}
if (del_fp) {
f = *del_fp;
dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
if (matched != 1) {
write_lock_bh(&dn_fib_tables_lock);
*del_fp = f->fn_next;
write_unlock_bh(&dn_fib_tables_lock);
if (f->fn_state & DN_S_ACCESSED)
dn_rt_cache_flush(-1);
dn_free_node(f);
dz->dz_nent--;
} else {
f->fn_state |= DN_S_ZOMBIE;
if (f->fn_state & DN_S_ACCESSED) {
f->fn_state &= ~DN_S_ACCESSED;
dn_rt_cache_flush(-1);
}
if (++dn_fib_hash_zombies > 128)
dn_fib_flush();
}
return 0;
}
return -ESRCH;
}
static inline int dn_flush_list(struct dn_fib_node **fp, int z, struct dn_hash *table)
{
int found = 0;
struct dn_fib_node *f;
while((f = *fp) != NULL) {
struct dn_fib_info *fi = DN_FIB_INFO(f);
if (fi && ((f->fn_state & DN_S_ZOMBIE) || (fi->fib_flags & RTNH_F_DEAD))) {
write_lock_bh(&dn_fib_tables_lock);
*fp = f->fn_next;
write_unlock_bh(&dn_fib_tables_lock);
dn_free_node(f);
found++;
continue;
}
fp = &f->fn_next;
}
return found;
}
static int dn_fib_table_flush(struct dn_fib_table *tb)
{
struct dn_hash *table = (struct dn_hash *)tb->data;
struct dn_zone *dz;
int found = 0;
dn_fib_hash_zombies = 0;
for(dz = table->dh_zone_list; dz; dz = dz->dz_next) {
int i;
int tmp = 0;
for(i = dz->dz_divisor-1; i >= 0; i--)
tmp += dn_flush_list(&dz->dz_hash[i], dz->dz_order, table);
dz->dz_nent -= tmp;
found += tmp;
}
return found;
}
static int dn_fib_table_lookup(struct dn_fib_table *tb, const struct flowidn *flp, struct dn_fib_res *res)
{
int err;
struct dn_zone *dz;
struct dn_hash *t = (struct dn_hash *)tb->data;
read_lock(&dn_fib_tables_lock);
for(dz = t->dh_zone_list; dz; dz = dz->dz_next) {
struct dn_fib_node *f;
dn_fib_key_t k = dz_key(flp->daddr, dz);
for(f = dz_chain(k, dz); f; f = f->fn_next) {
if (!dn_key_eq(k, f->fn_key)) {
if (dn_key_leq(k, f->fn_key))
break;
else
continue;
}
f->fn_state |= DN_S_ACCESSED;
if (f->fn_state&DN_S_ZOMBIE)
continue;
if (f->fn_scope < flp->flowidn_scope)
continue;
err = dn_fib_semantic_match(f->fn_type, DN_FIB_INFO(f), flp, res);
if (err == 0) {
res->type = f->fn_type;
res->scope = f->fn_scope;
res->prefixlen = dz->dz_order;
goto out;
}
if (err < 0)
goto out;
}
}
err = 1;
out:
read_unlock(&dn_fib_tables_lock);
return err;
}
struct dn_fib_table *dn_fib_get_table(u32 n, int create)
{
struct dn_fib_table *t;
unsigned int h;
if (n < RT_TABLE_MIN)
return NULL;
if (n > RT_TABLE_MAX)
return NULL;
h = n & (DN_FIB_TABLE_HASHSZ - 1);
rcu_read_lock();
hlist_for_each_entry_rcu(t, &dn_fib_table_hash[h], hlist) {
if (t->n == n) {
rcu_read_unlock();
return t;
}
}
rcu_read_unlock();
if (!create)
return NULL;
if (in_interrupt()) {
net_dbg_ratelimited("DECnet: BUG! Attempt to create routing table from interrupt\n");
return NULL;
}
t = kzalloc(sizeof(struct dn_fib_table) + sizeof(struct dn_hash),
GFP_KERNEL);
if (t == NULL)
return NULL;
t->n = n;
t->insert = dn_fib_table_insert;
t->delete = dn_fib_table_delete;
t->lookup = dn_fib_table_lookup;
t->flush = dn_fib_table_flush;
t->dump = dn_fib_table_dump;
hlist_add_head_rcu(&t->hlist, &dn_fib_table_hash[h]);
return t;
}
struct dn_fib_table *dn_fib_empty_table(void)
{
u32 id;
for(id = RT_TABLE_MIN; id <= RT_TABLE_MAX; id++)
if (dn_fib_get_table(id, 0) == NULL)
return dn_fib_get_table(id, 1);
return NULL;
}
void dn_fib_flush(void)
{
int flushed = 0;
struct dn_fib_table *tb;
unsigned int h;
for (h = 0; h < DN_FIB_TABLE_HASHSZ; h++) {
hlist_for_each_entry(tb, &dn_fib_table_hash[h], hlist)
flushed += tb->flush(tb);
}
if (flushed)
dn_rt_cache_flush(-1);
}
void __init dn_fib_table_init(void)
{
dn_hash_kmem = kmem_cache_create("dn_fib_info_cache",
sizeof(struct dn_fib_info),
0, SLAB_HWCACHE_ALIGN,
NULL);
}
void __exit dn_fib_table_cleanup(void)
{
struct dn_fib_table *t;
struct hlist_node *next;
unsigned int h;
write_lock(&dn_fib_tables_lock);
for (h = 0; h < DN_FIB_TABLE_HASHSZ; h++) {
hlist_for_each_entry_safe(t, next, &dn_fib_table_hash[h],
hlist) {
hlist_del(&t->hlist);
kfree(t);
}
}
write_unlock(&dn_fib_tables_lock);
}

103
net/decnet/dn_timer.c Normal file
View file

@ -0,0 +1,103 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Socket Timer Functions
*
* Author: Steve Whitehouse <SteveW@ACM.org>
*
*
* Changes:
* Steve Whitehouse : Made keepalive timer part of the same
* timer idea.
* Steve Whitehouse : Added checks for sk->sock_readers
* David S. Miller : New socket locking
* Steve Whitehouse : Timer grabs socket ref.
*/
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <net/sock.h>
#include <linux/atomic.h>
#include <linux/jiffies.h>
#include <net/flow.h>
#include <net/dn.h>
/*
* Slow timer is for everything else (n * 500mS)
*/
#define SLOW_INTERVAL (HZ/2)
static void dn_slow_timer(unsigned long arg);
void dn_start_slow_timer(struct sock *sk)
{
setup_timer(&sk->sk_timer, dn_slow_timer, (unsigned long)sk);
sk_reset_timer(sk, &sk->sk_timer, jiffies + SLOW_INTERVAL);
}
void dn_stop_slow_timer(struct sock *sk)
{
sk_stop_timer(sk, &sk->sk_timer);
}
static void dn_slow_timer(unsigned long arg)
{
struct sock *sk = (struct sock *)arg;
struct dn_scp *scp = DN_SK(sk);
bh_lock_sock(sk);
if (sock_owned_by_user(sk)) {
sk_reset_timer(sk, &sk->sk_timer, jiffies + HZ / 10);
goto out;
}
/*
* The persist timer is the standard slow timer used for retransmits
* in both connection establishment and disconnection as well as
* in the RUN state. The different states are catered for by changing
* the function pointer in the socket. Setting the timer to a value
* of zero turns it off. We allow the persist_fxn to turn the
* timer off in a permant way by returning non-zero, so that
* timer based routines may remove sockets. This is why we have a
* sock_hold()/sock_put() around the timer to prevent the socket
* going away in the middle.
*/
if (scp->persist && scp->persist_fxn) {
if (scp->persist <= SLOW_INTERVAL) {
scp->persist = 0;
if (scp->persist_fxn(sk))
goto out;
} else {
scp->persist -= SLOW_INTERVAL;
}
}
/*
* Check for keepalive timeout. After the other timer 'cos if
* the previous timer caused a retransmit, we don't need to
* do this. scp->stamp is the last time that we sent a packet.
* The keepalive function sends a link service packet to the
* other end. If it remains unacknowledged, the standard
* socket timers will eventually shut the socket down. Each
* time we do this, scp->stamp will be updated, thus
* we won't try and send another until scp->keepalive has passed
* since the last successful transmission.
*/
if (scp->keepalive && scp->keepalive_fxn && (scp->state == DN_RUN)) {
if (time_after_eq(jiffies, scp->stamp + scp->keepalive))
scp->keepalive_fxn(sk);
}
sk_reset_timer(sk, &sk->sk_timer, jiffies + SLOW_INTERVAL);
out:
bh_unlock_sock(sk);
sock_put(sk);
}

View file

@ -0,0 +1,16 @@
#
# DECnet netfilter configuration
#
menu "DECnet: Netfilter Configuration"
depends on DECNET && NETFILTER
depends on NETFILTER_ADVANCED
config DECNET_NF_GRABULATOR
tristate "Routing message grabulator (for userland routing daemon)"
help
Enable this module if you want to use the userland DECnet routing
daemon. You will also need to enable routing support for DECnet
unless you just want to monitor routing messages from other nodes.
endmenu

View file

@ -0,0 +1,6 @@
#
# Makefile for DECnet netfilter modules
#
obj-$(CONFIG_DECNET_NF_GRABULATOR) += dn_rtmsg.o

View file

@ -0,0 +1,161 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet Routing Message Grabulator
*
* (C) 2000 ChyGwyn Limited - http://www.chygwyn.com/
* This code may be copied under the GPL v.2 or at your option
* any later version.
*
* Author: Steven Whitehouse <steve@chygwyn.com>
*
*/
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/spinlock.h>
#include <net/netlink.h>
#include <linux/netfilter_decnet.h>
#include <net/sock.h>
#include <net/flow.h>
#include <net/dn.h>
#include <net/dn_route.h>
static struct sock *dnrmg = NULL;
static struct sk_buff *dnrmg_build_message(struct sk_buff *rt_skb, int *errp)
{
struct sk_buff *skb = NULL;
size_t size;
sk_buff_data_t old_tail;
struct nlmsghdr *nlh;
unsigned char *ptr;
struct nf_dn_rtmsg *rtm;
size = NLMSG_ALIGN(rt_skb->len) +
NLMSG_ALIGN(sizeof(struct nf_dn_rtmsg));
skb = nlmsg_new(size, GFP_ATOMIC);
if (!skb) {
*errp = -ENOMEM;
return NULL;
}
old_tail = skb->tail;
nlh = nlmsg_put(skb, 0, 0, 0, size, 0);
if (!nlh) {
kfree_skb(skb);
*errp = -ENOMEM;
return NULL;
}
rtm = (struct nf_dn_rtmsg *)nlmsg_data(nlh);
rtm->nfdn_ifindex = rt_skb->dev->ifindex;
ptr = NFDN_RTMSG(rtm);
skb_copy_from_linear_data(rt_skb, ptr, rt_skb->len);
nlh->nlmsg_len = skb->tail - old_tail;
return skb;
}
static void dnrmg_send_peer(struct sk_buff *skb)
{
struct sk_buff *skb2;
int status = 0;
int group = 0;
unsigned char flags = *skb->data;
switch (flags & DN_RT_CNTL_MSK) {
case DN_RT_PKT_L1RT:
group = DNRNG_NLGRP_L1;
break;
case DN_RT_PKT_L2RT:
group = DNRNG_NLGRP_L2;
break;
default:
return;
}
skb2 = dnrmg_build_message(skb, &status);
if (skb2 == NULL)
return;
NETLINK_CB(skb2).dst_group = group;
netlink_broadcast(dnrmg, skb2, 0, group, GFP_ATOMIC);
}
static unsigned int dnrmg_hook(const struct nf_hook_ops *ops,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
dnrmg_send_peer(skb);
return NF_ACCEPT;
}
#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)
static inline void dnrmg_receive_user_skb(struct sk_buff *skb)
{
struct nlmsghdr *nlh = nlmsg_hdr(skb);
if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
return;
if (!netlink_capable(skb, CAP_NET_ADMIN))
RCV_SKB_FAIL(-EPERM);
/* Eventually we might send routing messages too */
RCV_SKB_FAIL(-EINVAL);
}
static struct nf_hook_ops dnrmg_ops __read_mostly = {
.hook = dnrmg_hook,
.pf = NFPROTO_DECNET,
.hooknum = NF_DN_ROUTE,
.priority = NF_DN_PRI_DNRTMSG,
};
static int __init dn_rtmsg_init(void)
{
int rv = 0;
struct netlink_kernel_cfg cfg = {
.groups = DNRNG_NLGRP_MAX,
.input = dnrmg_receive_user_skb,
};
dnrmg = netlink_kernel_create(&init_net, NETLINK_DNRTMSG, &cfg);
if (dnrmg == NULL) {
printk(KERN_ERR "dn_rtmsg: Cannot create netlink socket");
return -ENOMEM;
}
rv = nf_register_hook(&dnrmg_ops);
if (rv) {
netlink_kernel_release(dnrmg);
}
return rv;
}
static void __exit dn_rtmsg_fini(void)
{
nf_unregister_hook(&dnrmg_ops);
netlink_kernel_release(dnrmg);
}
MODULE_DESCRIPTION("DECnet Routing Message Grabulator");
MODULE_AUTHOR("Steven Whitehouse <steve@chygwyn.com>");
MODULE_LICENSE("GPL");
MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_DNRTMSG);
module_init(dn_rtmsg_init);
module_exit(dn_rtmsg_fini);

View file

@ -0,0 +1,372 @@
/*
* DECnet An implementation of the DECnet protocol suite for the LINUX
* operating system. DECnet is implemented using the BSD Socket
* interface as the means of communication with the user level.
*
* DECnet sysctl support functions
*
* Author: Steve Whitehouse <SteveW@ACM.org>
*
*
* Changes:
* Steve Whitehouse - C99 changes and default device handling
* Steve Whitehouse - Memory buffer settings, like the tcp ones
*
*/
#include <linux/mm.h>
#include <linux/sysctl.h>
#include <linux/fs.h>
#include <linux/netdevice.h>
#include <linux/string.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/flow.h>
#include <asm/uaccess.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_route.h>
int decnet_debug_level;
int decnet_time_wait = 30;
int decnet_dn_count = 1;
int decnet_di_count = 3;
int decnet_dr_count = 3;
int decnet_log_martians = 1;
int decnet_no_fc_max_cwnd = NSP_MIN_WINDOW;
/* Reasonable defaults, I hope, based on tcp's defaults */
long sysctl_decnet_mem[3] = { 768 << 3, 1024 << 3, 1536 << 3 };
int sysctl_decnet_wmem[3] = { 4 * 1024, 16 * 1024, 128 * 1024 };
int sysctl_decnet_rmem[3] = { 4 * 1024, 87380, 87380 * 2 };
#ifdef CONFIG_SYSCTL
extern int decnet_dst_gc_interval;
static int min_decnet_time_wait[] = { 5 };
static int max_decnet_time_wait[] = { 600 };
static int min_state_count[] = { 1 };
static int max_state_count[] = { NSP_MAXRXTSHIFT };
static int min_decnet_dst_gc_interval[] = { 1 };
static int max_decnet_dst_gc_interval[] = { 60 };
static int min_decnet_no_fc_max_cwnd[] = { NSP_MIN_WINDOW };
static int max_decnet_no_fc_max_cwnd[] = { NSP_MAX_WINDOW };
static char node_name[7] = "???";
static struct ctl_table_header *dn_table_header = NULL;
/*
* ctype.h :-)
*/
#define ISNUM(x) (((x) >= '0') && ((x) <= '9'))
#define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z'))
#define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z'))
#define ISALPHA(x) (ISLOWER(x) || ISUPPER(x))
#define INVALID_END_CHAR(x) (ISNUM(x) || ISALPHA(x))
static void strip_it(char *str)
{
for(;;) {
switch (*str) {
case ' ':
case '\n':
case '\r':
case ':':
*str = 0;
/* Fallthrough */
case 0:
return;
}
str++;
}
}
/*
* Simple routine to parse an ascii DECnet address
* into a network order address.
*/
static int parse_addr(__le16 *addr, char *str)
{
__u16 area, node;
while(*str && !ISNUM(*str)) str++;
if (*str == 0)
return -1;
area = (*str++ - '0');
if (ISNUM(*str)) {
area *= 10;
area += (*str++ - '0');
}
if (*str++ != '.')
return -1;
if (!ISNUM(*str))
return -1;
node = *str++ - '0';
if (ISNUM(*str)) {
node *= 10;
node += (*str++ - '0');
}
if (ISNUM(*str)) {
node *= 10;
node += (*str++ - '0');
}
if (ISNUM(*str)) {
node *= 10;
node += (*str++ - '0');
}
if ((node > 1023) || (area > 63))
return -1;
if (INVALID_END_CHAR(*str))
return -1;
*addr = cpu_to_le16((area << 10) | node);
return 0;
}
static int dn_node_address_handler(struct ctl_table *table, int write,
void __user *buffer,
size_t *lenp, loff_t *ppos)
{
char addr[DN_ASCBUF_LEN];
size_t len;
__le16 dnaddr;
if (!*lenp || (*ppos && !write)) {
*lenp = 0;
return 0;
}
if (write) {
len = (*lenp < DN_ASCBUF_LEN) ? *lenp : (DN_ASCBUF_LEN-1);
if (copy_from_user(addr, buffer, len))
return -EFAULT;
addr[len] = 0;
strip_it(addr);
if (parse_addr(&dnaddr, addr))
return -EINVAL;
dn_dev_devices_off();
decnet_address = dnaddr;
dn_dev_devices_on();
*ppos += len;
return 0;
}
dn_addr2asc(le16_to_cpu(decnet_address), addr);
len = strlen(addr);
addr[len++] = '\n';
if (len > *lenp) len = *lenp;
if (copy_to_user(buffer, addr, len))
return -EFAULT;
*lenp = len;
*ppos += len;
return 0;
}
static int dn_def_dev_handler(struct ctl_table *table, int write,
void __user *buffer,
size_t *lenp, loff_t *ppos)
{
size_t len;
struct net_device *dev;
char devname[17];
if (!*lenp || (*ppos && !write)) {
*lenp = 0;
return 0;
}
if (write) {
if (*lenp > 16)
return -E2BIG;
if (copy_from_user(devname, buffer, *lenp))
return -EFAULT;
devname[*lenp] = 0;
strip_it(devname);
dev = dev_get_by_name(&init_net, devname);
if (dev == NULL)
return -ENODEV;
if (dev->dn_ptr == NULL) {
dev_put(dev);
return -ENODEV;
}
if (dn_dev_set_default(dev, 1)) {
dev_put(dev);
return -ENODEV;
}
*ppos += *lenp;
return 0;
}
dev = dn_dev_get_default();
if (dev == NULL) {
*lenp = 0;
return 0;
}
strcpy(devname, dev->name);
dev_put(dev);
len = strlen(devname);
devname[len++] = '\n';
if (len > *lenp) len = *lenp;
if (copy_to_user(buffer, devname, len))
return -EFAULT;
*lenp = len;
*ppos += len;
return 0;
}
static struct ctl_table dn_table[] = {
{
.procname = "node_address",
.maxlen = 7,
.mode = 0644,
.proc_handler = dn_node_address_handler,
},
{
.procname = "node_name",
.data = node_name,
.maxlen = 7,
.mode = 0644,
.proc_handler = proc_dostring,
},
{
.procname = "default_device",
.maxlen = 16,
.mode = 0644,
.proc_handler = dn_def_dev_handler,
},
{
.procname = "time_wait",
.data = &decnet_time_wait,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_decnet_time_wait,
.extra2 = &max_decnet_time_wait
},
{
.procname = "dn_count",
.data = &decnet_dn_count,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_state_count,
.extra2 = &max_state_count
},
{
.procname = "di_count",
.data = &decnet_di_count,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_state_count,
.extra2 = &max_state_count
},
{
.procname = "dr_count",
.data = &decnet_dr_count,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_state_count,
.extra2 = &max_state_count
},
{
.procname = "dst_gc_interval",
.data = &decnet_dst_gc_interval,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_decnet_dst_gc_interval,
.extra2 = &max_decnet_dst_gc_interval
},
{
.procname = "no_fc_max_cwnd",
.data = &decnet_no_fc_max_cwnd,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec_minmax,
.extra1 = &min_decnet_no_fc_max_cwnd,
.extra2 = &max_decnet_no_fc_max_cwnd
},
{
.procname = "decnet_mem",
.data = &sysctl_decnet_mem,
.maxlen = sizeof(sysctl_decnet_mem),
.mode = 0644,
.proc_handler = proc_doulongvec_minmax
},
{
.procname = "decnet_rmem",
.data = &sysctl_decnet_rmem,
.maxlen = sizeof(sysctl_decnet_rmem),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "decnet_wmem",
.data = &sysctl_decnet_wmem,
.maxlen = sizeof(sysctl_decnet_wmem),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{
.procname = "debug",
.data = &decnet_debug_level,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec,
},
{ }
};
void dn_register_sysctl(void)
{
dn_table_header = register_net_sysctl(&init_net, "net/decnet", dn_table);
}
void dn_unregister_sysctl(void)
{
unregister_net_sysctl_table(dn_table_header);
}
#else /* CONFIG_SYSCTL */
void dn_unregister_sysctl(void)
{
}
void dn_register_sysctl(void)
{
}
#endif