OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /or1k/trunk/linux/linux-2.4/net/decnet
    from Rev 1275 to Rev 1765
    Reverse comparison

Rev 1275 → Rev 1765

/dn_dev.c
0,0 → 1,1249
/*
* 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 Device Layer
*
* Authors: Steve Whitehouse <SteveW@ACM.org>
* Eduardo Marcelo Serrat <emserrat@geocities.com>
*
* Changes:
* Steve Whitehouse : Devices now see incoming frames so they
* can mark on who it came from.
* Steve Whitehouse : Fixed bug in creating neighbours. Each neighbour
* can now have a device specific setup func.
* Steve Whitehouse : Added /proc/sys/net/decnet/conf/<dev>/
* Steve Whitehouse : Fixed bug which sometimes killed timer
* Steve Whitehouse : Multiple ifaddr support
* Steve Whitehouse : SIOCGIFCONF is now a compile time option
* Steve Whitehouse : /proc/sys/net/decnet/conf/<sys>/forwarding
* Steve Whitehouse : Removed timer1 - its a user space issue now
* Patrick Caulfield : Fixed router hello message format
*/
 
#include <linux/config.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/proc_fs.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/sysctl.h>
#include <asm/uaccess.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_route.h>
#include <net/dn_neigh.h>
#include <net/dn_fib.h>
 
#define DN_IFREQ_SIZE (sizeof(struct ifreq) - sizeof(struct sockaddr) + sizeof(struct sockaddr_dn))
 
static char dn_rt_all_end_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x04,0x00,0x00};
static char dn_rt_all_rt_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x03,0x00,0x00};
static char dn_hiord[ETH_ALEN] = {0xAA,0x00,0x04,0x00,0x00,0x00};
static unsigned char dn_eco_version[3] = {0x02,0x00,0x00};
 
extern struct neigh_table dn_neigh_table;
 
struct net_device *decnet_default_device;
 
static struct dn_dev *dn_dev_create(struct net_device *dev, int *err);
static void dn_dev_delete(struct net_device *dev);
static void rtmsg_ifa(int event, struct dn_ifaddr *ifa);
 
static int dn_eth_up(struct net_device *);
static void dn_send_brd_hello(struct net_device *dev);
#if 0
static void dn_send_ptp_hello(struct net_device *dev);
#endif
 
static struct dn_dev_parms dn_dev_list[] = {
{
type: ARPHRD_ETHER, /* Ethernet */
mode: DN_DEV_BCAST,
state: DN_DEV_S_RU,
blksize: 1498,
t2: 1,
t3: 10,
name: "ethernet",
ctl_name: NET_DECNET_CONF_ETHER,
up: dn_eth_up,
timer3: dn_send_brd_hello,
},
{
type: ARPHRD_IPGRE, /* DECnet tunneled over GRE in IP */
mode: DN_DEV_BCAST,
state: DN_DEV_S_RU,
blksize: 1400,
t2: 1,
t3: 10,
name: "ipgre",
ctl_name: NET_DECNET_CONF_GRE,
timer3: dn_send_brd_hello,
},
#if 0
{
type: ARPHRD_X25, /* Bog standard X.25 */
mode: DN_DEV_UCAST,
state: DN_DEV_S_DS,
blksize: 230,
t2: 1,
t3: 120,
name: "x25",
ctl_name: NET_DECNET_CONF_X25,
timer3: dn_send_ptp_hello,
},
#endif
#if 0
{
type: ARPHRD_PPP, /* DECnet over PPP */
mode: DN_DEV_BCAST,
state: DN_DEV_S_RU,
blksize: 230,
t2: 1,
t3: 10,
name: "ppp",
ctl_name: NET_DECNET_CONF_PPP,
timer3: dn_send_brd_hello,
},
#endif
#if 0
{
type: ARPHRD_DDCMP, /* DECnet over DDCMP */
mode: DN_DEV_UCAST,
state: DN_DEV_S_DS,
blksize: 230,
t2: 1,
t3: 120,
name: "ddcmp",
ctl_name: NET_DECNET_CONF_DDCMP,
timer3: dn_send_ptp_hello,
},
#endif
{
type: ARPHRD_LOOPBACK, /* Loopback interface - always last */
mode: DN_DEV_BCAST,
state: DN_DEV_S_RU,
blksize: 1498,
t2: 1,
t3: 10,
name: "loopback",
ctl_name: NET_DECNET_CONF_LOOPBACK,
timer3: dn_send_brd_hello,
}
};
 
#define DN_DEV_LIST_SIZE (sizeof(dn_dev_list)/sizeof(struct dn_dev_parms))
 
#define DN_DEV_PARMS_OFFSET(x) ((int) ((char *) &((struct dn_dev_parms *)0)->x))
 
#ifdef CONFIG_SYSCTL
 
static int min_t2[] = { 1 };
static int max_t2[] = { 60 }; /* No max specified, but this seems sensible */
static int min_t3[] = { 1 };
static int max_t3[] = { 8191 }; /* Must fit in 16 bits when multiplied by BCT3MULT or T3MULT */
 
static int min_priority[1];
static int max_priority[] = { 127 }; /* From DECnet spec */
 
static int dn_forwarding_proc(ctl_table *, int, struct file *,
void *, size_t *);
static int dn_forwarding_sysctl(ctl_table *table, int *name, int nlen,
void *oldval, size_t *oldlenp,
void *newval, size_t newlen,
void **context);
 
static struct dn_dev_sysctl_table {
struct ctl_table_header *sysctl_header;
ctl_table dn_dev_vars[5];
ctl_table dn_dev_dev[2];
ctl_table dn_dev_conf_dir[2];
ctl_table dn_dev_proto_dir[2];
ctl_table dn_dev_root_dir[2];
} dn_dev_sysctl = {
NULL,
{
{NET_DECNET_CONF_DEV_FORWARDING, "forwarding",
(void *)DN_DEV_PARMS_OFFSET(forwarding),
sizeof(int), 0644, NULL,
dn_forwarding_proc, dn_forwarding_sysctl,
NULL, NULL, NULL},
{NET_DECNET_CONF_DEV_PRIORITY, "priority",
(void *)DN_DEV_PARMS_OFFSET(priority),
sizeof(int), 0644, NULL,
proc_dointvec_minmax, sysctl_intvec,
NULL, &min_priority, &max_priority},
{NET_DECNET_CONF_DEV_T2, "t2", (void *)DN_DEV_PARMS_OFFSET(t2),
sizeof(int), 0644, NULL,
proc_dointvec_minmax, sysctl_intvec,
NULL, &min_t2, &max_t2},
{NET_DECNET_CONF_DEV_T3, "t3", (void *)DN_DEV_PARMS_OFFSET(t3),
sizeof(int), 0644, NULL,
proc_dointvec_minmax, sysctl_intvec,
NULL, &min_t3, &max_t3},
{0}
},
{{0, "", NULL, 0, 0555, dn_dev_sysctl.dn_dev_vars}, {0}},
{{NET_DECNET_CONF, "conf", NULL, 0, 0555, dn_dev_sysctl.dn_dev_dev}, {0}},
{{NET_DECNET, "decnet", NULL, 0, 0555, dn_dev_sysctl.dn_dev_conf_dir}, {0}},
{{CTL_NET, "net", NULL, 0, 0555, dn_dev_sysctl.dn_dev_proto_dir}, {0}}
};
 
static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
{
struct dn_dev_sysctl_table *t;
int i;
 
t = kmalloc(sizeof(*t), GFP_KERNEL);
if (t == NULL)
return;
 
memcpy(t, &dn_dev_sysctl, sizeof(*t));
 
for(i = 0; i < (sizeof(t->dn_dev_vars)/sizeof(t->dn_dev_vars[0]) - 1); i++) {
long offset = (long)t->dn_dev_vars[i].data;
t->dn_dev_vars[i].data = ((char *)parms) + offset;
t->dn_dev_vars[i].de = NULL;
}
 
if (dev) {
t->dn_dev_dev[0].procname = dev->name;
t->dn_dev_dev[0].ctl_name = dev->ifindex;
} else {
t->dn_dev_dev[0].procname = parms->name;
t->dn_dev_dev[0].ctl_name = parms->ctl_name;
}
 
t->dn_dev_dev[0].child = t->dn_dev_vars;
t->dn_dev_dev[0].de = NULL;
t->dn_dev_conf_dir[0].child = t->dn_dev_dev;
t->dn_dev_conf_dir[0].de = NULL;
t->dn_dev_proto_dir[0].child = t->dn_dev_conf_dir;
t->dn_dev_proto_dir[0].de = NULL;
t->dn_dev_root_dir[0].child = t->dn_dev_proto_dir;
t->dn_dev_root_dir[0].de = NULL;
t->dn_dev_vars[0].extra1 = (void *)dev;
 
t->sysctl_header = register_sysctl_table(t->dn_dev_root_dir, 0);
if (t->sysctl_header == NULL)
kfree(t);
else
parms->sysctl = t;
}
 
static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
{
if (parms->sysctl) {
struct dn_dev_sysctl_table *t = parms->sysctl;
parms->sysctl = NULL;
unregister_sysctl_table(t->sysctl_header);
kfree(t);
}
}
 
 
static int dn_forwarding_proc(ctl_table *table, int write,
struct file *filep,
void *buffer, size_t *lenp)
{
#ifdef CONFIG_DECNET_ROUTER
struct net_device *dev = table->extra1;
struct dn_dev *dn_db;
int err;
int tmp, old;
 
if (table->extra1 == NULL)
return -EINVAL;
 
dn_db = dev->dn_ptr;
old = dn_db->parms.forwarding;
 
err = proc_dointvec(table, write, filep, buffer, lenp);
 
if ((err >= 0) && write) {
if (dn_db->parms.forwarding < 0)
dn_db->parms.forwarding = 0;
if (dn_db->parms.forwarding > 2)
dn_db->parms.forwarding = 2;
/*
* What an ugly hack this is... its works, just. It
* would be nice if sysctl/proc were just that little
* bit more flexible so I don't have to write a special
* routine, or suffer hacks like this - SJW
*/
tmp = dn_db->parms.forwarding;
dn_db->parms.forwarding = old;
if (dn_db->parms.down)
dn_db->parms.down(dev);
dn_db->parms.forwarding = tmp;
if (dn_db->parms.up)
dn_db->parms.up(dev);
}
 
return err;
#else
return -EINVAL;
#endif
}
 
static int dn_forwarding_sysctl(ctl_table *table, int *name, int nlen,
void *oldval, size_t *oldlenp,
void *newval, size_t newlen,
void **context)
{
#ifdef CONFIG_DECNET_ROUTER
struct net_device *dev = table->extra1;
struct dn_dev *dn_db;
int value;
 
if (table->extra1 == NULL)
return -EINVAL;
 
dn_db = dev->dn_ptr;
 
if (newval && newlen) {
if (newlen != sizeof(int))
return -EINVAL;
 
if (get_user(value, (int *)newval))
return -EFAULT;
if (value < 0)
return -EINVAL;
if (value > 2)
return -EINVAL;
 
if (dn_db->parms.down)
dn_db->parms.down(dev);
dn_db->parms.forwarding = value;
if (dn_db->parms.up)
dn_db->parms.up(dev);
}
 
return 0;
#else
return -EINVAL;
#endif
}
 
#else /* CONFIG_SYSCTL */
static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
{
}
static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
{
}
 
#endif /* CONFIG_SYSCTL */
 
static struct dn_ifaddr *dn_dev_alloc_ifa(void)
{
struct dn_ifaddr *ifa;
 
ifa = kmalloc(sizeof(*ifa), GFP_KERNEL);
 
if (ifa) {
memset(ifa, 0, sizeof(*ifa));
}
 
return ifa;
}
 
static __inline__ void dn_dev_free_ifa(struct dn_ifaddr *ifa)
{
kfree(ifa);
}
 
static void dn_dev_del_ifa(struct dn_dev *dn_db, struct dn_ifaddr **ifap, int destroy)
{
struct dn_ifaddr *ifa1 = *ifap;
 
*ifap = ifa1->ifa_next;
 
rtmsg_ifa(RTM_DELADDR, ifa1);
 
if (destroy) {
dn_dev_free_ifa(ifa1);
 
if (dn_db->ifa_list == NULL)
dn_dev_delete(dn_db->dev);
}
}
 
static int dn_dev_insert_ifa(struct dn_dev *dn_db, struct dn_ifaddr *ifa)
{
/*
* FIXME: Duplicate check here.
*/
 
ifa->ifa_next = dn_db->ifa_list;
dn_db->ifa_list = ifa;
 
rtmsg_ifa(RTM_NEWADDR, ifa);
 
return 0;
}
 
static int dn_dev_set_ifa(struct net_device *dev, struct dn_ifaddr *ifa)
{
struct dn_dev *dn_db = dev->dn_ptr;
 
if (dn_db == NULL) {
int err;
dn_db = dn_dev_create(dev, &err);
if (dn_db == NULL)
return err;
}
 
ifa->ifa_dev = dn_db;
 
if (dev->flags & IFF_LOOPBACK)
ifa->ifa_scope = RT_SCOPE_HOST;
 
return dn_dev_insert_ifa(dn_db, ifa);
}
 
 
int dn_dev_ioctl(unsigned int cmd, void *arg)
{
char buffer[DN_IFREQ_SIZE];
struct ifreq *ifr = (struct ifreq *)buffer;
struct sockaddr_dn *sdn = (struct sockaddr_dn *)&ifr->ifr_addr;
struct dn_dev *dn_db;
struct net_device *dev;
struct dn_ifaddr *ifa = NULL, **ifap = NULL;
int exclusive = 0;
int ret = 0;
 
if (copy_from_user(ifr, arg, DN_IFREQ_SIZE))
return -EFAULT;
ifr->ifr_name[IFNAMSIZ-1] = 0;
 
#ifdef CONFIG_KMOD
dev_load(ifr->ifr_name);
#endif
 
switch(cmd) {
case SIOCGIFADDR:
break;
case SIOCSIFADDR:
if (!capable(CAP_NET_ADMIN))
return -EACCES;
if (sdn->sdn_family != AF_DECnet)
return -EINVAL;
rtnl_lock();
exclusive = 1;
break;
default:
return -EINVAL;
}
 
if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL) {
ret = -ENODEV;
goto done;
}
 
if ((dn_db = dev->dn_ptr) != NULL) {
for (ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next)
if (strcmp(ifr->ifr_name, ifa->ifa_label) == 0)
break;
}
 
if (ifa == NULL && cmd != SIOCSIFADDR) {
ret = -EADDRNOTAVAIL;
goto done;
}
 
switch(cmd) {
case SIOCGIFADDR:
*((dn_address *)sdn->sdn_nodeaddr) = ifa->ifa_local;
goto rarok;
 
case SIOCSIFADDR:
if (!ifa) {
if ((ifa = dn_dev_alloc_ifa()) == NULL) {
ret = -ENOBUFS;
break;
}
memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
} else {
if (ifa->ifa_local == dn_saddr2dn(sdn))
break;
dn_dev_del_ifa(dn_db, ifap, 0);
}
 
ifa->ifa_local = dn_saddr2dn(sdn);
 
ret = dn_dev_set_ifa(dev, ifa);
}
done:
if (exclusive)
rtnl_unlock();
 
return ret;
rarok:
if (copy_to_user(arg, ifr, DN_IFREQ_SIZE))
return -EFAULT;
 
return 0;
}
 
static struct dn_dev *dn_dev_by_index(int ifindex)
{
struct net_device *dev;
struct dn_dev *dn_dev = NULL;
dev = dev_get_by_index(ifindex);
if (dev) {
dn_dev = dev->dn_ptr;
dev_put(dev);
}
 
return dn_dev;
}
 
static int dn_dev_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct rtattr **rta = arg;
struct dn_dev *dn_db;
struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
struct dn_ifaddr *ifa, **ifap;
 
if ((dn_db = dn_dev_by_index(ifm->ifa_index)) == NULL)
return -EADDRNOTAVAIL;
 
for(ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next) {
void *tmp = rta[IFA_LOCAL-1];
if ((tmp && memcmp(RTA_DATA(tmp), &ifa->ifa_local, 2)) ||
(rta[IFA_LABEL-1] && strcmp(RTA_DATA(rta[IFA_LABEL-1]), ifa->ifa_label)))
continue;
 
dn_dev_del_ifa(dn_db, ifap, 1);
return 0;
}
 
return -EADDRNOTAVAIL;
}
 
static int dn_dev_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct rtattr **rta = arg;
struct net_device *dev;
struct dn_dev *dn_db;
struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
struct dn_ifaddr *ifa;
 
if (rta[IFA_LOCAL-1] == NULL)
return -EINVAL;
 
if ((dev = __dev_get_by_index(ifm->ifa_index)) == NULL)
return -ENODEV;
 
if ((dn_db = dev->dn_ptr) == NULL) {
int err;
dn_db = dn_dev_create(dev, &err);
if (!dn_db)
return err;
}
if ((ifa = dn_dev_alloc_ifa()) == NULL)
return -ENOBUFS;
 
memcpy(&ifa->ifa_local, RTA_DATA(rta[IFA_LOCAL-1]), 2);
ifa->ifa_flags = ifm->ifa_flags;
ifa->ifa_scope = ifm->ifa_scope;
ifa->ifa_dev = dn_db;
if (rta[IFA_LABEL-1])
memcpy(ifa->ifa_label, RTA_DATA(rta[IFA_LABEL-1]), IFNAMSIZ);
else
memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
 
return dn_dev_insert_ifa(dn_db, ifa);
}
 
static int dn_dev_fill_ifaddr(struct sk_buff *skb, struct dn_ifaddr *ifa,
u32 pid, u32 seq, int event)
{
struct ifaddrmsg *ifm;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
 
nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
ifm = NLMSG_DATA(nlh);
 
ifm->ifa_family = AF_DECnet;
ifm->ifa_prefixlen = 16;
ifm->ifa_flags = ifa->ifa_flags | IFA_F_PERMANENT;
ifm->ifa_scope = ifa->ifa_scope;
ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
RTA_PUT(skb, IFA_LOCAL, 2, &ifa->ifa_local);
if (ifa->ifa_label[0])
RTA_PUT(skb, IFA_LABEL, IFNAMSIZ, &ifa->ifa_label);
nlh->nlmsg_len = skb->tail - b;
return skb->len;
 
nlmsg_failure:
rtattr_failure:
skb_trim(skb, b - skb->data);
return -1;
}
 
static void rtmsg_ifa(int event, struct dn_ifaddr *ifa)
{
struct sk_buff *skb;
int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
 
skb = alloc_skb(size, GFP_KERNEL);
if (!skb) {
netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, ENOBUFS);
return;
}
if (dn_dev_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
kfree_skb(skb);
netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, EINVAL);
return;
}
NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_IFADDR;
netlink_broadcast(rtnl, skb, 0, RTMGRP_DECnet_IFADDR, GFP_KERNEL);
}
 
static int dn_dev_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
{
int idx, dn_idx;
int s_idx, s_dn_idx;
struct net_device *dev;
struct dn_dev *dn_db;
struct dn_ifaddr *ifa;
 
s_idx = cb->args[0];
s_dn_idx = dn_idx = cb->args[1];
read_lock(&dev_base_lock);
for(dev = dev_base, idx = 0; dev; dev = dev->next) {
if ((dn_db = dev->dn_ptr) == NULL)
continue;
idx++;
if (idx < s_idx)
continue;
if (idx > s_idx)
s_dn_idx = 0;
if ((dn_db = dev->dn_ptr) == NULL)
continue;
 
for(ifa = dn_db->ifa_list, dn_idx = 0; ifa; ifa = ifa->ifa_next, dn_idx++) {
if (dn_idx < s_dn_idx)
continue;
 
if (dn_dev_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0)
goto done;
}
}
done:
read_unlock(&dev_base_lock);
cb->args[0] = idx;
cb->args[1] = dn_idx;
 
return skb->len;
}
 
static void dn_send_endnode_hello(struct net_device *dev)
{
struct endnode_hello_message *msg;
struct sk_buff *skb = NULL;
unsigned short int *pktlen;
struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
 
if ((skb = dn_alloc_skb(NULL, sizeof(*msg), GFP_ATOMIC)) == NULL)
return;
 
skb->dev = dev;
 
msg = (struct endnode_hello_message *)skb_put(skb,sizeof(*msg));
 
msg->msgflg = 0x0D;
memcpy(msg->tiver, dn_eco_version, 3);
memcpy(msg->id, decnet_ether_address, 6);
msg->iinfo = DN_RT_INFO_ENDN;
msg->blksize = dn_htons(dn_db->parms.blksize);
msg->area = 0x00;
memset(msg->seed, 0, 8);
memcpy(msg->neighbor, dn_hiord, ETH_ALEN);
 
if (dn_db->router) {
struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
dn_dn2eth(msg->neighbor, dn->addr);
}
 
msg->timer = dn_htons((unsigned short)dn_db->parms.t3);
msg->mpd = 0x00;
msg->datalen = 0x02;
memset(msg->data, 0xAA, 2);
pktlen = (unsigned short *)skb_push(skb,2);
*pktlen = dn_htons(skb->len - 2);
 
skb->nh.raw = skb->data;
 
dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
}
 
 
#ifdef CONFIG_DECNET_ROUTER
 
#define DRDELAY (5 * HZ)
 
static int dn_am_i_a_router(struct dn_neigh *dn, struct dn_dev *dn_db)
{
/* First check time since device went up */
if ((jiffies - dn_db->uptime) < DRDELAY)
return 0;
 
/* If there is no router, then yes... */
if (!dn_db->router)
return 1;
 
/* otherwise only if we have a higher priority or.. */
if (dn->priority < dn_db->parms.priority)
return 1;
 
/* if we have equal priority and a higher node number */
if (dn->priority != dn_db->parms.priority)
return 0;
 
if (dn_ntohs(dn->addr) < dn_ntohs(decnet_address))
return 1;
 
return 0;
}
 
static void dn_send_router_hello(struct net_device *dev)
{
int n;
struct dn_dev *dn_db = dev->dn_ptr;
struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
struct sk_buff *skb;
size_t size;
unsigned char *ptr;
unsigned char *i1, *i2;
unsigned short *pktlen;
 
if (dn_db->parms.blksize < (26 + 7))
return;
 
n = dn_db->parms.blksize - 26;
n /= 7;
 
if (n > 32)
n = 32;
 
size = 2 + 26 + 7 * n;
 
if ((skb = dn_alloc_skb(NULL, size, GFP_ATOMIC)) == NULL)
return;
 
skb->dev = dev;
ptr = skb_put(skb, size);
 
*ptr++ = DN_RT_PKT_CNTL | DN_RT_PKT_ERTH;
*ptr++ = 2; /* ECO */
*ptr++ = 0;
*ptr++ = 0;
memcpy(ptr, decnet_ether_address, ETH_ALEN);
ptr += ETH_ALEN;
*ptr++ = dn_db->parms.forwarding == 1 ?
DN_RT_INFO_L1RT : DN_RT_INFO_L2RT;
*((unsigned short *)ptr) = dn_htons(dn_db->parms.blksize);
ptr += 2;
*ptr++ = dn_db->parms.priority; /* Priority */
*ptr++ = 0; /* Area: Reserved */
*((unsigned short *)ptr) = dn_htons((unsigned short)dn_db->parms.t3);
ptr += 2;
*ptr++ = 0; /* MPD: Reserved */
i1 = ptr++;
memset(ptr, 0, 7); /* Name: Reserved */
ptr += 7;
i2 = ptr++;
 
n = dn_neigh_elist(dev, ptr, n);
 
*i2 = 7 * n;
*i1 = 8 + *i2;
 
skb_trim(skb, (27 + *i2));
 
pktlen = (unsigned short *)skb_push(skb, 2);
*pktlen = dn_htons(skb->len - 2);
 
skb->nh.raw = skb->data;
 
if (dn_am_i_a_router(dn, dn_db)) {
struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
if (skb2) {
dn_rt_finish_output(skb2, dn_rt_all_end_mcast);
}
}
 
dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
}
 
static void dn_send_brd_hello(struct net_device *dev)
{
struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
 
if (dn_db->parms.forwarding == 0)
dn_send_endnode_hello(dev);
else
dn_send_router_hello(dev);
}
#else
static void dn_send_brd_hello(struct net_device *dev)
{
dn_send_endnode_hello(dev);
}
#endif
 
#if 0
static void dn_send_ptp_hello(struct net_device *dev)
{
int tdlen = 16;
int size = dev->hard_header_len + 2 + 4 + tdlen;
struct sk_buff *skb = dn_alloc_skb(NULL, size, GFP_ATOMIC);
struct dn_dev *dn_db = dev->dn_ptr;
int i;
unsigned char *ptr;
struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
 
if (skb == NULL)
return ;
 
skb->dev = dev;
skb_push(skb, dev->hard_header_len);
ptr = skb_put(skb, 2 + 4 + tdlen);
 
*ptr++ = DN_RT_PKT_HELO;
*((dn_address *)ptr) = decnet_address;
ptr += 2;
*ptr++ = tdlen;
 
for(i = 0; i < tdlen; i++)
*ptr++ = 0252;
 
if (dn_am_i_a_router(dn, dn_db)) {
struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
if (skb2) {
dn_rt_finish_output(skb2, dn_rt_all_end_mcast);
}
}
 
dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
}
#endif
 
static int dn_eth_up(struct net_device *dev)
{
struct dn_dev *dn_db = dev->dn_ptr;
 
if (dn_db->parms.forwarding == 0)
dev_mc_add(dev, dn_rt_all_end_mcast, ETH_ALEN, 0);
else
dev_mc_add(dev, dn_rt_all_rt_mcast, ETH_ALEN, 0);
 
dev_mc_upload(dev);
 
dn_db->use_long = 1;
 
return 0;
}
 
static void dn_dev_set_timer(struct net_device *dev);
 
static void dn_dev_timer_func(unsigned long arg)
{
struct net_device *dev = (struct net_device *)arg;
struct dn_dev *dn_db = dev->dn_ptr;
 
if (dn_db->t3 <= dn_db->parms.t2) {
if (dn_db->parms.timer3)
dn_db->parms.timer3(dev);
dn_db->t3 = dn_db->parms.t3;
} else {
dn_db->t3 -= dn_db->parms.t2;
}
 
dn_dev_set_timer(dev);
}
 
static void dn_dev_set_timer(struct net_device *dev)
{
struct dn_dev *dn_db = dev->dn_ptr;
 
if (dn_db->parms.t2 > dn_db->parms.t3)
dn_db->parms.t2 = dn_db->parms.t3;
 
dn_db->timer.data = (unsigned long)dev;
dn_db->timer.function = dn_dev_timer_func;
dn_db->timer.expires = jiffies + (dn_db->parms.t2 * HZ);
 
add_timer(&dn_db->timer);
}
 
struct dn_dev *dn_dev_create(struct net_device *dev, int *err)
{
int i;
struct dn_dev_parms *p = dn_dev_list;
struct dn_dev *dn_db;
 
for(i = 0; i < DN_DEV_LIST_SIZE; i++, p++) {
if (p->type == dev->type)
break;
}
 
*err = -ENODEV;
if (i == DN_DEV_LIST_SIZE)
return NULL;
 
*err = -ENOBUFS;
if ((dn_db = kmalloc(sizeof(struct dn_dev), GFP_ATOMIC)) == NULL)
return NULL;
 
memset(dn_db, 0, sizeof(struct dn_dev));
memcpy(&dn_db->parms, p, sizeof(struct dn_dev_parms));
dev->dn_ptr = dn_db;
dn_db->dev = dev;
init_timer(&dn_db->timer);
 
memcpy(dn_db->addr, decnet_ether_address, ETH_ALEN); /* To go... */
 
dn_db->uptime = jiffies;
if (dn_db->parms.up) {
if (dn_db->parms.up(dev) < 0) {
dev->dn_ptr = NULL;
kfree(dn_db);
return NULL;
}
}
 
dn_db->neigh_parms = neigh_parms_alloc(dev, &dn_neigh_table);
/* dn_db->neigh_parms->neigh_setup = dn_db->parms.neigh_setup; */
 
dn_dev_sysctl_register(dev, &dn_db->parms);
 
dn_dev_set_timer(dev);
 
*err = 0;
return dn_db;
}
 
 
/*
* This processes a device up event. We only start up
* the loopback device & ethernet devices with correct
* MAC addreses automatically. Others must be started
* specifically.
*/
void dn_dev_up(struct net_device *dev)
{
struct dn_ifaddr *ifa;
 
if ((dev->type != ARPHRD_ETHER) && (dev->type != ARPHRD_LOOPBACK))
return;
 
if (dev->type == ARPHRD_ETHER)
if (memcmp(dev->dev_addr, decnet_ether_address, ETH_ALEN) != 0)
return;
 
if ((ifa = dn_dev_alloc_ifa()) == NULL)
return;
 
ifa->ifa_local = decnet_address;
ifa->ifa_flags = 0;
ifa->ifa_scope = RT_SCOPE_UNIVERSE;
strcpy(ifa->ifa_label, dev->name);
 
dn_dev_set_ifa(dev, ifa);
}
 
static void dn_dev_delete(struct net_device *dev)
{
struct dn_dev *dn_db = dev->dn_ptr;
 
if (dn_db == NULL)
return;
 
del_timer_sync(&dn_db->timer);
 
dn_dev_sysctl_unregister(&dn_db->parms);
 
neigh_ifdown(&dn_neigh_table, dev);
 
if (dev == decnet_default_device)
decnet_default_device = NULL;
 
if (dn_db->parms.down)
dn_db->parms.down(dev);
 
dev->dn_ptr = NULL;
 
neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms);
 
if (dn_db->router)
neigh_release(dn_db->router);
if (dn_db->peer)
neigh_release(dn_db->peer);
 
kfree(dn_db);
}
 
void dn_dev_down(struct net_device *dev)
{
struct dn_dev *dn_db = dev->dn_ptr;
struct dn_ifaddr *ifa;
 
if (dn_db == NULL)
return;
 
while((ifa = dn_db->ifa_list) != NULL) {
dn_dev_del_ifa(dn_db, &dn_db->ifa_list, 0);
dn_dev_free_ifa(ifa);
}
 
dn_dev_delete(dev);
}
 
void dn_dev_init_pkt(struct sk_buff *skb)
{
return;
}
 
void dn_dev_veri_pkt(struct sk_buff *skb)
{
return;
}
 
void dn_dev_hello(struct sk_buff *skb)
{
return;
}
 
void dn_dev_devices_off(void)
{
struct net_device *dev;
 
for(dev = dev_base; dev; dev = dev->next)
dn_dev_down(dev);
 
}
 
void dn_dev_devices_on(void)
{
struct net_device *dev;
 
for(dev = dev_base; dev; dev = dev->next) {
if (dev->flags & IFF_UP)
dn_dev_up(dev);
}
}
 
 
#ifdef CONFIG_DECNET_SIOCGIFCONF
/*
* Now we support multiple addresses per interface.
* Since we don't want to break existing code, you have to enable
* it as a compile time option. Probably you should use the
* rtnetlink interface instead.
*/
int dnet_gifconf(struct net_device *dev, char *buf, int len)
{
struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
struct dn_ifaddr *ifa;
struct ifreq *ifr = (struct ifreq *)buf;
int done = 0;
 
if ((dn_db == NULL) || ((ifa = dn_db->ifa_list) == NULL))
return 0;
 
for(; ifa; ifa = ifa->ifa_next) {
if (!ifr) {
done += sizeof(DN_IFREQ_SIZE);
continue;
}
if (len < DN_IFREQ_SIZE)
return done;
memset(ifr, 0, DN_IFREQ_SIZE);
 
if (ifa->ifa_label)
strcpy(ifr->ifr_name, ifa->ifa_label);
else
strcpy(ifr->ifr_name, dev->name);
 
(*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_family = AF_DECnet;
(*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_add.a_len = 2;
(*(dn_address *)(*(struct sockaddr_dn *) &ifr->ifr_addr).sdn_add.a_addr) = ifa->ifa_local;
 
ifr = (struct ifreq *)((char *)ifr + DN_IFREQ_SIZE);
len -= DN_IFREQ_SIZE;
done += DN_IFREQ_SIZE;
}
 
return done;
}
#endif /* CONFIG_DECNET_SIOCGIFCONF */
 
 
#ifdef CONFIG_PROC_FS
 
static char *dn_type2asc(char type)
{
switch(type) {
case DN_DEV_BCAST:
return "B";
case DN_DEV_UCAST:
return "U";
case DN_DEV_MPOINT:
return "M";
}
 
return "?";
}
 
static int decnet_dev_get_info(char *buffer, char **start, off_t offset, int length)
{
struct dn_dev *dn_db;
struct net_device *dev;
int len = 0;
off_t pos = 0;
off_t begin = 0;
char peer_buf[DN_ASCBUF_LEN];
char router_buf[DN_ASCBUF_LEN];
 
 
len += sprintf(buffer, "Name Flags T1 Timer1 T3 Timer3 BlkSize Pri State DevType Router Peer\n");
 
read_lock(&dev_base_lock);
for (dev = dev_base; dev; dev = dev->next) {
if ((dn_db = (struct dn_dev *)dev->dn_ptr) == NULL)
continue;
 
len += sprintf(buffer + len, "%-8s %1s %04u %04u %04lu %04lu %04hu %03d %02x %-10s %-7s %-7s\n",
dev->name ? dev->name : "???",
dn_type2asc(dn_db->parms.mode),
0, 0,
dn_db->t3, dn_db->parms.t3,
dn_db->parms.blksize,
dn_db->parms.priority,
dn_db->parms.state, dn_db->parms.name,
dn_db->router ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->router->primary_key), router_buf) : "",
dn_db->peer ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->peer->primary_key), peer_buf) : "");
 
 
pos = begin + len;
 
if (pos < offset) {
len = 0;
begin = pos;
}
if (pos > offset + length)
break;
}
 
read_unlock(&dev_base_lock);
 
*start = buffer + (offset - begin);
len -= (offset - begin);
 
if (len > length) len = length;
 
return(len);
}
 
#endif /* CONFIG_PROC_FS */
 
static struct rtnetlink_link dnet_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
{
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
 
{ dn_dev_rtm_newaddr, NULL, },
{ dn_dev_rtm_deladdr, NULL, },
{ NULL, dn_dev_dump_ifaddr, },
{ NULL, NULL, },
 
#ifdef CONFIG_DECNET_ROUTER
{ dn_fib_rtm_newroute, NULL, },
{ dn_fib_rtm_delroute, NULL, },
{ dn_cache_getroute, dn_fib_dump, },
{ NULL, NULL, },
#else
{ NULL, NULL, },
{ NULL, NULL, },
{ dn_cache_getroute, dn_cache_dump, },
{ NULL, NULL, },
#endif
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
 
#ifdef CONFIG_DECNET_ROUTER
{ dn_fib_rtm_newrule, NULL, },
{ dn_fib_rtm_delrule, NULL, },
{ NULL, dn_fib_dump_rules, },
{ NULL, NULL, }
#else
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, }
#endif
};
 
void __init dn_dev_init(void)
{
 
dn_dev_devices_on();
#ifdef CONFIG_DECNET_SIOCGIFCONF
register_gifconf(PF_DECnet, dnet_gifconf);
#endif /* CONFIG_DECNET_SIOCGIFCONF */
 
rtnetlink_links[PF_DECnet] = dnet_rtnetlink_table;
 
#ifdef CONFIG_PROC_FS
proc_net_create("decnet_dev", 0, decnet_dev_get_info);
#endif /* CONFIG_PROC_FS */
 
#ifdef CONFIG_SYSCTL
{
int i;
for(i = 0; i < DN_DEV_LIST_SIZE; i++)
dn_dev_sysctl_register(NULL, &dn_dev_list[i]);
}
#endif /* CONFIG_SYSCTL */
}
 
void __exit dn_dev_cleanup(void)
{
rtnetlink_links[PF_DECnet] = NULL;
 
#ifdef CONFIG_DECNET_SIOCGIFCONF
unregister_gifconf(PF_DECnet);
#endif /* CONFIG_DECNET_SIOCGIFCONF */
 
#ifdef CONFIG_SYSCTL
{
int i;
for(i = 0; i < DN_DEV_LIST_SIZE; i++)
dn_dev_sysctl_unregister(&dn_dev_list[i]);
}
#endif /* CONFIG_SYSCTL */
 
proc_net_remove("decnet_dev");
 
dn_dev_devices_off();
}
/dn_route.c
0,0 → 1,1306
 
/*
* 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 Functions (Endnode and Router)
*
* Authors: Steve Whitehouse <SteveW@ACM.org>
* Eduardo Marcelo Serrat <emserrat@geocities.com>
*
* Changes:
* Steve Whitehouse : Fixes to allow "intra-ethernet" and
* "return-to-sender" bits on outgoing
* packets.
* Steve Whitehouse : Timeouts for cached routes.
* Steve Whitehouse : Use dst cache for input routes too.
* Steve Whitehouse : Fixed error values in dn_send_skb.
* Steve Whitehouse : Rework routing functions to better fit
* DECnet routing design
* Alexey Kuznetsov : New SMP locking
* Steve Whitehouse : More SMP locking changes & dn_cache_dump()
* Steve Whitehouse : Prerouting NF hook, now really is prerouting.
* Fixed possible skb leak in rtnetlink funcs.
* Steve Whitehouse : Dave Miller's dynamic hash table sizing and
* Alexey Kuznetsov's finer grained locking
* from ipv4/route.c.
* Steve Whitehouse : Routing is now starting to look like a
* sensible set of code now, mainly due to
* my copying the IPv4 routing code. The
* hooks here are modified and will continue
* to evolve for a while.
* Steve Whitehouse : Real SMP at last :-) Also new netfilter
* stuff. Look out raw sockets your days
* are numbered!
* Steve Whitehouse : Added return-to-sender functions. Added
* backlog congestion level return codes.
* Steve Whitehouse : Fixed bug where routes were set up with
* no ref count on net devices.
*
*/
 
/******************************************************************************
(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/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/netdevice.h>
#include <linux/inet.h>
#include <linux/route.h>
#include <net/sock.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <linux/rtnetlink.h>
#include <linux/string.h>
#include <linux/netfilter_decnet.h>
#include <asm/errno.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_nsp.h>
#include <net/dn_route.h>
#include <net/dn_neigh.h>
#include <net/dn_fib.h>
 
struct dn_rt_hash_bucket
{
struct dn_route *chain;
rwlock_t lock;
} __attribute__((__aligned__(8)));
 
extern struct neigh_table dn_neigh_table;
 
 
static unsigned char dn_hiord_addr[6] = {0xAA,0x00,0x04,0x00,0x00,0x00};
 
int dn_rt_min_delay = 2*HZ;
int dn_rt_max_delay = 10*HZ;
static unsigned long dn_rt_deadline = 0;
 
static int dn_dst_gc(void);
static struct dst_entry *dn_dst_check(struct dst_entry *, __u32);
static struct dst_entry *dn_dst_reroute(struct dst_entry *, struct sk_buff *skb);
static struct dst_entry *dn_dst_negative_advice(struct dst_entry *);
static void dn_dst_link_failure(struct sk_buff *);
static int dn_route_input(struct sk_buff *);
static void dn_run_flush(unsigned long dummy);
 
static struct dn_rt_hash_bucket *dn_rt_hash_table;
static unsigned dn_rt_hash_mask;
 
static struct timer_list dn_route_timer;
static struct timer_list dn_rt_flush_timer = { function: dn_run_flush };
int decnet_dst_gc_interval = 2;
 
static struct dst_ops dn_dst_ops = {
family: PF_DECnet,
protocol: __constant_htons(ETH_P_DNA_RT),
gc_thresh: 128,
gc: dn_dst_gc,
check: dn_dst_check,
reroute: dn_dst_reroute,
negative_advice: dn_dst_negative_advice,
link_failure: dn_dst_link_failure,
entry_size: sizeof(struct dn_route),
entries: ATOMIC_INIT(0),
};
 
static __inline__ unsigned dn_hash(unsigned short src, unsigned short dst)
{
unsigned short tmp = src ^ dst;
tmp ^= (tmp >> 3);
tmp ^= (tmp >> 5);
tmp ^= (tmp >> 10);
return dn_rt_hash_mask & (unsigned)tmp;
}
 
static void SMP_TIMER_NAME(dn_dst_check_expire)(unsigned long dummy)
{
int i;
struct dn_route *rt, **rtp;
unsigned long now = jiffies;
unsigned long expire = 120 * HZ;
 
for(i = 0; i <= dn_rt_hash_mask; i++) {
rtp = &dn_rt_hash_table[i].chain;
 
write_lock(&dn_rt_hash_table[i].lock);
while((rt=*rtp) != NULL) {
if (atomic_read(&rt->u.dst.__refcnt) ||
(now - rt->u.dst.lastuse) < expire) {
rtp = &rt->u.rt_next;
continue;
}
*rtp = rt->u.rt_next;
rt->u.rt_next = NULL;
dst_free(&rt->u.dst);
}
write_unlock(&dn_rt_hash_table[i].lock);
 
if ((jiffies - now) > 0)
break;
}
 
mod_timer(&dn_route_timer, now + decnet_dst_gc_interval * HZ);
}
 
SMP_TIMER_DEFINE(dn_dst_check_expire, dn_dst_task);
 
static int dn_dst_gc(void)
{
struct dn_route *rt, **rtp;
int i;
unsigned long now = jiffies;
unsigned long expire = 10 * HZ;
 
for(i = 0; i <= dn_rt_hash_mask; i++) {
 
write_lock_bh(&dn_rt_hash_table[i].lock);
rtp = &dn_rt_hash_table[i].chain;
 
while((rt=*rtp) != NULL) {
if (atomic_read(&rt->u.dst.__refcnt) ||
(now - rt->u.dst.lastuse) < expire) {
rtp = &rt->u.rt_next;
continue;
}
*rtp = rt->u.rt_next;
rt->u.rt_next = NULL;
dst_free(&rt->u.dst);
break;
}
write_unlock_bh(&dn_rt_hash_table[i].lock);
}
 
return 0;
}
 
static struct dst_entry *dn_dst_check(struct dst_entry *dst, __u32 cookie)
{
dst_release(dst);
return NULL;
}
 
static struct dst_entry *dn_dst_reroute(struct dst_entry *dst,
struct sk_buff *skb)
{
return NULL;
}
 
/*
* This is called through sendmsg() when you specify MSG_TRYHARD
* and there is already a route in cache.
*/
static struct dst_entry *dn_dst_negative_advice(struct dst_entry *dst)
{
dst_release(dst);
return NULL;
}
 
static void dn_dst_link_failure(struct sk_buff *skb)
{
return;
}
 
static void dn_insert_route(struct dn_route *rt, unsigned hash)
{
unsigned long now = jiffies;
 
write_lock_bh(&dn_rt_hash_table[hash].lock);
rt->u.rt_next = dn_rt_hash_table[hash].chain;
dn_rt_hash_table[hash].chain = rt;
dst_hold(&rt->u.dst);
rt->u.dst.__use++;
rt->u.dst.lastuse = now;
 
write_unlock_bh(&dn_rt_hash_table[hash].lock);
}
 
void SMP_TIMER_NAME(dn_run_flush)(unsigned long dummy)
{
int i;
struct dn_route *rt, *next;
 
for(i = 0; i < dn_rt_hash_mask; i++) {
write_lock_bh(&dn_rt_hash_table[i].lock);
 
if ((rt = xchg(&dn_rt_hash_table[i].chain, NULL)) == NULL)
goto nothing_to_declare;
 
for(; rt; rt=next) {
next = rt->u.rt_next;
rt->u.rt_next = NULL;
dst_free((struct dst_entry *)rt);
}
 
nothing_to_declare:
write_unlock_bh(&dn_rt_hash_table[i].lock);
}
}
 
SMP_TIMER_DEFINE(dn_run_flush, dn_flush_task);
 
static spinlock_t dn_rt_flush_lock = SPIN_LOCK_UNLOCKED;
 
void dn_rt_cache_flush(int delay)
{
unsigned long now = jiffies;
int user_mode = !in_interrupt();
 
if (delay < 0)
delay = dn_rt_min_delay;
 
spin_lock_bh(&dn_rt_flush_lock);
 
if (del_timer(&dn_rt_flush_timer) && delay > 0 && dn_rt_deadline) {
long tmo = (long)(dn_rt_deadline - now);
 
if (user_mode && tmo < dn_rt_max_delay - dn_rt_min_delay)
tmo = 0;
 
if (delay > tmo)
delay = tmo;
}
 
if (delay <= 0) {
spin_unlock_bh(&dn_rt_flush_lock);
dn_run_flush(0);
return;
}
 
if (dn_rt_deadline == 0)
dn_rt_deadline = now + dn_rt_max_delay;
 
dn_rt_flush_timer.expires = now + delay;
add_timer(&dn_rt_flush_timer);
spin_unlock_bh(&dn_rt_flush_lock);
}
 
/**
* dn_return_short - Return a short packet to its sender
* @skb: The packet to return
*
*/
static int dn_return_short(struct sk_buff *skb)
{
struct dn_skb_cb *cb;
unsigned char *ptr;
dn_address *src;
dn_address *dst;
dn_address tmp;
 
/* Add back headers */
skb_push(skb, skb->data - skb->nh.raw);
 
if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
return NET_RX_DROP;
 
cb = DN_SKB_CB(skb);
/* Skip packet length and point to flags */
ptr = skb->data + 2;
*ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
 
dst = (dn_address *)ptr;
ptr += 2;
src = (dn_address *)ptr;
ptr += 2;
*ptr = 0; /* Zero hop count */
 
/* Swap source and destination */
tmp = *src;
*src = *dst;
*dst = tmp;
 
skb->pkt_type = PACKET_OUTGOING;
dn_rt_finish_output(skb, NULL);
return NET_RX_SUCCESS;
}
 
/**
* dn_return_long - Return a long packet to its sender
* @skb: The long format packet to return
*
*/
static int dn_return_long(struct sk_buff *skb)
{
struct dn_skb_cb *cb;
unsigned char *ptr;
unsigned char *src_addr, *dst_addr;
unsigned char tmp[ETH_ALEN];
 
/* Add back all headers */
skb_push(skb, skb->data - skb->nh.raw);
 
if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
return NET_RX_DROP;
 
cb = DN_SKB_CB(skb);
/* Ignore packet length and point to flags */
ptr = skb->data + 2;
 
/* Skip padding */
if (*ptr & DN_RT_F_PF) {
char padlen = (*ptr & ~DN_RT_F_PF);
ptr += padlen;
}
 
*ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
ptr += 2;
dst_addr = ptr;
ptr += 8;
src_addr = ptr;
ptr += 6;
*ptr = 0; /* Zero hop count */
 
/* Swap source and destination */
memcpy(tmp, src_addr, ETH_ALEN);
memcpy(src_addr, dst_addr, ETH_ALEN);
memcpy(dst_addr, tmp, ETH_ALEN);
 
skb->pkt_type = PACKET_OUTGOING;
dn_rt_finish_output(skb, tmp);
return NET_RX_SUCCESS;
}
 
/**
* dn_route_rx_packet - Try and find a route for an incoming packet
* @skb: The packet to find a route for
*
* Returns: result of input function if route is found, error code otherwise
*/
static int dn_route_rx_packet(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
int err;
 
if ((err = dn_route_input(skb)) == 0)
return skb->dst->input(skb);
 
if (decnet_debug_level & 4) {
char *devname = skb->dev ? skb->dev->name : "???";
struct dn_skb_cb *cb = DN_SKB_CB(skb);
printk(KERN_DEBUG
"DECnet: dn_route_rx_packet: rt_flags=0x%02x dev=%s len=%d src=0x%04hx dst=0x%04hx err=%d type=%d\n",
(int)cb->rt_flags, devname, skb->len, cb->src, cb->dst,
err, skb->pkt_type);
}
 
if ((skb->pkt_type == PACKET_HOST) && (cb->rt_flags & DN_RT_F_RQR)) {
switch(cb->rt_flags & DN_RT_PKT_MSK) {
case DN_RT_PKT_SHORT:
return dn_return_short(skb);
case DN_RT_PKT_LONG:
return dn_return_long(skb);
}
}
 
kfree_skb(skb);
return NET_RX_DROP;
}
 
static int dn_route_rx_long(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
unsigned char *ptr = skb->data;
 
if (skb->len < 21) /* 20 for long header, 1 for shortest nsp */
goto drop_it;
 
skb_pull(skb, 20);
skb->h.raw = skb->data;
 
/* Destination info */
ptr += 2;
cb->dst = dn_htons(dn_eth2dn(ptr));
if (memcmp(ptr, dn_hiord_addr, 4) != 0)
goto drop_it;
ptr += 6;
 
 
/* Source info */
ptr += 2;
cb->src = dn_htons(dn_eth2dn(ptr));
if (memcmp(ptr, dn_hiord_addr, 4) != 0)
goto drop_it;
ptr += 6;
/* Other junk */
ptr++;
cb->hops = *ptr++; /* Visit Count */
 
return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
 
drop_it:
kfree_skb(skb);
return NET_RX_DROP;
}
 
 
 
static int dn_route_rx_short(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
unsigned char *ptr = skb->data;
 
if (skb->len < 6) /* 5 for short header + 1 for shortest nsp */
goto drop_it;
 
skb_pull(skb, 5);
skb->h.raw = skb->data;
 
cb->dst = *(dn_address *)ptr;
ptr += 2;
cb->src = *(dn_address *)ptr;
ptr += 2;
cb->hops = *ptr & 0x3f;
 
return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
 
drop_it:
kfree_skb(skb);
return NET_RX_DROP;
}
 
static int dn_route_discard(struct sk_buff *skb)
{
/*
* I know we drop the packet here, but thats considered success in
* this case
*/
kfree_skb(skb);
return NET_RX_SUCCESS;
}
 
static int dn_route_ptp_hello(struct sk_buff *skb)
{
dn_dev_hello(skb);
dn_neigh_pointopoint_hello(skb);
return NET_RX_SUCCESS;
}
 
int dn_route_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
{
struct dn_skb_cb *cb;
unsigned char flags = 0;
__u16 len = dn_ntohs(*(__u16 *)skb->data);
struct dn_dev *dn = (struct dn_dev *)dev->dn_ptr;
unsigned char padlen = 0;
 
if (dn == NULL)
goto dump_it;
 
if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
goto out;
 
skb_pull(skb, 2);
 
if (len > skb->len)
goto dump_it;
 
skb_trim(skb, len);
 
flags = *skb->data;
 
cb = DN_SKB_CB(skb);
cb->stamp = jiffies;
cb->iif = dev->ifindex;
 
/*
* If we have padding, remove it.
*/
if (flags & DN_RT_F_PF) {
padlen = flags & ~DN_RT_F_PF;
skb_pull(skb, padlen);
flags = *skb->data;
}
 
skb->nh.raw = skb->data;
 
/*
* Weed out future version DECnet
*/
if (flags & DN_RT_F_VER)
goto dump_it;
 
cb->rt_flags = flags;
 
if (decnet_debug_level & 1)
printk(KERN_DEBUG
"dn_route_rcv: got 0x%02x from %s [%d %d %d]\n",
(int)flags, (dev) ? dev->name : "???", len, skb->len,
padlen);
 
if (flags & DN_RT_PKT_CNTL) {
switch(flags & DN_RT_CNTL_MSK) {
case DN_RT_PKT_INIT:
dn_dev_init_pkt(skb);
break;
case DN_RT_PKT_VERI:
dn_dev_veri_pkt(skb);
break;
}
 
if (dn->parms.state != DN_DEV_S_RU)
goto dump_it;
 
switch(flags & DN_RT_CNTL_MSK) {
case DN_RT_PKT_HELO:
return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_route_ptp_hello);
 
case DN_RT_PKT_L1RT:
case DN_RT_PKT_L2RT:
return NF_HOOK(PF_DECnet, NF_DN_ROUTE, skb, skb->dev, NULL, dn_route_discard);
case DN_RT_PKT_ERTH:
return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_router_hello);
 
case DN_RT_PKT_EEDH:
return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_endnode_hello);
}
} else {
if (dn->parms.state != DN_DEV_S_RU)
goto dump_it;
 
skb_pull(skb, 1); /* Pull flags */
 
switch(flags & DN_RT_PKT_MSK) {
case DN_RT_PKT_LONG:
return dn_route_rx_long(skb);
case DN_RT_PKT_SHORT:
return dn_route_rx_short(skb);
}
}
 
dump_it:
kfree_skb(skb);
out:
return NET_RX_DROP;
}
 
static int dn_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct dn_route *rt = (struct dn_route *)dst;
struct net_device *dev = dst->dev;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct neighbour *neigh;
 
int err = -EINVAL;
 
if ((neigh = dst->neighbour) == NULL)
goto error;
 
skb->dev = dev;
 
cb->src = rt->rt_saddr;
cb->dst = rt->rt_daddr;
 
/*
* Always set the Intra-Ethernet bit on all outgoing packets
* originated on this node. Only valid flag from upper layers
* is return-to-sender-requested. Set hop count to 0 too.
*/
cb->rt_flags &= ~DN_RT_F_RQR;
cb->rt_flags |= DN_RT_F_IE;
cb->hops = 0;
 
return NF_HOOK(PF_DECnet, NF_DN_LOCAL_OUT, skb, NULL, dev, neigh->output);
 
error:
if (net_ratelimit())
printk(KERN_DEBUG "dn_output: This should not happen\n");
 
kfree_skb(skb);
 
return err;
}
 
#ifdef CONFIG_DECNET_ROUTER
static int dn_forward(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct dst_entry *dst = skb->dst;
struct neighbour *neigh;
struct net_device *dev = skb->dev;
int err = -EINVAL;
 
if ((neigh = dst->neighbour) == NULL)
goto error;
 
/*
* Hop count exceeded.
*/
err = NET_RX_DROP;
if (++cb->hops > 30)
goto drop;
 
skb->dev = dst->dev;
 
/*
* If packet goes out same interface it came in on, then set
* the Intra-Ethernet bit. This has no effect for short
* packets, so we don't need to test for them here.
*/
if (cb->iif == dst->dev->ifindex)
cb->rt_flags |= DN_RT_F_IE;
else
cb->rt_flags &= ~DN_RT_F_IE;
 
return NF_HOOK(PF_DECnet, NF_DN_FORWARD, skb, dev, skb->dev, neigh->output);
 
 
error:
if (net_ratelimit())
printk(KERN_DEBUG "dn_forward: This should not happen\n");
drop:
kfree_skb(skb);
 
return err;
}
#endif
 
/*
* Drop packet. This is used for endnodes and for
* when we should not be forwarding packets from
* this dest.
*/
static int dn_blackhole(struct sk_buff *skb)
{
kfree_skb(skb);
return NET_RX_DROP;
}
 
/*
* Used to catch bugs. This should never normally get
* called.
*/
static int dn_rt_bug(struct sk_buff *skb)
{
if (net_ratelimit()) {
struct dn_skb_cb *cb = DN_SKB_CB(skb);
 
printk(KERN_DEBUG "dn_rt_bug: skb from:%04x to:%04x\n",
cb->src, cb->dst);
}
 
kfree_skb(skb);
 
return NET_RX_BAD;
}
 
static int dn_route_output_slow(struct dst_entry **pprt, dn_address dst, dn_address src, int flags)
{
struct dn_route *rt = NULL;
struct net_device *dev = decnet_default_device;
struct neighbour *neigh = NULL;
struct dn_dev *dn_db;
unsigned hash;
#ifdef CONFIG_DECNET_ROUTER
struct dn_fib_key key;
struct dn_fib_res res;
int err;
 
key.src = src;
key.dst = dst;
key.iif = 0;
key.oif = 0;
key.fwmark = 0;
key.scope = RT_SCOPE_UNIVERSE;
 
if ((err = dn_fib_lookup(&key, &res)) == 0) {
switch(res.type) {
case RTN_UNICAST:
/*
* This method of handling multipath
* routes is a hack and will change.
* It works for now though.
*/
if (res.fi->fib_nhs)
dn_fib_select_multipath(&key, &res);
neigh = __neigh_lookup(&dn_neigh_table, &DN_FIB_RES_GW(res), DN_FIB_RES_DEV(res), 1);
err = -ENOBUFS;
if (!neigh)
break;
err = 0;
break;
case RTN_UNREACHABLE:
err = -EHOSTUNREACH;
break;
default:
err = -EINVAL;
}
dn_fib_res_put(&res);
if (err < 0)
return err;
goto got_route;
}
 
if (err != -ESRCH)
return err;
#endif
 
/* Look in On-Ethernet cache first */
if (!(flags & MSG_TRYHARD)) {
if ((neigh = dn_neigh_lookup(&dn_neigh_table, &dst)) != NULL)
goto got_route;
}
 
if (dev == NULL)
return -EINVAL;
 
dn_db = dev->dn_ptr;
 
if (dn_db == NULL)
return -EINVAL;
 
/* Try default router */
if ((neigh = neigh_clone(dn_db->router)) != NULL)
goto got_route;
 
/* Send to default device (and hope for the best) if above fail */
if ((neigh = __neigh_lookup(&dn_neigh_table, &dst, dev, 1)) != NULL)
goto got_route;
 
 
return -EINVAL;
 
got_route:
 
if ((rt = dst_alloc(&dn_dst_ops)) == NULL) {
neigh_release(neigh);
return -EINVAL;
}
 
dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
rt->key.saddr = src;
rt->rt_saddr = src;
rt->key.daddr = dst;
rt->rt_daddr = dst;
rt->key.oif = neigh ? neigh->dev->ifindex : -1;
rt->key.iif = 0;
rt->key.fwmark = 0;
 
rt->u.dst.neighbour = neigh;
rt->u.dst.dev = neigh ? neigh->dev : NULL;
if (rt->u.dst.dev)
dev_hold(rt->u.dst.dev);
rt->u.dst.lastuse = jiffies;
rt->u.dst.output = dn_output;
rt->u.dst.input = dn_rt_bug;
 
if (dn_dev_islocal(neigh->dev, rt->rt_daddr))
rt->u.dst.input = dn_nsp_rx;
 
hash = dn_hash(rt->key.saddr, rt->key.daddr);
dn_insert_route(rt, hash);
*pprt = &rt->u.dst;
 
return 0;
}
 
int dn_route_output(struct dst_entry **pprt, dn_address dst, dn_address src, int flags)
{
unsigned hash = dn_hash(src, dst);
struct dn_route *rt = NULL;
 
if (!(flags & MSG_TRYHARD)) {
read_lock_bh(&dn_rt_hash_table[hash].lock);
for(rt = dn_rt_hash_table[hash].chain; rt; rt = rt->u.rt_next) {
if ((dst == rt->key.daddr) &&
(src == rt->key.saddr) &&
(rt->key.iif == 0) &&
(rt->key.oif != 0)) {
rt->u.dst.lastuse = jiffies;
dst_hold(&rt->u.dst);
rt->u.dst.__use++;
read_unlock_bh(&dn_rt_hash_table[hash].lock);
*pprt = &rt->u.dst;
return 0;
}
}
read_unlock_bh(&dn_rt_hash_table[hash].lock);
}
 
return dn_route_output_slow(pprt, dst, src, flags);
}
 
static int dn_route_input_slow(struct sk_buff *skb)
{
struct dn_route *rt = NULL;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct net_device *dev = skb->dev;
struct dn_dev *dn_db;
struct neighbour *neigh = NULL;
int (*dnrt_input)(struct sk_buff *skb);
int (*dnrt_output)(struct sk_buff *skb);
u32 fwmark = 0;
unsigned hash;
dn_address saddr = cb->src;
dn_address daddr = cb->dst;
#ifdef CONFIG_DECNET_ROUTER
struct dn_fib_key key;
struct dn_fib_res res;
int err;
#endif
 
if (dev == NULL)
return -EINVAL;
 
if ((dn_db = dev->dn_ptr) == NULL)
return -EINVAL;
 
/*
* In this case we've just received a packet from a source
* outside ourselves pretending to come from us. We don't
* allow it any further to prevent routing loops, spoofing and
* other nasties. Loopback packets already have the dst attached
* so this only affects packets which have originated elsewhere.
*/
if (dn_dev_islocal(dev, cb->src))
return -ENOTUNIQ;
 
/*
* Default is to create a drop everything entry
*/
dnrt_input = dn_blackhole;
dnrt_output = dn_rt_bug;
 
/*
* Is the destination us ?
*/
if (!dn_dev_islocal(dev, cb->dst))
goto non_local_input;
 
/*
* Local input... find source of skb
*/
dnrt_input = dn_nsp_rx;
dnrt_output = dn_output;
saddr = cb->dst;
daddr = cb->src;
 
if ((neigh = neigh_lookup(&dn_neigh_table, &cb->src, dev)) != NULL)
goto add_entry;
 
if (dn_db->router && ((neigh = neigh_clone(dn_db->router)) != NULL))
goto add_entry;
 
neigh = neigh_create(&dn_neigh_table, &cb->src, dev);
if (!IS_ERR(neigh)) {
if (dev->type == ARPHRD_ETHER)
memcpy(neigh->ha, skb->mac.ethernet->h_source, ETH_ALEN);
goto add_entry;
}
 
return PTR_ERR(neigh);
 
non_local_input:
 
#ifdef CONFIG_DECNET_ROUTER
/*
* Destination is another node... find next hop in
* routing table here.
*/
 
key.src = cb->src;
key.dst = cb->dst;
key.iif = dev->ifindex;
key.oif = 0;
key.scope = RT_SCOPE_UNIVERSE;
 
#ifdef CONFIG_DECNET_ROUTE_FWMARK
key.fwmark = skb->nfmark;
#else
key.fwmark = 0;
#endif
 
if ((err = dn_fib_lookup(&key, &res)) == 0) {
switch(res.type) {
case RTN_UNICAST:
if (res.fi->fib_nhs)
dn_fib_select_multipath(&key, &res);
neigh = __neigh_lookup(&dn_neigh_table, &DN_FIB_RES_GW(res), DN_FIB_RES_DEV(res), 1);
err = -ENOBUFS;
if (!neigh)
break;
err = 0;
dnrt_input = dn_forward;
fwmark = key.fwmark;
break;
case RTN_UNREACHABLE:
dnrt_input = dn_blackhole;
fwmark = key.fwmark;
break;
default:
err = -EINVAL;
}
dn_fib_res_put(&res);
if (err < 0)
return err;
goto add_entry;
}
 
return err;
 
#endif /* CONFIG_DECNET_ROUTER */
 
add_entry:
 
if ((rt = dst_alloc(&dn_dst_ops)) == NULL) {
neigh_release(neigh);
return -EINVAL;
}
 
rt->key.saddr = cb->src;
rt->rt_saddr = saddr;
rt->key.daddr = cb->dst;
rt->rt_daddr = daddr;
rt->key.oif = 0;
rt->key.iif = dev->ifindex;
rt->key.fwmark = fwmark;
 
rt->u.dst.neighbour = neigh;
rt->u.dst.dev = neigh ? neigh->dev : NULL;
if (rt->u.dst.dev)
dev_hold(rt->u.dst.dev);
rt->u.dst.lastuse = jiffies;
rt->u.dst.output = dnrt_output;
rt->u.dst.input = dnrt_input;
 
hash = dn_hash(rt->key.saddr, rt->key.daddr);
dn_insert_route(rt, hash);
skb->dst = (struct dst_entry *)rt;
 
return 0;
}
 
int dn_route_input(struct sk_buff *skb)
{
struct dn_route *rt;
struct dn_skb_cb *cb = DN_SKB_CB(skb);
unsigned hash = dn_hash(cb->src, cb->dst);
 
if (skb->dst)
return 0;
 
read_lock(&dn_rt_hash_table[hash].lock);
for(rt = dn_rt_hash_table[hash].chain; rt != NULL; rt = rt->u.rt_next) {
if ((rt->key.saddr == cb->src) &&
(rt->key.daddr == cb->dst) &&
(rt->key.oif == 0) &&
#ifdef CONFIG_DECNET_ROUTE_FWMARK
(rt->key.fwmark == skb->nfmark) &&
#endif
(rt->key.iif == cb->iif)) {
rt->u.dst.lastuse = jiffies;
dst_hold(&rt->u.dst);
rt->u.dst.__use++;
read_unlock(&dn_rt_hash_table[hash].lock);
skb->dst = (struct dst_entry *)rt;
return 0;
}
}
read_unlock(&dn_rt_hash_table[hash].lock);
 
return dn_route_input_slow(skb);
}
 
static int dn_rt_fill_info(struct sk_buff *skb, u32 pid, u32 seq, int event, int nowait)
{
struct dn_route *rt = (struct dn_route *)skb->dst;
struct rtmsg *r;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
 
nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*r));
r = NLMSG_DATA(nlh);
nlh->nlmsg_flags = nowait ? NLM_F_MULTI : 0;
r->rtm_family = AF_DECnet;
r->rtm_dst_len = 16;
r->rtm_src_len = 16;
r->rtm_tos = 0;
r->rtm_table = 0;
r->rtm_type = 0;
r->rtm_flags = 0;
r->rtm_scope = RT_SCOPE_UNIVERSE;
r->rtm_protocol = RTPROT_UNSPEC;
RTA_PUT(skb, RTA_DST, 2, &rt->rt_daddr);
RTA_PUT(skb, RTA_SRC, 2, &rt->rt_saddr);
if (rt->u.dst.dev)
RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->u.dst.dev->ifindex);
if (rt->u.dst.window)
RTA_PUT(skb, RTAX_WINDOW, sizeof(unsigned), &rt->u.dst.window);
if (rt->u.dst.rtt)
RTA_PUT(skb, RTAX_RTT, sizeof(unsigned), &rt->u.dst.rtt);
 
nlh->nlmsg_len = skb->tail - b;
return skb->len;
 
nlmsg_failure:
rtattr_failure:
skb_trim(skb, b - skb->data);
return -1;
}
 
/*
* This is called by both endnodes and routers now.
*/
int dn_cache_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, void *arg)
{
struct rtattr **rta = arg;
struct dn_route *rt = NULL;
struct dn_skb_cb *cb;
dn_address dst = 0;
dn_address src = 0;
int iif = 0;
int err;
struct sk_buff *skb;
 
skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
return -ENOBUFS;
skb->mac.raw = skb->data;
cb = DN_SKB_CB(skb);
 
if (rta[RTA_SRC-1])
memcpy(&src, RTA_DATA(rta[RTA_SRC-1]), 2);
if (rta[RTA_DST-1])
memcpy(&dst, RTA_DATA(rta[RTA_DST-1]), 2);
if (rta[RTA_IIF-1])
memcpy(&iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
 
if (iif) {
struct net_device *dev;
if ((dev = dev_get_by_index(iif)) == NULL) {
kfree_skb(skb);
return -ENODEV;
}
if (!dev->dn_ptr) {
dev_put(dev);
kfree_skb(skb);
return -ENODEV;
}
skb->protocol = __constant_htons(ETH_P_DNA_RT);
skb->dev = dev;
cb->src = src;
cb->dst = dst;
local_bh_disable();
err = dn_route_input(skb);
local_bh_enable();
memset(cb, 0, sizeof(struct dn_skb_cb));
rt = (struct dn_route *)skb->dst;
} else {
err = dn_route_output((struct dst_entry **)&rt, dst, src, 0);
}
 
if (!err && rt->u.dst.error)
err = rt->u.dst.error;
if (skb->dev)
dev_put(skb->dev);
skb->dev = NULL;
if (err)
goto out_free;
skb->dst = &rt->u.dst;
 
NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
 
err = dn_rt_fill_info(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq, RTM_NEWROUTE, 0);
 
if (err == 0)
goto out_free;
if (err < 0) {
err = -EMSGSIZE;
goto out_free;
}
 
err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
 
return err;
 
out_free:
kfree_skb(skb);
return err;
}
 
/*
* For routers, this is called from dn_fib_dump, but for endnodes its
* called directly from the rtnetlink dispatch table.
*/
int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
struct dn_route *rt;
int h, s_h;
int idx, s_idx;
 
if (NLMSG_PAYLOAD(cb->nlh, 0) < sizeof(struct rtmsg))
return -EINVAL;
if (!(((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED))
return 0;
 
s_h = cb->args[0];
s_idx = idx = cb->args[1];
for(h = 0; h <= dn_rt_hash_mask; h++) {
if (h < s_h)
continue;
if (h > s_h)
s_idx = 0;
read_lock_bh(&dn_rt_hash_table[h].lock);
for(rt = dn_rt_hash_table[h].chain, idx = 0; rt; rt = rt->u.rt_next, idx++) {
if (idx < s_idx)
continue;
skb->dst = dst_clone(&rt->u.dst);
if (dn_rt_fill_info(skb, NETLINK_CB(cb->skb).pid,
cb->nlh->nlmsg_seq, RTM_NEWROUTE, 1) <= 0) {
dst_release(xchg(&skb->dst, NULL));
read_unlock_bh(&dn_rt_hash_table[h].lock);
goto done;
}
dst_release(xchg(&skb->dst, NULL));
}
read_unlock_bh(&dn_rt_hash_table[h].lock);
}
 
done:
cb->args[0] = h;
cb->args[1] = idx;
return skb->len;
}
 
#ifdef CONFIG_PROC_FS
 
static int decnet_cache_get_info(char *buffer, char **start, off_t offset, int length)
{
int len = 0;
off_t pos = 0;
off_t begin = 0;
struct dn_route *rt;
int i;
char buf1[DN_ASCBUF_LEN], buf2[DN_ASCBUF_LEN];
 
for(i = 0; i <= dn_rt_hash_mask; i++) {
read_lock_bh(&dn_rt_hash_table[i].lock);
rt = dn_rt_hash_table[i].chain;
for(; rt != NULL; rt = rt->u.rt_next) {
len += sprintf(buffer + len, "%-8s %-7s %-7s %04d %04d %04d\n",
rt->u.dst.dev ? rt->u.dst.dev->name : "*",
dn_addr2asc(dn_ntohs(rt->rt_daddr), buf1),
dn_addr2asc(dn_ntohs(rt->rt_saddr), buf2),
atomic_read(&rt->u.dst.__refcnt),
rt->u.dst.__use,
(int)rt->u.dst.rtt
);
 
 
 
pos = begin + len;
if (pos < offset) {
len = 0;
begin = pos;
}
if (pos > offset + length)
break;
}
read_unlock_bh(&dn_rt_hash_table[i].lock);
if (pos > offset + length)
break;
}
 
*start = buffer + (offset - begin);
len -= (offset - begin);
 
if (len > length) len = length;
 
return(len);
}
 
#endif /* CONFIG_PROC_FS */
 
void __init dn_route_init(void)
{
int i, goal, order;
 
dn_dst_ops.kmem_cachep = kmem_cache_create("dn_dst_cache",
sizeof(struct dn_route),
0, SLAB_HWCACHE_ALIGN,
NULL, NULL);
 
if (!dn_dst_ops.kmem_cachep)
panic("DECnet: Failed to allocate dn_dst_cache\n");
 
dn_route_timer.function = dn_dst_check_expire;
dn_route_timer.expires = jiffies + decnet_dst_gc_interval * HZ;
add_timer(&dn_route_timer);
 
goal = num_physpages >> (26 - PAGE_SHIFT);
 
for(order = 0; (1UL << order) < goal; order++)
/* NOTHING */;
 
/*
* Only want 1024 entries max, since the table is very, very unlikely
* to be larger than that.
*/
while(order && ((((1UL << order) * PAGE_SIZE) /
sizeof(struct dn_rt_hash_bucket)) >= 2048))
order--;
 
do {
dn_rt_hash_mask = (1UL << order) * PAGE_SIZE /
sizeof(struct dn_rt_hash_bucket);
while(dn_rt_hash_mask & (dn_rt_hash_mask - 1))
dn_rt_hash_mask--;
dn_rt_hash_table = (struct dn_rt_hash_bucket *)
__get_free_pages(GFP_ATOMIC, order);
} while (dn_rt_hash_table == NULL && --order > 0);
 
if (!dn_rt_hash_table)
panic("Failed to allocate DECnet route cache hash table\n");
 
printk(KERN_INFO
"DECnet: Routing cache hash table of %u buckets, %ldKbytes\n",
dn_rt_hash_mask,
(long)(dn_rt_hash_mask*sizeof(struct dn_rt_hash_bucket))/1024);
 
dn_rt_hash_mask--;
for(i = 0; i <= dn_rt_hash_mask; i++) {
dn_rt_hash_table[i].lock = RW_LOCK_UNLOCKED;
dn_rt_hash_table[i].chain = NULL;
}
 
dn_dst_ops.gc_thresh = (dn_rt_hash_mask + 1);
 
#ifdef CONFIG_PROC_FS
proc_net_create("decnet_cache",0,decnet_cache_get_info);
#endif /* CONFIG_PROC_FS */
}
 
void __exit dn_route_cleanup(void)
{
del_timer(&dn_route_timer);
dn_run_flush(0);
 
proc_net_remove("decnet_cache");
}
 
/dn_timer.c
0,0 → 1,155
/*
* 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 <asm/atomic.h>
#include <net/dn.h>
 
/*
* Fast timer is for delayed acks (200mS max)
* Slow timer is for everything else (n * 500mS)
*/
 
#define FAST_INTERVAL (HZ/5)
#define SLOW_INTERVAL (HZ/2)
 
static void dn_slow_timer(unsigned long arg);
 
void dn_start_slow_timer(struct sock *sk)
{
sk->timer.expires = jiffies + SLOW_INTERVAL;
sk->timer.function = dn_slow_timer;
sk->timer.data = (unsigned long)sk;
 
add_timer(&sk->timer);
}
 
void dn_stop_slow_timer(struct sock *sk)
{
del_timer(&sk->timer);
}
 
static void dn_slow_timer(unsigned long arg)
{
struct sock *sk = (struct sock *)arg;
struct dn_scp *scp = DN_SK(sk);
 
sock_hold(sk);
bh_lock_sock(sk);
 
if (sk->lock.users != 0) {
sk->timer.expires = jiffies + HZ / 10;
add_timer(&sk->timer);
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 ((jiffies - scp->stamp) >= scp->keepalive)
scp->keepalive_fxn(sk);
}
 
sk->timer.expires = jiffies + SLOW_INTERVAL;
 
add_timer(&sk->timer);
out:
bh_unlock_sock(sk);
sock_put(sk);
}
 
static void dn_fast_timer(unsigned long arg)
{
struct sock *sk = (struct sock *)arg;
struct dn_scp *scp = DN_SK(sk);
 
bh_lock_sock(sk);
if (sk->lock.users != 0) {
scp->delack_timer.expires = jiffies + HZ / 20;
add_timer(&scp->delack_timer);
goto out;
}
 
scp->delack_pending = 0;
 
if (scp->delack_fxn)
scp->delack_fxn(sk);
out:
bh_unlock_sock(sk);
}
 
void dn_start_fast_timer(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
if (!scp->delack_pending) {
scp->delack_pending = 1;
init_timer(&scp->delack_timer);
scp->delack_timer.expires = jiffies + FAST_INTERVAL;
scp->delack_timer.data = (unsigned long)sk;
scp->delack_timer.function = dn_fast_timer;
add_timer(&scp->delack_timer);
}
}
 
void dn_stop_fast_timer(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
if (scp->delack_pending) {
scp->delack_pending = 0;
del_timer(&scp->delack_timer);
}
}
 
/dn_fib.c
0,0 → 1,661
/*
* 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.
*
*/
#include <linux/config.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.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 <asm/atomic.h>
#include <asm/uaccess.h>
#include <net/neighbour.h>
#include <net/dst.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 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) }
 
extern int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb);
 
 
static struct dn_fib_info *dn_fib_info_list;
static rwlock_t dn_fib_info_lock = RW_LOCK_UNLOCKED;
int dn_fib_info_cnt;
 
static struct
{
int error;
u8 scope;
} dn_fib_props[RTA_MAX+1] = {
{ 0, RT_SCOPE_NOWHERE }, /* RTN_UNSPEC */
{ 0, RT_SCOPE_UNIVERSE }, /* RTN_UNICAST */
{ 0, RT_SCOPE_HOST }, /* RTN_LOCAL */
{ -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_BROADCAST */
{ -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_ANYCAST */
{ -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_MULTICAST */
{ -EINVAL, RT_SCOPE_UNIVERSE }, /* RTN_BLACKHOLE */
{ -EHOSTUNREACH, RT_SCOPE_UNIVERSE }, /* RTN_UNREACHABLE */
{ -EACCES, RT_SCOPE_UNIVERSE }, /* RTN_PROHIBIT */
{ -EAGAIN, RT_SCOPE_UNIVERSE }, /* RTN_THROW */
{ -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_NAT */
{ -EINVAL, RT_SCOPE_NOWHERE } /* RTN_XRESOLVE */
};
 
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);
dn_fib_info_cnt--;
kfree(fi);
}
 
void dn_fib_release_info(struct dn_fib_info *fi)
{
write_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);
}
write_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 &&
((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;
}
 
u16 dn_fib_get_attr16(struct rtattr *attr, int attrlen, int type)
{
while(RTA_OK(attr,attrlen)) {
if (attr->rta_type == type)
return *(u16*)RTA_DATA(attr);
attr = RTA_NEXT(attr, attrlen);
}
 
return 0;
}
 
static int dn_fib_count_nhs(struct rtattr *rta)
{
int nhs = 0;
struct rtnexthop *nhp = RTA_DATA(rta);
int nhlen = RTA_PAYLOAD(rta);
 
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 rtattr *rta, const struct rtmsg *r)
{
struct rtnexthop *nhp = RTA_DATA(rta);
int nhlen = RTA_PAYLOAD(rta);
 
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) {
nh->nh_gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
}
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 dn_fib_key key;
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 ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
atomic_inc(&dev->refcnt);
nh->nh_scope = RT_SCOPE_LINK;
return 0;
}
 
memset(&key, 0, sizeof(key));
key.dst = nh->nh_gw;
key.oif = nh->nh_oif;
key.scope = r->rtm_scope + 1;
 
if (key.scope < RT_SCOPE_LINK)
key.scope = RT_SCOPE_LINK;
 
if ((err = dn_fib_lookup(&key, &res)) != 0)
return err;
 
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)
atomic_inc(&nh->nh_dev->refcnt);
dn_fib_res_put(&res);
} else {
struct net_device *dev;
 
if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
return -EINVAL;
 
dev = __dev_get_by_index(nh->nh_oif);
if (dev == NULL || dev->dn_ptr == NULL)
return -ENODEV;
if (!(dev->flags&IFF_UP))
return -ENETDOWN;
nh->nh_dev = dev;
atomic_inc(&nh->nh_dev->refcnt);
nh->nh_scope = RT_SCOPE_HOST;
}
 
return 0;
}
 
 
struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct dn_kern_rta *rta, const struct nlmsghdr *nlh, int *errp)
{
int err;
struct dn_fib_info *fi = NULL;
struct dn_fib_info *ofi;
int nhs = 1;
 
if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
goto err_inval;
 
if (rta->rta_mp) {
nhs = dn_fib_count_nhs(rta->rta_mp);
if (nhs == 0)
goto err_inval;
}
 
fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL);
err = -ENOBUFS;
if (fi == NULL)
goto failure;
memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct dn_fib_nh));
 
fi->fib_protocol = r->rtm_protocol;
fi->fib_nhs = nhs;
fi->fib_flags = r->rtm_flags;
if (rta->rta_priority)
fi->fib_priority = *rta->rta_priority;
if (rta->rta_prefsrc)
memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 2);
 
if (rta->rta_mp) {
if ((err = dn_fib_get_nhs(fi, rta->rta_mp, r)) != 0)
goto failure;
if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
goto err_inval;
if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 2))
goto err_inval;
} else {
struct dn_fib_nh *nh = fi->fib_nh;
if (rta->rta_oif)
nh->nh_oif = *rta->rta_oif;
if (rta->rta_gw)
memcpy(&nh->nh_gw, rta->rta_gw, 2);
nh->nh_flags = r->rtm_flags;
nh->nh_weight = 1;
}
 
if (dn_fib_props[r->rtm_type].error) {
if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
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(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 I_GET_AROUND_TO_FIXING_PREFSRC
if (fi->fib_prefsrc) {
if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
memcmp(&fi->fib_prefsrc, rta->rta_dst, 2))
if (dn_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
goto err_inval;
}
#endif
 
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);
write_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;
dn_fib_info_cnt++;
write_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;
}
 
 
void dn_fib_select_multipath(const struct dn_fib_key *key, struct dn_fib_res *res)
{
struct dn_fib_info *fi = res->fi;
int w;
 
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;
}
 
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;
return;
}
}
} endfor_nexthops(fi);
 
printk(KERN_DEBUG "DECnet: BUG! dn_fib_select_multipath\n");
}
 
 
 
/*
* Punt to user via netlink for example, but for now
* we just drop it.
*/
int dn_fib_rt_message(struct sk_buff *skb)
{
kfree_skb(skb);
 
return 0;
}
 
 
static int dn_fib_check_attr(struct rtmsg *r, struct rtattr **rta)
{
int i;
 
for(i = 1; i <= RTA_MAX; i++) {
struct rtattr *attr = rta[i-1];
if (attr) {
if (RTA_PAYLOAD(attr) < 4 && RTA_PAYLOAD(attr) != 2)
return -EINVAL;
if (i != RTA_MULTIPATH && i != RTA_METRICS)
rta[i-1] = (struct rtattr *)RTA_DATA(attr);
}
}
 
return 0;
}
 
int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct dn_fib_table *tb;
struct rtattr **rta = arg;
struct rtmsg *r = NLMSG_DATA(nlh);
 
if (dn_fib_check_attr(r, rta))
return -EINVAL;
 
tb = dn_fib_get_table(r->rtm_table, 0);
if (tb)
return tb->delete(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
 
return -ESRCH;
}
 
int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct dn_fib_table *tb;
struct rtattr **rta = arg;
struct rtmsg *r = NLMSG_DATA(nlh);
 
if (dn_fib_check_attr(r, rta))
return -EINVAL;
 
tb = dn_fib_get_table(r->rtm_table, 1);
if (tb)
return tb->insert(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
 
return -ENOBUFS;
}
 
 
int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
int t;
int s_t;
struct dn_fib_table *tb;
 
if (NLMSG_PAYLOAD(cb->nlh, 0) >= sizeof(struct rtmsg) &&
((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED)
return dn_cache_dump(skb, cb);
 
s_t = cb->args[0];
if (s_t == 0)
s_t = cb->args[0] = DN_MIN_TABLE;
 
for(t = s_t; t < DN_NUM_TABLES; t++) {
if (t < s_t)
continue;
if (t > s_t)
memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(int));
tb = dn_fib_get_table(t, 0);
if (tb == NULL)
continue;
if (tb->dump(tb, skb, cb) < 0)
break;
}
 
cb->args[0] = t;
 
return skb->len;
}
 
int dn_fib_sync_down(dn_address 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) {
nh->nh_flags |= RTNH_F_DEAD;
fi->fib_power -= nh->nh_power;
nh->nh_power = 0;
dead++;
}
} endfor_nexthops(fi)
if (dead == fi->fib_nhs) {
fi->fib_flags |= RTNH_F_DEAD;
ret++;
}
}
} endfor_fib_info();
return ret;
}
 
 
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++;
nh->nh_power = 0;
nh->nh_flags &= ~RTNH_F_DEAD;
} endfor_nexthops(fi);
 
if (alive == fi->fib_nhs) {
fi->fib_flags &= ~RTNH_F_DEAD;
ret++;
}
} endfor_fib_info();
return ret;
}
 
void dn_fib_flush(void)
{
int flushed = 0;
struct dn_fib_table *tb;
int id;
 
for(id = DN_NUM_TABLES; id > 0; id--) {
if ((tb = dn_fib_get_table(id, 0)) == NULL)
continue;
flushed += tb->flush(tb);
}
 
if (flushed)
dn_rt_cache_flush(-1);
}
 
int dn_fib_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
 
if (!capable(CAP_NET_ADMIN))
return -EPERM;
 
switch(cmd) {
case SIOCADDRT:
case SIOCDELRT:
return 0;
}
 
return -EINVAL;
}
 
#ifdef CONFIG_PROC_FS
 
static int decnet_rt_get_info(char *buffer, char **start, off_t offset, int length)
{
int first = offset / 128;
char *ptr = buffer;
int count = (length + 127) / 128;
int len;
int i;
struct dn_fib_table *tb;
 
*start = buffer + (offset % 128);
 
if (--first < 0) {
sprintf(buffer, "%-127s\n", "Iface\tDest\tGW \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT");
--count;
ptr += 128;
first = 0;
}
 
 
for(i = DN_MIN_TABLE; (i <= DN_NUM_TABLES) && (count > 0); i++) {
if ((tb = dn_fib_get_table(i, 0)) != NULL) {
int n = tb->get_info(tb, ptr, first, count);
count -= n;
ptr += n * 128;
}
}
 
len = ptr - *start;
if (len >= length)
return length;
if (len >= 0)
return len;
 
return 0;
}
#endif /* CONFIG_PROC_FS */
 
void __exit dn_fib_cleanup(void)
{
proc_net_remove("decnet_route");
 
dn_fib_table_cleanup();
dn_fib_rules_cleanup();
}
 
 
void __init dn_fib_init(void)
{
 
#ifdef CONFIG_PROC_FS
proc_net_create("decnet_route", 0, decnet_rt_get_info);
#endif
 
dn_fib_table_init();
dn_fib_rules_init();
}
 
 
/TODO
0,0 → 1,57
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 Start to hack together user level software and add more DECnet support
in ifconfig for example.
 
o Test adding/deleting of routes
 
o Test route lookup
 
o Test /proc/net/decnet_route route listing works correctly (maybe I'll
change the format of this file... atm its very similar to the IPv4 route
file)
 
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.5.xx]
 
o Test ip_gre tunneling works... it did the last time I tested it and it
will have to if I'm to test routing properly.
 
o Hello messages should be generated for each primary address on each
interface.
 
o Add the routing message grabbing netfilter module [written, tested,
awaiting merge]
 
o Add perfect socket hashing - an idea suggested by Paul Koning [part written,
awaiting debugging and merge]
 
o Add session control message flow control
 
o Add NSP message flow control
 
o DECnet sendpages() function
 
/dn_nsp_in.c
0,0 → 1,910
/*
* 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)
*/
 
/******************************************************************************
(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/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.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 <net/sock.h>
#include <asm/segment.h>
#include <asm/system.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_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 && net_ratelimit()) {
char *devname = skb->dev ? skb->dev->name : "???";
struct dn_skb_cb *cb = DN_SKB_CB(skb);
printk(KERN_INFO "DECnet: Martian packet (%s) dev=%s src=0x%04hx dst=0x%04hx srcport=0x%04hx dstport=0x%04hx\n", msg, devname, cb->src, cb->dst, cb->src_port, 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 (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 (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 && !sk->dead)
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)
{
unsigned short *ptr = (unsigned short *)skb->data;
int len = 0;
unsigned short ack;
 
if (skb->len < 2)
return len;
 
if ((ack = dn_ntohs(*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 = dn_ntohs(*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 = dn_ntohs(msg->segsize);
 
if (skb->len < 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->ack_backlog >= sk->max_ack_backlog) {
kfree_skb(skb);
return;
}
 
sk->ack_backlog++;
skb_queue_tail(&sk->receive_queue, skb);
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 = dn_ntohs(*(__u16 *)ptr);
 
if ((scp->state == DN_CI) || (scp->state == DN_CD)) {
scp->persist = 0;
scp->addrrem = cb->src_port;
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) {
unsigned char dlen = *skb->data;
if ((dlen <= 16) && (dlen <= skb->len)) {
scp->conndata_in.opt_optl = dlen;
memcpy(scp->conndata_in.opt_data, skb->data + 1, dlen);
}
}
dn_nsp_send_link(sk, DN_NOCHANGE, 0);
if (!sk->dead)
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 = dn_ntohs(*(__u16 *)skb->data);
skb_pull(skb, 2);
 
scp->discdata_in.opt_status = reason;
scp->discdata_in.opt_optl = 0;
memset(scp->discdata_in.opt_data, 0, 16);
 
if (skb->len > 0) {
unsigned char dlen = *skb->data;
if ((dlen <= 16) && (dlen <= skb->len)) {
scp->discdata_in.opt_optl = dlen;
memcpy(scp->discdata_in.opt_data, skb->data + 1, dlen);
}
}
 
scp->addrrem = cb->src_port;
sk->state = TCP_CLOSE;
 
switch(scp->state) {
case DN_CI:
case DN_CD:
scp->state = DN_RJ;
break;
case DN_RUN:
sk->shutdown |= SHUTDOWN_MASK;
scp->state = DN_DN;
break;
case DN_DI:
scp->state = DN_DIC;
break;
}
 
if (!sk->dead) {
if (sk->socket->state != SS_UNCONNECTED)
sk->socket->state = SS_DISCONNECTING;
sk->state_change(sk);
}
 
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 = dn_ntohs(*(__u16 *)skb->data);
 
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->shutdown |= SHUTDOWN_MASK;
case DN_CC:
scp->state = DN_CN;
}
 
if (!sk->dead) {
if (sk->socket->state != SS_UNCONNECTED)
sk->socket->state = SS_DISCONNECTING;
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;
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 = dn_ntohs(*(__u16 *)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 && !sk->dead)
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->rmem_alloc) + skb->truesize >= (unsigned)sk->rcvbuf) {
err = -ENOMEM;
goto out;
}
 
err = sk_filter(sk, skb, 0);
if (err)
goto out;
 
skb_set_owner_r(skb, sk);
skb_queue_tail(queue, skb);
 
/* This code only runs from BH or BH protected context.
* Therefore the plain read_lock is ok here. -DaveM
*/
read_lock(&sk->callback_lock);
if (!sk->dead) {
struct socket *sock = sk->socket;
wake_up_interruptible(sk->sleep);
if (sock && sock->fasync_list &&
!test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
__kill_fasync(sock->fasync_list, sig,
(sig == SIGURG) ? POLL_PRI : POLL_IN);
}
read_unlock(&sk->callback_lock);
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 = dn_ntohs(*(__u16 *)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 = dn_ntohs(*(__u16 *)skb->data);
skb_pull(skb, 2);
 
if (seq_next(scp->numdat_rcv, segnum)) {
if (dn_queue_skb(sk, skb, SIGIO, &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->state = TCP_CLOSE;
if (!sk->dead)
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;
 
skb->h.raw = skb->data;
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 (skb->len < 2)
goto free_out;
 
if (cb->nsp_flags & 0x83)
goto free_out;
 
/*
* Returned packets...
* Swap src & dst and look up in the normal way.
*/
if (cb->rt_flags & DN_RT_F_RTS) {
unsigned short tmp = cb->dst_port;
cb->dst_port = cb->src_port;
cb->src_port = tmp;
tmp = cb->dst;
cb->dst = cb->src;
cb->src = tmp;
sk = dn_find_by_skb(skb);
goto got_it;
}
 
/*
* 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:
sk = dn_find_listener(skb, &reason);
goto got_it;
}
}
 
if (skb->len < 3)
goto free_out;
 
/*
* Grab the destination address.
*/
cb->dst_port = *(unsigned short *)ptr;
cb->src_port = 0;
ptr += 2;
 
/*
* If not a connack, grab the source address too.
*/
if (skb->len >= 5) {
cb->src_port = *(unsigned short *)ptr;
ptr += 2;
skb_pull(skb, 5);
}
 
/*
* 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);
int ret;
 
/* Reset backoff */
scp->nsp_rxtshift = 0;
 
bh_lock_sock(sk);
ret = NET_RX_SUCCESS;
if (decnet_debug_level & 8)
printk(KERN_DEBUG "NSP: 0x%02x 0x%02x 0x%04x 0x%04x %d\n",
(int)cb->rt_flags, (int)cb->nsp_flags,
(int)cb->src_port, (int)cb->dst_port,
(int)sk->lock.users);
if (sk->lock.users == 0)
ret = dn_nsp_backlog_rcv(sk, skb);
else
sk_add_backlog(sk, skb);
bh_unlock_sock(sk);
sock_put(sk);
 
return ret;
}
 
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(PF_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) {
dn_returned_conn_init(sk, 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) && !sk->dead) {
scp->state = DN_RUN;
sk->state = TCP_ESTABLISHED;
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;
}
 
/af_decnet.c
0,0 → 1,2325
 
/*
* 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 Layer Interface
*
* Authors: Eduardo Marcelo Serrat <emserrat@geocities.com>
* Patrick Caulfield <patrick@pandh.demon.co.uk>
*
* Changes:
* Steve Whitehouse: Copied from Eduardo Serrat and Patrick Caulfield's
* version of the code. Original copyright preserved
* below.
* Steve Whitehouse: Some bug fixes, cleaning up some code to make it
* compatible with my routing layer.
* Steve Whitehouse: Merging changes from Eduardo Serrat and Patrick
* Caulfield.
* Steve Whitehouse: Further bug fixes, checking module code still works
* with new routing layer.
* Steve Whitehouse: Additional set/get_sockopt() calls.
* Steve Whitehouse: Fixed TIOCINQ ioctl to be same as Eduardo's new
* code.
* Steve Whitehouse: recvmsg() changed to try and behave in a POSIX like
* way. Didn't manage it entirely, but its better.
* Steve Whitehouse: ditto for sendmsg().
* Steve Whitehouse: A selection of bug fixes to various things.
* Steve Whitehouse: Added TIOCOUTQ ioctl.
* Steve Whitehouse: Fixes to username2sockaddr & sockaddr2username.
* Steve Whitehouse: Fixes to connect() error returns.
* Patrick Caulfield: Fixes to delayed acceptance logic.
* David S. Miller: New socket locking
* Steve Whitehouse: Socket list hashing/locking
* Arnaldo C. Melo: use capable, not suser
* Steve Whitehouse: Removed unused code. Fix to use sk->allocation
* when required.
* Patrick Caulfield: /proc/net/decnet now has object name/number
* Steve Whitehouse: Fixed local port allocation, hashed sk list
*/
 
 
/******************************************************************************
(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.
 
HISTORY:
 
Version Kernel Date Author/Comments
------- ------ ---- ---------------
Version 0.0.1 2.0.30 01-dic-97 Eduardo Marcelo Serrat
(emserrat@geocities.com)
 
First Development of DECnet Socket La-
yer for Linux. Only supports outgoing
connections.
 
Version 0.0.2 2.1.105 20-jun-98 Patrick J. Caulfield
(patrick@pandh.demon.co.uk)
 
Port to new kernel development version.
 
Version 0.0.3 2.1.106 25-jun-98 Eduardo Marcelo Serrat
(emserrat@geocities.com)
_
Added support for incoming connections
so we can start developing server apps
on Linux.
-
Module Support
Version 0.0.4 2.1.109 21-jul-98 Eduardo Marcelo Serrat
(emserrat@geocities.com)
_
Added support for X11R6.4. Now we can
use DECnet transport for X on Linux!!!
-
Version 0.0.5 2.1.110 01-aug-98 Eduardo Marcelo Serrat
(emserrat@geocities.com)
Removed bugs on flow control
Removed bugs on incoming accessdata
order
-
Version 0.0.6 2.1.110 07-aug-98 Eduardo Marcelo Serrat
dn_recvmsg fixes
 
Patrick J. Caulfield
dn_bind fixes
*******************************************************************************/
 
#include <linux/config.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/socket.h>
#include <linux/in.h>
#include <linux/kernel.h>
#include <linux/sched.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/netfilter.h>
#include <net/sock.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <asm/ioctls.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/poll.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>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
 
static void dn_keepalive(struct sock *sk);
 
/*
* decnet_address is kept in network order, decnet_ether_address is kept
* as a string of bytes.
*/
dn_address decnet_address = 0;
unsigned char decnet_ether_address[ETH_ALEN] = { 0xAA, 0x00, 0x04, 0x00, 0x00, 0x00 };
 
#define DN_SK_HASH_SHIFT 8
#define DN_SK_HASH_SIZE (1 << DN_SK_HASH_SHIFT)
#define DN_SK_HASH_MASK (DN_SK_HASH_SIZE - 1)
 
static struct proto_ops dn_proto_ops;
rwlock_t dn_hash_lock = RW_LOCK_UNLOCKED;
static struct sock *dn_sk_hash[DN_SK_HASH_SIZE];
static struct sock *dn_wild_sk;
 
static int __dn_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen, int flags);
static int __dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen, int flags);
 
static struct sock **dn_find_list(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
if (scp->addr.sdn_flags & SDF_WILD)
return dn_wild_sk ? NULL : &dn_wild_sk;
 
return &dn_sk_hash[scp->addrloc & DN_SK_HASH_MASK];
}
 
/*
* Valid ports are those greater than zero and not already in use.
*/
static int check_port(unsigned short port)
{
struct sock *sk = dn_sk_hash[port & DN_SK_HASH_MASK];
if (port == 0)
return -1;
while(sk) {
struct dn_scp *scp = DN_SK(sk);
if (scp->addrloc == port)
return -1;
sk = sk->next;
}
return 0;
}
 
static unsigned short port_alloc(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
static unsigned short port = 0x2000;
unsigned short i_port = port;
 
while(check_port(++port) != 0) {
if (port == i_port)
return 0;
}
 
scp->addrloc = port;
 
return 1;
}
 
/*
* Since this is only ever called from user
* level, we don't need a write_lock() version
* of this.
*/
static int dn_hash_sock(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
struct sock **skp;
int rv = -EUSERS;
 
if (sk->next)
BUG();
if (sk->pprev)
BUG();
 
write_lock_bh(&dn_hash_lock);
if (!scp->addrloc && !port_alloc(sk))
goto out;
 
rv = -EADDRINUSE;
if ((skp = dn_find_list(sk)) == NULL)
goto out;
 
sk->next = *skp;
sk->pprev = skp;
*skp = sk;
rv = 0;
out:
write_unlock_bh(&dn_hash_lock);
return rv;
}
 
static void dn_unhash_sock(struct sock *sk)
{
struct sock **skp = sk->pprev;
 
if (skp == NULL)
return;
 
write_lock(&dn_hash_lock);
while(*skp != sk)
skp = &((*skp)->next);
*skp = sk->next;
write_unlock(&dn_hash_lock);
 
sk->next = NULL;
sk->pprev = NULL;
}
 
static void dn_unhash_sock_bh(struct sock *sk)
{
struct sock **skp = sk->pprev;
 
if (skp == NULL)
return;
 
write_lock_bh(&dn_hash_lock);
while(*skp != sk)
skp = &((*skp)->next);
*skp = sk->next;
write_unlock_bh(&dn_hash_lock);
 
sk->next = NULL;
sk->pprev = NULL;
}
 
struct sock **listen_hash(struct sockaddr_dn *addr)
{
int i;
unsigned hash = addr->sdn_objnum;
 
if (hash == 0) {
hash = addr->sdn_objnamel;
for(i = 0; i < addr->sdn_objnamel; i++) {
hash ^= addr->sdn_objname[i];
hash ^= (hash << 3);
}
}
 
return &dn_sk_hash[hash & DN_SK_HASH_MASK];
}
 
/*
* Called to transform a socket from bound (i.e. with a local address)
* into a listening socket (doesn't need a local port number) and rehashes
* based upon the object name/number.
*/
static void dn_rehash_sock(struct sock *sk)
{
struct sock **skp = sk->pprev;
struct dn_scp *scp = DN_SK(sk);
 
if (scp->addr.sdn_flags & SDF_WILD)
return;
 
write_lock_bh(&dn_hash_lock);
while(*skp != sk)
skp = &((*skp)->next);
*skp = sk->next;
 
DN_SK(sk)->addrloc = 0;
skp = listen_hash(&DN_SK(sk)->addr);
 
sk->next = *skp;
sk->pprev = skp;
*skp = sk;
write_unlock_bh(&dn_hash_lock);
}
 
int dn_sockaddr2username(struct sockaddr_dn *sdn, unsigned char *buf, unsigned char type)
{
int len = 2;
 
*buf++ = type;
 
switch(type) {
case 0:
*buf++ = sdn->sdn_objnum;
break;
case 1:
*buf++ = 0;
*buf++ = dn_ntohs(sdn->sdn_objnamel);
memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
len = 3 + dn_ntohs(sdn->sdn_objnamel);
break;
case 2:
memset(buf, 0, 5);
buf += 5;
*buf++ = dn_ntohs(sdn->sdn_objnamel);
memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
len = 7 + dn_ntohs(sdn->sdn_objnamel);
break;
}
 
return len;
}
 
/*
* On reception of usernames, we handle types 1 and 0 for destination
* addresses only. Types 2 and 4 are used for source addresses, but the
* UIC, GIC are ignored and they are both treated the same way. Type 3
* is never used as I've no idea what its purpose might be or what its
* format is.
*/
int dn_username2sockaddr(unsigned char *data, int len, struct sockaddr_dn *sdn, unsigned char *fmt)
{
unsigned char type;
int size = len;
int namel = 12;
 
sdn->sdn_objnum = 0;
sdn->sdn_objnamel = dn_htons(0);
memset(sdn->sdn_objname, 0, DN_MAXOBJL);
 
if (len < 2)
return -1;
 
len -= 2;
*fmt = *data++;
type = *data++;
 
switch(*fmt) {
case 0:
sdn->sdn_objnum = type;
return 2;
case 1:
namel = 16;
break;
case 2:
len -= 4;
data += 4;
break;
case 4:
len -= 8;
data += 8;
break;
default:
return -1;
}
 
len -= 1;
 
if (len < 0)
return -1;
 
sdn->sdn_objnamel = dn_htons(*data++);
len -= dn_ntohs(sdn->sdn_objnamel);
 
if ((len < 0) || (dn_ntohs(sdn->sdn_objnamel) > namel))
return -1;
 
memcpy(sdn->sdn_objname, data, dn_ntohs(sdn->sdn_objnamel));
 
return size - len;
}
 
struct sock *dn_sklist_find_listener(struct sockaddr_dn *addr)
{
struct sock **skp = listen_hash(addr);
struct sock *sk;
 
read_lock(&dn_hash_lock);
for(sk = *skp; sk != NULL; sk = sk->next) {
struct dn_scp *scp = DN_SK(sk);
if (sk->state != TCP_LISTEN)
continue;
if (scp->addr.sdn_objnum) {
if (scp->addr.sdn_objnum != addr->sdn_objnum)
continue;
} else {
if (addr->sdn_objnum)
continue;
if (scp->addr.sdn_objnamel != addr->sdn_objnamel)
continue;
if (memcmp(scp->addr.sdn_objname, addr->sdn_objname, dn_ntohs(addr->sdn_objnamel)) != 0)
continue;
}
sock_hold(sk);
read_unlock(&dn_hash_lock);
return sk;
}
 
if (dn_wild_sk && (dn_wild_sk->state == TCP_LISTEN))
sock_hold((sk = dn_wild_sk));
 
read_unlock(&dn_hash_lock);
return sk;
}
 
struct sock *dn_find_by_skb(struct sk_buff *skb)
{
struct dn_skb_cb *cb = DN_SKB_CB(skb);
struct sock *sk;
struct dn_scp *scp;
 
read_lock(&dn_hash_lock);
sk = dn_sk_hash[cb->dst_port & DN_SK_HASH_MASK];
for (; sk != NULL; sk = sk->next) {
scp = DN_SK(sk);
if (cb->src != dn_saddr2dn(&scp->peer))
continue;
if (cb->dst_port != scp->addrloc)
continue;
if (scp->addrrem && (cb->src_port != scp->addrrem))
continue;
break;
}
 
if (sk)
sock_hold(sk);
 
read_unlock(&dn_hash_lock);
 
return sk;
}
 
 
 
static void dn_destruct(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
skb_queue_purge(&scp->data_xmit_queue);
skb_queue_purge(&scp->other_xmit_queue);
skb_queue_purge(&scp->other_receive_queue);
 
dst_release(xchg(&sk->dst_cache, NULL));
 
MOD_DEC_USE_COUNT;
}
 
struct sock *dn_alloc_sock(struct socket *sock, int gfp)
{
struct sock *sk;
struct dn_scp *scp;
 
if ((sk = sk_alloc(PF_DECnet, gfp, 1)) == NULL)
goto no_sock;
 
if (sock) {
sock->ops = &dn_proto_ops;
}
sock_init_data(sock,sk);
scp = DN_SK(sk);
 
sk->backlog_rcv = dn_nsp_backlog_rcv;
sk->destruct = dn_destruct;
sk->no_check = 1;
sk->family = PF_DECnet;
sk->protocol = 0;
sk->allocation = gfp;
 
/* Initialization of DECnet Session Control Port */
scp->state = DN_O; /* Open */
scp->numdat = 1; /* Next data seg to tx */
scp->numoth = 1; /* Next oth data to tx */
scp->ackxmt_dat = 0; /* Last data seg ack'ed */
scp->ackxmt_oth = 0; /* Last oth data ack'ed */
scp->ackrcv_dat = 0; /* Highest data ack recv*/
scp->ackrcv_oth = 0; /* Last oth data ack rec*/
scp->flowrem_sw = DN_SEND;
scp->flowloc_sw = DN_SEND;
scp->flowrem_dat = 0;
scp->flowrem_oth = 1;
scp->flowloc_dat = 0;
scp->flowloc_oth = 1;
scp->services_rem = 0;
scp->services_loc = 1 | NSP_FC_NONE;
scp->info_rem = 0;
scp->info_loc = 0x03; /* NSP version 4.1 */
scp->segsize_rem = 230; /* Default: Updated by remote segsize */
scp->segsize_loc = 1450; /* Best guess for ethernet */
scp->nonagle = 0;
scp->multi_ireq = 1;
scp->accept_mode = ACC_IMMED;
scp->addr.sdn_family = AF_DECnet;
scp->peer.sdn_family = AF_DECnet;
scp->accessdata.acc_accl = 5;
memcpy(scp->accessdata.acc_acc, "LINUX", 5);
 
scp->max_window = NSP_MAX_WINDOW;
scp->snd_window = NSP_MIN_WINDOW;
scp->nsp_srtt = NSP_INITIAL_SRTT;
scp->nsp_rttvar = NSP_INITIAL_RTTVAR;
scp->nsp_rxtshift = 0;
 
skb_queue_head_init(&scp->data_xmit_queue);
skb_queue_head_init(&scp->other_xmit_queue);
skb_queue_head_init(&scp->other_receive_queue);
 
scp->persist = 0;
scp->persist_fxn = NULL;
scp->keepalive = 10 * HZ;
scp->keepalive_fxn = dn_keepalive;
 
init_timer(&scp->delack_timer);
scp->delack_pending = 0;
scp->delack_fxn = dn_nsp_delayed_ack;
 
dn_start_slow_timer(sk);
 
MOD_INC_USE_COUNT;
 
return sk;
no_sock:
return NULL;
}
 
/*
* Keepalive timer.
* FIXME: Should respond to SO_KEEPALIVE etc.
*/
static void dn_keepalive(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
/*
* By checking the other_data transmit queue is empty
* we are double checking that we are not sending too
* many of these keepalive frames.
*/
if (skb_queue_len(&scp->other_xmit_queue) == 0)
dn_nsp_send_link(sk, DN_NOCHANGE, 0);
}
 
 
/*
* Timer for shutdown/destroyed sockets.
* When socket is dead & no packets have been sent for a
* certain amount of time, they are removed by this
* routine. Also takes care of sending out DI & DC
* frames at correct times.
*/
int dn_destroy_timer(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
scp->persist = dn_nsp_persist(sk);
 
switch(scp->state) {
case DN_DI:
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
if (scp->nsp_rxtshift >= decnet_di_count)
scp->state = DN_CN;
return 0;
 
case DN_DR:
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
if (scp->nsp_rxtshift >= decnet_dr_count)
scp->state = DN_DRC;
return 0;
 
case DN_DN:
if (scp->nsp_rxtshift < decnet_dn_count) {
/* printk(KERN_DEBUG "dn_destroy_timer: DN\n"); */
dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
return 0;
}
}
 
scp->persist = (HZ * decnet_time_wait);
 
if (sk->socket)
return 0;
 
dn_stop_fast_timer(sk); /* unlikely, but possible that this is runninng */
if ((jiffies - scp->stamp) >= (HZ * decnet_time_wait)) {
dn_unhash_sock(sk);
sock_put(sk);
return 1;
}
 
return 0;
}
 
static void dn_destroy_sock(struct sock *sk)
{
struct dn_scp *scp = DN_SK(sk);
 
scp->nsp_rxtshift = 0; /* reset back off */
 
if (sk->socket) {
if (sk->socket->state != SS_UNCONNECTED)
sk->socket->state = SS_DISCONNECTING;
}
 
sk->state = TCP_CLOSE;
 
switch(scp->state) {
case DN_DN:
dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, sk->allocation);
scp->persist_fxn = dn_destroy_timer;
scp->persist = dn_nsp_persist(sk);
break;
case DN_CR:
scp->state = DN_DR;
goto disc_reject;
case DN_RUN:
scp->state = DN_DI;
case DN_DI:
case DN_DR:
disc_reject:
dn_nsp_send_disc(sk, NSP_DISCINIT, 0, sk->allocation);
case DN_NC:
case DN_NR:
case DN_RJ:
case DN_DIC:
case DN_CN:
case DN_DRC:
case DN_CI:
case DN_CD:
scp->persist_fxn = dn_destroy_timer;
scp->persist = dn_nsp_persist(sk);
break;
default:
printk(KERN_DEBUG "DECnet: dn_destroy_sock passed socket in invalid state\n");
case DN_O:
dn_stop_fast_timer(sk);
dn_stop_slow_timer(sk);
 
dn_unhash_sock_bh(sk);
sock_put(sk);
 
break;
}
}
 
char *dn_addr2asc(dn_address addr, char *buf)
{
unsigned short node, area;
 
node = addr & 0x03ff;
area = addr >> 10;
sprintf(buf, "%hd.%hd", area, node);
 
return buf;
}
 
 
static char *dn_state2asc(unsigned char state)
{
switch(state) {
case DN_O:
return "OPEN";
case DN_CR:
return " CR";
case DN_DR:
return " DR";
case DN_DRC:
return " DRC";
case DN_CC:
return " CC";
case DN_CI:
return " CI";
case DN_NR:
return " NR";
case DN_NC:
return " NC";
case DN_CD:
return " CD";
case DN_RJ:
return " RJ";
case DN_RUN:
return " RUN";
case DN_DI:
return " DI";
case DN_DIC:
return " DIC";
case DN_DN:
return " DN";
case DN_CL:
return " CL";
case DN_CN:
return " CN";
}
 
return "????";
}
 
static int dn_create(struct socket *sock, int protocol)
{
struct sock *sk;
 
switch(sock->type) {
case SOCK_SEQPACKET:
if (protocol != DNPROTO_NSP)
return -EPROTONOSUPPORT;
break;
case SOCK_STREAM:
break;
default:
return -ESOCKTNOSUPPORT;
}
 
 
if ((sk = dn_alloc_sock(sock, GFP_KERNEL)) == NULL)
return -ENOBUFS;
 
sk->protocol = protocol;
 
return 0;
}
 
 
static int
dn_release(struct socket *sock)
{
struct sock *sk = sock->sk;
 
if (sk) {
sock_orphan(sk);
sock_hold(sk);
lock_sock(sk);
dn_destroy_sock(sk);
release_sock(sk);
sock_put(sk);
}
 
return 0;
}
 
static int dn_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
struct net_device *dev;
int rv;
 
if (sk->zapped == 0)
return -EINVAL;
 
if (addr_len != sizeof(struct sockaddr_dn))
return -EINVAL;
 
if (saddr->sdn_family != AF_DECnet)
return -EINVAL;
 
if (dn_ntohs(saddr->sdn_nodeaddrl) && (dn_ntohs(saddr->sdn_nodeaddrl) != 2))
return -EINVAL;
 
if (saddr->sdn_objnum && !capable(CAP_NET_BIND_SERVICE))
return -EPERM;
 
if (dn_ntohs(saddr->sdn_objnamel) > DN_MAXOBJL)
return -EINVAL;
 
if (saddr->sdn_flags & ~SDF_WILD)
return -EINVAL;
 
if (saddr->sdn_flags & SDF_WILD) {
if (!capable(CAP_NET_BIND_SERVICE))
return -EPERM;
} else {
if (dn_ntohs(saddr->sdn_nodeaddrl)) {
read_lock(&dev_base_lock);
for(dev = dev_base; dev; dev = dev->next) {
if (!dev->dn_ptr)
continue;
if (dn_dev_islocal(dev, dn_saddr2dn(saddr)))
break;
}
read_unlock(&dev_base_lock);
if (dev == NULL)
return -EADDRNOTAVAIL;
}
}
 
 
memcpy(&scp->addr, saddr, addr_len);
sk->zapped = 0;
 
if ((rv = dn_hash_sock(sk)) != 0)
sk->zapped = 1;
 
return rv;
}
 
 
static int dn_auto_bind(struct socket *sock)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
 
sk->zapped = 0;
 
scp->addr.sdn_flags = 0;
scp->addr.sdn_objnum = 0;
 
/*
* This stuff is to keep compatibility with Eduardo's
* patch. I hope I can dispense with it shortly...
*/
if ((scp->accessdata.acc_accl != 0) &&
(scp->accessdata.acc_accl <= 12)) {
scp->addr.sdn_objnamel = dn_htons(scp->accessdata.acc_accl);
memcpy(scp->addr.sdn_objname, scp->accessdata.acc_acc, dn_ntohs(scp->addr.sdn_objnamel));
 
scp->accessdata.acc_accl = 0;
memset(scp->accessdata.acc_acc, 0, 40);
}
 
scp->addr.sdn_add.a_len = dn_htons(2);
*(dn_address *)scp->addr.sdn_add.a_addr = decnet_address;
 
dn_hash_sock(sk);
 
return 0;
}
 
 
static int dn_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
int err = -EISCONN;
 
lock_sock(sk);
 
if (sock->state == SS_CONNECTED)
goto out;
 
if (sock->state == SS_CONNECTING) {
err = 0;
if (sk->state == TCP_ESTABLISHED)
goto out;
 
err = -ECONNREFUSED;
if (sk->state == TCP_CLOSE)
goto out;
}
 
err = -EINVAL;
if (DN_SK(sk)->state != DN_O)
goto out;
 
if (addr_len != sizeof(struct sockaddr_dn))
goto out;
 
if (addr->sdn_family != AF_DECnet)
goto out;
 
if (addr->sdn_flags & SDF_WILD)
goto out;
 
err = -EADDRNOTAVAIL;
if (sk->zapped && (err = dn_auto_bind(sock)))
goto out;
 
memcpy(&scp->peer, addr, addr_len);
 
err = -EHOSTUNREACH;
if (dn_route_output(&sk->dst_cache, dn_saddr2dn(&scp->peer), dn_saddr2dn(&scp->addr), 0) < 0)
goto out;
 
sk->state = TCP_SYN_SENT;
sock->state = SS_CONNECTING;
DN_SK(sk)->state = DN_CI;
 
dn_nsp_send_conninit(sk, NSP_CI);
 
err = -EINPROGRESS;
if ((sk->state == TCP_SYN_SENT) && (flags & O_NONBLOCK))
goto out;
 
while(sk->state == TCP_SYN_SENT) {
 
err = -ERESTARTSYS;
if (signal_pending(current))
goto out;
 
if ((err = sock_error(sk)) != 0) {
sock->state = SS_UNCONNECTED;
goto out;
}
 
SOCK_SLEEP_PRE(sk);
 
if (sk->state == TCP_SYN_SENT)
schedule();
 
SOCK_SLEEP_POST(sk);
}
 
if (sk->state != TCP_ESTABLISHED) {
sock->state = SS_UNCONNECTED;
err = sock_error(sk);
goto out;
}
 
err = 0;
sock->state = SS_CONNECTED;
out:
release_sock(sk);
 
return err;
}
 
static void dn_access_copy(struct sk_buff *skb, struct accessdata_dn *acc)
{
unsigned char *ptr = skb->data;
 
acc->acc_userl = *ptr++;
memcpy(&acc->acc_user, ptr, acc->acc_userl);
ptr += acc->acc_userl;
 
acc->acc_passl = *ptr++;
memcpy(&acc->acc_pass, ptr, acc->acc_passl);
ptr += acc->acc_passl;
 
acc->acc_accl = *ptr++;
memcpy(&acc->acc_acc, ptr, acc->acc_accl);
 
skb_pull(skb, acc->acc_accl + acc->acc_passl + acc->acc_userl + 3);
 
}
 
static void dn_user_copy(struct sk_buff *skb, struct optdata_dn *opt)
{
unsigned char *ptr = skb->data;
opt->opt_optl = *ptr++;
opt->opt_status = 0;
memcpy(opt->opt_data, ptr, opt->opt_optl);
skb_pull(skb, opt->opt_optl + 1);
 
}
 
 
/*
* This is here for use in the sockopt() call as well as
* in accept(). Must be called with a locked socket.
*/
static int dn_wait_accept(struct socket *sock, int flags)
{
struct sock *sk = sock->sk;
 
while(sk->state == TCP_LISTEN) {
if (flags & O_NONBLOCK) {
return -EAGAIN;
}
 
SOCK_SLEEP_PRE(sk)
 
if (sk->state == TCP_LISTEN)
schedule();
 
SOCK_SLEEP_POST(sk)
 
if (signal_pending(current))
return -ERESTARTSYS; /* But of course you don't! */
}
 
if ((DN_SK(sk)->state != DN_RUN) && (DN_SK(sk)->state != DN_DRC)) {
sock->state = SS_UNCONNECTED;
return sock_error(sk);
}
 
sock->state = SS_CONNECTED;
 
return 0;
}
 
 
static int dn_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk = sock->sk, *newsk;
struct sk_buff *skb = NULL;
struct dn_skb_cb *cb;
unsigned char menuver;
int err = 0;
unsigned char type;
 
lock_sock(sk);
 
if (sk->state != TCP_LISTEN) {
release_sock(sk);
return -EINVAL;
}
 
if (DN_SK(sk)->state != DN_O) {
release_sock(sk);
return -EINVAL;
}
 
do
{
if ((skb = skb_dequeue(&sk->receive_queue)) == NULL)
{
if (flags & O_NONBLOCK)
{
release_sock(sk);
return -EAGAIN;
}
 
SOCK_SLEEP_PRE(sk);
 
if (!skb_peek(&sk->receive_queue))
schedule();
 
SOCK_SLEEP_POST(sk);
 
if (signal_pending(current))
{
release_sock(sk);
return -ERESTARTSYS;
}
}
} while (skb == NULL);
 
cb = DN_SKB_CB(skb);
 
if ((newsk = dn_alloc_sock(newsock, sk->allocation)) == NULL) {
release_sock(sk);
kfree_skb(skb);
return -ENOBUFS;
}
sk->ack_backlog--;
release_sock(sk);
 
dst_release(xchg(&newsk->dst_cache, skb->dst));
skb->dst = NULL;
 
DN_SK(newsk)->state = DN_CR;
DN_SK(newsk)->addrrem = cb->src_port;
DN_SK(newsk)->services_rem = cb->services;
DN_SK(newsk)->info_rem = cb->info;
DN_SK(newsk)->segsize_rem = cb->segsize;
DN_SK(newsk)->accept_mode = DN_SK(sk)->accept_mode;
if (DN_SK(newsk)->segsize_rem < 230)
DN_SK(newsk)->segsize_rem = 230;
 
if ((DN_SK(newsk)->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
DN_SK(newsk)->max_window = decnet_no_fc_max_cwnd;
 
newsk->state = TCP_LISTEN;
newsk->zapped = 0;
 
memcpy(&(DN_SK(newsk)->addr), &(DN_SK(sk)->addr), sizeof(struct sockaddr_dn));
 
/*
* If we are listening on a wild socket, we don't want
* the newly created socket on the wrong hash queue.
*/
DN_SK(newsk)->addr.sdn_flags &= ~SDF_WILD;
 
skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->addr), &type));
skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->peer), &type));
*(dn_address *)(DN_SK(newsk)->peer.sdn_add.a_addr) = cb->src;
*(dn_address *)(DN_SK(newsk)->addr.sdn_add.a_addr) = cb->dst;
 
menuver = *skb->data;
skb_pull(skb, 1);
 
if (menuver & DN_MENUVER_ACC)
dn_access_copy(skb, &(DN_SK(newsk)->accessdata));
 
if (menuver & DN_MENUVER_USR)
dn_user_copy(skb, &(DN_SK(newsk)->conndata_in));
 
if (menuver & DN_MENUVER_PRX)
DN_SK(newsk)->peer.sdn_flags |= SDF_PROXY;
 
if (menuver & DN_MENUVER_UIC)
DN_SK(newsk)->peer.sdn_flags |= SDF_UICPROXY;
 
kfree_skb(skb);
 
memcpy(&(DN_SK(newsk)->conndata_out), &(DN_SK(sk)->conndata_out),
sizeof(struct optdata_dn));
memcpy(&(DN_SK(newsk)->discdata_out), &(DN_SK(sk)->discdata_out),
sizeof(struct optdata_dn));
 
lock_sock(newsk);
/*
* FIXME: This can fail if we've run out of local ports....
*/
dn_hash_sock(newsk);
 
dn_send_conn_ack(newsk);
 
/*
* Here we use sk->allocation since although the conn conf is
* for the newsk, the context is the old socket.
*/
if (DN_SK(newsk)->accept_mode == ACC_IMMED) {
DN_SK(newsk)->state = DN_CC;
dn_send_conn_conf(newsk, sk->allocation);
err = dn_wait_accept(newsock, flags);
}
 
release_sock(newsk);
return err;
}
 
 
static int dn_getname(struct socket *sock, struct sockaddr *uaddr,int *uaddr_len,int peer)
{
struct sockaddr_dn *sa = (struct sockaddr_dn *)uaddr;
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
 
*uaddr_len = sizeof(struct sockaddr_dn);
 
lock_sock(sk);
 
if (peer) {
if ((sock->state != SS_CONNECTED &&
sock->state != SS_CONNECTING) &&
scp->accept_mode == ACC_IMMED)
return -ENOTCONN;
 
memcpy(sa, &scp->peer, sizeof(struct sockaddr_dn));
} else {
memcpy(sa, &scp->addr, sizeof(struct sockaddr_dn));
}
 
release_sock(sk);
 
return 0;
}
 
 
static unsigned int dn_poll(struct file *file, struct socket *sock, poll_table *wait)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
int mask = datagram_poll(file, sock, wait);
 
if (skb_queue_len(&scp->other_receive_queue))
mask |= POLLRDBAND;
 
return mask;
}
 
static int dn_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
int err = -EOPNOTSUPP;
long amount = 0;
struct sk_buff *skb;
int val;
 
switch(cmd)
{
case SIOCGIFADDR:
case SIOCSIFADDR:
return dn_dev_ioctl(cmd, (void *)arg);
 
case SIOCATMARK:
lock_sock(sk);
val = (skb_queue_len(&scp->other_receive_queue) != 0);
if (scp->state != DN_RUN)
val = -ENOTCONN;
release_sock(sk);
return val;
 
#ifdef CONFIG_DECNET_ROUTER
case SIOCADDRT:
case SIOCDELRT:
return dn_fib_ioctl(sock, cmd, arg);
#endif /* CONFIG_DECNET_ROUTER */
 
case OSIOCSNETADDR:
if (!capable(CAP_NET_ADMIN)) {
err = -EPERM;
break;
}
 
dn_dev_devices_off();
 
decnet_address = (unsigned short)arg;
dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
 
dn_dev_devices_on();
err = 0;
break;
 
case OSIOCGNETADDR:
err = put_user(decnet_address, (unsigned short *)arg);
break;
case SIOCGIFCONF:
case SIOCGIFFLAGS:
case SIOCGIFBRDADDR:
return dev_ioctl(cmd,(void *)arg);
 
case TIOCOUTQ:
amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
if (amount < 0)
amount = 0;
err = put_user(amount, (int *)arg);
break;
 
case TIOCINQ:
lock_sock(sk);
if ((skb = skb_peek(&scp->other_receive_queue)) != NULL) {
amount = skb->len;
} else {
struct sk_buff *skb = sk->receive_queue.next;
for(;;) {
if (skb == (struct sk_buff *)&sk->receive_queue)
break;
amount += skb->len;
skb = skb->next;
}
}
release_sock(sk);
err = put_user(amount, (int *)arg);
break;
}
 
return err;
}
 
static int dn_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
int err = -EINVAL;
 
lock_sock(sk);
 
if (sk->zapped)
goto out;
 
if ((DN_SK(sk)->state != DN_O) || (sk->state == TCP_LISTEN))
goto out;
 
sk->max_ack_backlog = backlog;
sk->ack_backlog = 0;
sk->state = TCP_LISTEN;
err = 0;
dn_rehash_sock(sk);
 
out:
release_sock(sk);
 
return err;
}
 
 
static int dn_shutdown(struct socket *sock, int how)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
int err = -ENOTCONN;
 
lock_sock(sk);
 
if (sock->state == SS_UNCONNECTED)
goto out;
 
err = 0;
if (sock->state == SS_DISCONNECTING)
goto out;
 
err = -EINVAL;
if (scp->state == DN_O)
goto out;
 
if (how != SHUTDOWN_MASK)
goto out;
 
 
sk->shutdown = how;
dn_destroy_sock(sk);
err = 0;
 
out:
release_sock(sk);
 
return err;
}
 
static int dn_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
{
struct sock *sk = sock->sk;
int err;
 
lock_sock(sk);
err = __dn_setsockopt(sock, level, optname, optval, optlen, 0);
release_sock(sk);
 
return err;
}
 
static int __dn_setsockopt(struct socket *sock, int level,int optname, char *optval, int optlen, int flags)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
union {
struct optdata_dn opt;
struct accessdata_dn acc;
int mode;
unsigned long win;
int val;
unsigned char services;
unsigned char info;
} u;
int err;
 
if (optlen && !optval)
return -EINVAL;
 
if (optlen > sizeof(u))
return -EINVAL;
 
if (copy_from_user(&u, optval, optlen))
return -EFAULT;
 
switch(optname) {
case DSO_CONDATA:
if (sock->state == SS_CONNECTED)
return -EISCONN;
if ((scp->state != DN_O) && (scp->state != DN_CR))
return -EINVAL;
 
if (optlen != sizeof(struct optdata_dn))
return -EINVAL;
 
if (u.opt.opt_optl > 16)
return -EINVAL;
 
memcpy(&scp->conndata_out, &u.opt, optlen);
break;
 
case DSO_DISDATA:
if (sock->state != SS_CONNECTED && scp->accept_mode == ACC_IMMED)
return -ENOTCONN;
 
if (optlen != sizeof(struct optdata_dn))
return -EINVAL;
 
if (u.opt.opt_optl > 16)
return -EINVAL;
 
memcpy(&scp->discdata_out, &u.opt, optlen);
break;
 
case DSO_CONACCESS:
if (sock->state == SS_CONNECTED)
return -EISCONN;
if (scp->state != DN_O)
return -EINVAL;
 
if (optlen != sizeof(struct accessdata_dn))
return -EINVAL;
 
if ((u.acc.acc_accl > DN_MAXACCL) ||
(u.acc.acc_passl > DN_MAXACCL) ||
(u.acc.acc_userl > DN_MAXACCL))
return -EINVAL;
 
memcpy(&scp->accessdata, &u.acc, optlen);
break;
 
case DSO_ACCEPTMODE:
if (sock->state == SS_CONNECTED)
return -EISCONN;
if (scp->state != DN_O)
return -EINVAL;
 
if (optlen != sizeof(int))
return -EINVAL;
 
if ((u.mode != ACC_IMMED) && (u.mode != ACC_DEFER))
return -EINVAL;
 
scp->accept_mode = (unsigned char)u.mode;
break;
 
case DSO_CONACCEPT:
 
if (scp->state != DN_CR)
return -EINVAL;
 
scp->state = DN_CC;
dn_send_conn_conf(sk, sk->allocation);
err = dn_wait_accept(sock, sock->file->f_flags);
return err;
 
case DSO_CONREJECT:
 
if (scp->state != DN_CR)
return -EINVAL;
 
scp->state = DN_DR;
sk->shutdown = SHUTDOWN_MASK;
dn_nsp_send_disc(sk, 0x38, 0, sk->allocation);
break;
 
default:
#ifdef CONFIG_NETFILTER
return nf_setsockopt(sk, PF_DECnet, optname, optval, optlen);
#endif
case DSO_LINKINFO:
case DSO_STREAM:
case DSO_SEQPACKET:
return -ENOPROTOOPT;
 
case DSO_MAXWINDOW:
if (optlen != sizeof(unsigned long))
return -EINVAL;
if (u.win > NSP_MAX_WINDOW)
u.win = NSP_MAX_WINDOW;
if (u.win == 0)
return -EINVAL;
scp->max_window = u.win;
if (scp->snd_window > u.win)
scp->snd_window = u.win;
break;
 
case DSO_NODELAY:
if (optlen != sizeof(int))
return -EINVAL;
if (scp->nonagle == 2)
return -EINVAL;
scp->nonagle = (u.val == 0) ? 0 : 1;
/* if (scp->nonagle == 1) { Push pending frames } */
break;
 
case DSO_CORK:
if (optlen != sizeof(int))
return -EINVAL;
if (scp->nonagle == 1)
return -EINVAL;
scp->nonagle = (u.val == 0) ? 0 : 2;
/* if (scp->nonagle == 0) { Push pending frames } */
break;
 
case DSO_SERVICES:
if (optlen != sizeof(unsigned char))
return -EINVAL;
if ((u.services & ~NSP_FC_MASK) != 0x01)
return -EINVAL;
if ((u.services & NSP_FC_MASK) == NSP_FC_MASK)
return -EINVAL;
scp->services_loc = u.services;
break;
 
case DSO_INFO:
if (optlen != sizeof(unsigned char))
return -EINVAL;
if (u.info & 0xfc)
return -EINVAL;
scp->info_loc = u.info;
break;
}
 
return 0;
}
 
static int dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
{
struct sock *sk = sock->sk;
int err;
 
lock_sock(sk);
err = __dn_getsockopt(sock, level, optname, optval, optlen, 0);
release_sock(sk);
 
return err;
}
 
static int __dn_getsockopt(struct socket *sock, int level,int optname, char *optval,int *optlen, int flags)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
struct linkinfo_dn link;
unsigned int r_len;
void *r_data = NULL;
unsigned int val;
 
if(get_user(r_len , optlen))
return -EFAULT;
switch(optname) {
case DSO_CONDATA:
if (r_len > sizeof(struct optdata_dn))
r_len = sizeof(struct optdata_dn);
r_data = &scp->conndata_in;
break;
 
case DSO_DISDATA:
if (r_len > sizeof(struct optdata_dn))
r_len = sizeof(struct optdata_dn);
r_data = &scp->discdata_in;
break;
 
case DSO_CONACCESS:
if (r_len > sizeof(struct accessdata_dn))
r_len = sizeof(struct accessdata_dn);
r_data = &scp->accessdata;
break;
 
case DSO_ACCEPTMODE:
if (r_len > sizeof(unsigned char))
r_len = sizeof(unsigned char);
r_data = &scp->accept_mode;
break;
 
case DSO_LINKINFO:
if (r_len > sizeof(struct linkinfo_dn))
r_len = sizeof(struct linkinfo_dn);
 
switch(sock->state) {
case SS_CONNECTING:
link.idn_linkstate = LL_CONNECTING;
break;
case SS_DISCONNECTING:
link.idn_linkstate = LL_DISCONNECTING;
break;
case SS_CONNECTED:
link.idn_linkstate = LL_RUNNING;
break;
default:
link.idn_linkstate = LL_INACTIVE;
}
 
link.idn_segsize = scp->segsize_rem;
r_data = &link;
break;
 
default:
#ifdef CONFIG_NETFILTER
{
int val, len;
if(get_user(len, optlen))
return -EFAULT;
val = nf_getsockopt(sk, PF_DECnet, optname,
optval, &len);
if (val >= 0)
val = put_user(len, optlen);
return val;
}
#endif
case DSO_STREAM:
case DSO_SEQPACKET:
case DSO_CONACCEPT:
case DSO_CONREJECT:
return -ENOPROTOOPT;
 
case DSO_MAXWINDOW:
if (r_len > sizeof(unsigned long))
r_len = sizeof(unsigned long);
r_data = &scp->max_window;
break;
 
case DSO_NODELAY:
if (r_len > sizeof(int))
r_len = sizeof(int);
val = (scp->nonagle == 1);
r_data = &val;
break;
 
case DSO_CORK:
if (r_len > sizeof(int))
r_len = sizeof(int);
val = (scp->nonagle == 2);
r_data = &val;
break;
 
case DSO_SERVICES:
if (r_len > sizeof(unsigned char))
r_len = sizeof(unsigned char);
r_data = &scp->services_rem;
break;
 
case DSO_INFO:
if (r_len > sizeof(unsigned char))
r_len = sizeof(unsigned char);
r_data = &scp->info_rem;
break;
}
 
if (r_data) {
if (copy_to_user(optval, r_data, r_len))
return -EFAULT;
if (put_user(r_len, optlen))
return -EFAULT;
}
 
return 0;
}
 
 
/*
* Used by send/recvmsg to wait until the socket is connected
* before passing data.
*/
static int dn_wait_run(struct sock *sk, int flags)
{
struct dn_scp *scp = DN_SK(sk);
int err = 0;
 
switch(scp->state) {
case DN_RUN:
return 0;
 
case DN_CR:
scp->state = DN_CC;
dn_send_conn_conf(sk, sk->allocation);
return dn_wait_accept(sk->socket, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0);
case DN_CI:
case DN_CC:
break;
default:
return -ENOTCONN;
}
 
if (flags & MSG_DONTWAIT)
return -EWOULDBLOCK;
 
do {
if ((err = sock_error(sk)) != 0)
break;
 
if (signal_pending(current)) {
err = -ERESTARTSYS;
break;
}
 
SOCK_SLEEP_PRE(sk)
 
if (scp->state != DN_RUN)
schedule();
 
SOCK_SLEEP_POST(sk)
 
} while(scp->state != DN_RUN);
 
return 0;
}
 
 
static int dn_data_ready(struct sock *sk, struct sk_buff_head *q, int flags, int target)
{
struct sk_buff *skb = q->next;
int len = 0;
 
if (flags & MSG_OOB)
return skb_queue_len(q) ? 1 : 0;
 
while(skb != (struct sk_buff *)q) {
struct dn_skb_cb *cb = DN_SKB_CB(skb);
len += skb->len;
 
if (cb->nsp_flags & 0x40) {
/* SOCK_SEQPACKET reads to EOM */
if (sk->type == SOCK_SEQPACKET)
return 1;
/* so does SOCK_STREAM unless WAITALL is specified */
if (!(flags & MSG_WAITALL))
return 1;
}
 
/* minimum data length for read exceeded */
if (len >= target)
return 1;
 
skb = skb->next;
}
 
return 0;
}
 
 
static int dn_recvmsg(struct socket *sock, struct msghdr *msg, int size,
int flags, struct scm_cookie *scm)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
struct sk_buff_head *queue = &sk->receive_queue;
int target = size > 1 ? 1 : 0;
int copied = 0;
int rv = 0;
struct sk_buff *skb, *nskb;
struct dn_skb_cb *cb = NULL;
unsigned char eor = 0;
 
lock_sock(sk);
 
if (sk->zapped) {
rv = -EADDRNOTAVAIL;
goto out;
}
 
if ((rv = dn_wait_run(sk, flags)) != 0)
goto out;
 
if (sk->shutdown & RCV_SHUTDOWN) {
send_sig(SIGPIPE, current, 0);
rv = -EPIPE;
goto out;
}
 
if (flags & ~(MSG_PEEK|MSG_OOB|MSG_WAITALL|MSG_DONTWAIT)) {
rv = -EOPNOTSUPP;
goto out;
}
 
if (flags & MSG_OOB)
queue = &scp->other_receive_queue;
 
if (flags & MSG_WAITALL)
target = size;
 
 
/*
* See if there is data ready to read, sleep if there isn't
*/
for(;;) {
if (sk->err)
goto out;
 
if (skb_queue_len(&scp->other_receive_queue)) {
if (!(flags & MSG_OOB)) {
msg->msg_flags |= MSG_OOB;
if (!scp->other_report) {
scp->other_report = 1;
goto out;
}
}
}
if (scp->state != DN_RUN)
goto out;
 
if (signal_pending(current)) {
rv = -ERESTARTSYS;
goto out;
}
 
if (dn_data_ready(sk, queue, flags, target))
break;
 
if (flags & MSG_DONTWAIT) {
rv = -EWOULDBLOCK;
goto out;
}
 
set_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
SOCK_SLEEP_PRE(sk)
 
if (!dn_data_ready(sk, queue, flags, target))
schedule();
 
SOCK_SLEEP_POST(sk)
clear_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
}
 
for(skb = queue->next; skb != (struct sk_buff *)queue; skb = nskb) {
int chunk = skb->len;
cb = DN_SKB_CB(skb);
 
if ((chunk + copied) > size)
chunk = size - copied;
 
if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
rv = -EFAULT;
break;
}
copied += chunk;
 
if (!(flags & MSG_PEEK))
skb_pull(skb, chunk);
 
eor = cb->nsp_flags & 0x40;
nskb = skb->next;
 
if (skb->len == 0) {
skb_unlink(skb);
kfree_skb(skb);
/*
* N.B. Don't refer to skb or cb after this point
* in loop.
*/
if ((scp->flowloc_sw == DN_DONTSEND) && !dn_congested(sk)) {
scp->flowloc_sw = DN_SEND;
dn_nsp_send_link(sk, DN_SEND, 0);
}
}
 
if (eor) {
if (sk->type == SOCK_SEQPACKET)
break;
if (!(flags & MSG_WAITALL))
break;
}
 
if (flags & MSG_OOB)
break;
 
if (copied >= target)
break;
}
 
rv = copied;
 
 
if (eor && (sk->type == SOCK_SEQPACKET))
msg->msg_flags |= MSG_EOR;
 
out:
if (rv == 0)
rv = (flags & MSG_PEEK) ? -sk->err : sock_error(sk);
 
if ((rv >= 0) && msg->msg_name) {
memcpy(msg->msg_name, &scp->peer, sizeof(struct sockaddr_dn));
msg->msg_namelen = sizeof(struct sockaddr_dn);
}
 
release_sock(sk);
 
return rv;
}
 
 
static inline int dn_queue_too_long(struct dn_scp *scp, struct sk_buff_head *queue, int flags)
{
unsigned char fctype = scp->services_rem & NSP_FC_MASK;
if (skb_queue_len(queue) >= scp->snd_window)
return 1;
if (fctype != NSP_FC_NONE) {
if (flags & MSG_OOB) {
if (scp->flowrem_oth == 0)
return 1;
} else {
if (scp->flowrem_dat == 0)
return 1;
}
}
return 0;
}
 
static int dn_sendmsg(struct socket *sock, struct msghdr *msg, int size,
struct scm_cookie *scm)
{
struct sock *sk = sock->sk;
struct dn_scp *scp = DN_SK(sk);
int mss;
struct sk_buff_head *queue = &scp->data_xmit_queue;
int flags = msg->msg_flags;
int err = 0;
int sent = 0;
int addr_len = msg->msg_namelen;
struct sockaddr_dn *addr = (struct sockaddr_dn *)msg->msg_name;
struct sk_buff *skb = NULL;
struct dn_skb_cb *cb;
unsigned char msgflg;
unsigned char *ptr;
unsigned short ack;
int len;
unsigned char fctype;
 
if (flags & ~(MSG_TRYHARD|MSG_OOB|MSG_DONTWAIT|MSG_EOR))
return -EOPNOTSUPP;
 
if (addr_len && (addr_len != sizeof(struct sockaddr_dn)))
return -EINVAL;
 
if (sk->zapped && dn_auto_bind(sock)) {
err = -EADDRNOTAVAIL;
goto out;
}
 
if (scp->state == DN_O) {
if (!addr_len || !addr) {
err = -ENOTCONN;
goto out;
}
 
if ((err = dn_connect(sock, (struct sockaddr *)addr, addr_len, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0)) < 0)
goto out;
}
 
lock_sock(sk);
 
if ((err = dn_wait_run(sk, flags)) < 0)
goto out;
 
if (sk->shutdown & SEND_SHUTDOWN) {
send_sig(SIGPIPE, current, 0);
err = -EPIPE;
goto out;
}
 
if ((flags & MSG_TRYHARD) && sk->dst_cache)
dst_negative_advice(&sk->dst_cache);
 
mss = scp->segsize_rem;
fctype = scp->services_rem & NSP_FC_MASK;
 
if (sk->dst_cache && sk->dst_cache->neighbour) {
struct dn_neigh *dn = (struct dn_neigh *)sk->dst_cache->neighbour;
if (dn->blksize < (mss + 11))
mss = dn->blksize - 11;
}
 
/*
* The only difference between SEQPACKET & STREAM sockets under DECnet
* is that SEQPACKET sockets set the MSG_EOR flag for the last
* session control message segment.
*/
 
if (flags & MSG_OOB) {
mss = 16;
queue = &scp->other_xmit_queue;
if (size > mss) {
err = -EMSGSIZE;
goto out;
}
}
 
scp->persist_fxn = dn_nsp_xmit_timeout;
 
while(sent < size) {
err = sock_error(sk);
if (err)
goto out;
 
if (signal_pending(current)) {
err = -ERESTARTSYS;
goto out;
}
 
/*
* Calculate size that we wish to send.
*/
len = size - sent;
 
if (len > mss)
len = mss;
 
/*
* Wait for queue size to go down below the window
* size.
*/
if (dn_queue_too_long(scp, queue, flags)) {
if (flags & MSG_DONTWAIT) {
err = -EWOULDBLOCK;
goto out;
}
 
SOCK_SLEEP_PRE(sk)
 
if (dn_queue_too_long(scp, queue, flags))
schedule();
 
SOCK_SLEEP_POST(sk)
 
continue;
}
 
/*
* Get a suitably sized skb.
*/
skb = dn_alloc_send_skb(sk, &len, flags & MSG_DONTWAIT, &err);
 
if (err)
break;
 
if (!skb)
continue;
 
cb = DN_SKB_CB(skb);
 
ptr = skb_put(skb, 9);
 
if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
err = -EFAULT;
goto out;
}
 
if (flags & MSG_OOB) {
cb->segnum = scp->numoth;
seq_add(&scp->numoth, 1);
msgflg = 0x30;
ack = (scp->numoth_rcv & 0x0FFF) | 0x8000;
scp->ackxmt_oth = scp->numoth_rcv;
if (fctype != NSP_FC_NONE)
scp->flowrem_oth--;
} else {
cb->segnum = scp->numdat;
seq_add(&scp->numdat, 1);
msgflg = 0x00;
if (sock->type == SOCK_STREAM)
msgflg = 0x60;
if (scp->seg_total == 0)
msgflg |= 0x20;
 
scp->seg_total += len;
if (((sent + len) == size) && (flags & MSG_EOR)) {
msgflg |= 0x40;
scp->seg_total = 0;
if (fctype == NSP_FC_SCMC)
scp->flowrem_dat--;
}
ack = (scp->numdat_rcv & 0x0FFF) | 0x8000;
scp->ackxmt_dat = scp->numdat_rcv;
if (fctype == NSP_FC_SRC)
scp->flowrem_dat--;
}
 
*ptr++ = msgflg;
*(__u16 *)ptr = scp->addrrem;
ptr += 2;
*(__u16 *)ptr = scp->addrloc;
ptr += 2;
*(__u16 *)ptr = dn_htons(ack);
ptr += 2;
*(__u16 *)ptr = dn_htons(cb->segnum);
 
sent += len;
dn_nsp_queue_xmit(sk, skb, sk->allocation, flags & MSG_OOB);
skb = NULL;
 
scp->persist = dn_nsp_persist(sk);
 
}
out:
 
if (skb)
kfree_skb(skb);
 
release_sock(sk);
 
return sent ? sent : err;
}
 
static int dn_device_event(struct notifier_block *this, unsigned long event,
void *ptr)
{
struct net_device *dev = (struct net_device *)ptr;
 
switch(event) {
case NETDEV_UP:
dn_dev_up(dev);
break;
case NETDEV_DOWN:
dn_dev_down(dev);
break;
default:
break;
}
 
return NOTIFY_DONE;
}
 
static struct notifier_block dn_dev_notifier = {
notifier_call: dn_device_event,
};
 
extern int dn_route_rcv(struct sk_buff *, struct net_device *, struct packet_type *);
 
static struct packet_type dn_dix_packet_type = {
type: __constant_htons(ETH_P_DNA_RT),
dev: NULL, /* All devices */
func: dn_route_rcv,
data: (void*)1,
};
 
#define IS_NOT_PRINTABLE(x) ((x) < 32 || (x) > 126)
 
static void dn_printable_object(struct sockaddr_dn *dn, unsigned char *buf)
{
int i;
switch (dn_ntohs(dn->sdn_objnamel)) {
case 0:
sprintf(buf, "%d", dn->sdn_objnum);
break;
default:
for (i = 0; i < dn_ntohs(dn->sdn_objnamel); i++) {
buf[i] = dn->sdn_objname[i];
if (IS_NOT_PRINTABLE(buf[i]))
buf[i] = '.';
}
buf[i] = 0;
}
}
 
static int dn_get_info(char *buffer, char **start, off_t offset, int length)
{
struct sock *sk;
struct dn_scp *scp;
int len = 0;
off_t pos = 0;
off_t begin = 0;
char buf1[DN_ASCBUF_LEN];
char buf2[DN_ASCBUF_LEN];
char local_object[DN_MAXOBJL+3];
char remote_object[DN_MAXOBJL+3];
int i;
 
len += sprintf(buffer + len, "Local Remote\n");
 
read_lock(&dn_hash_lock);
for(i = 0; i < DN_SK_HASH_SIZE; i++) {
for(sk = dn_sk_hash[i]; sk != NULL; sk = sk->next) {
scp = DN_SK(sk);
 
dn_printable_object(&scp->addr, local_object);
dn_printable_object(&scp->peer, remote_object);
 
len += sprintf(buffer + len,
"%6s/%04X %04d:%04d %04d:%04d %01d %-16s %6s/%04X %04d:%04d %04d:%04d %01d %-16s %4s %s\n",
dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->addr)), buf1),
scp->addrloc,
scp->numdat,
scp->numoth,
scp->ackxmt_dat,
scp->ackxmt_oth,
scp->flowloc_sw,
local_object,
dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->peer)), buf2),
scp->addrrem,
scp->numdat_rcv,
scp->numoth_rcv,
scp->ackrcv_dat,
scp->ackrcv_oth,
scp->flowrem_sw,
remote_object,
dn_state2asc(scp->state),
((scp->accept_mode == ACC_IMMED) ? "IMMED" : "DEFER"));
 
pos = begin + len;
if (pos < offset) {
len = 0;
begin = pos;
}
if (pos > (offset + length))
break;
}
}
read_unlock(&dn_hash_lock);
 
*start = buffer + (offset - begin);
len -= (offset - begin);
 
if (len > length)
len = length;
 
return len;
}
 
 
static struct net_proto_family dn_family_ops = {
family: AF_DECnet,
create: dn_create,
};
 
static struct proto_ops dn_proto_ops = {
family: AF_DECnet,
 
release: dn_release,
bind: dn_bind,
connect: dn_connect,
socketpair: sock_no_socketpair,
accept: dn_accept,
getname: dn_getname,
poll: dn_poll,
ioctl: dn_ioctl,
listen: dn_listen,
shutdown: dn_shutdown,
setsockopt: dn_setsockopt,
getsockopt: dn_getsockopt,
sendmsg: dn_sendmsg,
recvmsg: dn_recvmsg,
mmap: sock_no_mmap,
sendpage: sock_no_sendpage,
};
 
#ifdef CONFIG_SYSCTL
void dn_register_sysctl(void);
void dn_unregister_sysctl(void);
#endif
 
 
#ifdef MODULE
EXPORT_NO_SYMBOLS;
MODULE_DESCRIPTION("The Linux DECnet Network Protocol");
MODULE_AUTHOR("Linux DECnet Project Team");
MODULE_LICENSE("GPL");
 
static int addr[2] = {0, 0};
 
MODULE_PARM(addr, "2i");
MODULE_PARM_DESC(addr, "The DECnet address of this machine: area,node");
#endif
 
static char banner[] __initdata = KERN_INFO "NET4: DECnet for Linux: V.2.4.20-pre1s (C) 1995-2002 Linux DECnet Project Team\n";
 
static int __init decnet_init(void)
{
#ifdef MODULE
if (addr[0] > 63 || addr[0] < 0) {
printk(KERN_ERR "DECnet: Area must be between 0 and 63");
return 1;
}
 
if (addr[1] > 1023 || addr[1] < 0) {
printk(KERN_ERR "DECnet: Node must be between 0 and 1023");
return 1;
}
 
decnet_address = dn_htons((addr[0] << 10) | addr[1]);
dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
#endif
 
printk(banner);
 
sock_register(&dn_family_ops);
dev_add_pack(&dn_dix_packet_type);
register_netdevice_notifier(&dn_dev_notifier);
 
proc_net_create("decnet", 0, dn_get_info);
 
dn_neigh_init();
dn_dev_init();
dn_route_init();
 
#ifdef CONFIG_DECNET_ROUTER
dn_fib_init();
#endif /* CONFIG_DECNET_ROUTER */
 
#ifdef CONFIG_SYSCTL
dn_register_sysctl();
#endif /* CONFIG_SYSCTL */
 
/*
* Prevent DECnet module unloading until its fixed properly.
* Requires an audit of the code to check for memory leaks and
* initialisation problems etc.
*/
MOD_INC_USE_COUNT;
 
return 0;
 
}
 
#ifndef MODULE
static int __init decnet_setup(char *str)
{
unsigned short area = simple_strtoul(str, &str, 0);
unsigned short node = simple_strtoul(*str > 0 ? ++str : str, &str, 0);
 
decnet_address = dn_htons(area << 10 | node);
dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
 
return 1;
}
 
__setup("decnet=", decnet_setup);
#endif
 
static void __exit decnet_exit(void)
{
sock_unregister(AF_DECnet);
dev_remove_pack(&dn_dix_packet_type);
 
#ifdef CONFIG_SYSCTL
dn_unregister_sysctl();
#endif /* CONFIG_SYSCTL */
 
unregister_netdevice_notifier(&dn_dev_notifier);
 
dn_route_cleanup();
dn_dev_cleanup();
dn_neigh_cleanup();
 
#ifdef CONFIG_DECNET_ROUTER
dn_fib_cleanup();
#endif /* CONFIG_DECNET_ROUTER */
 
proc_net_remove("decnet");
}
 
module_init(decnet_init);
module_exit(decnet_exit);
/dn_nsp_out.c
0,0 → 1,730
 
/*
* 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
*/
 
/******************************************************************************
(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/sched.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 <net/sock.h>
#include <asm/segment.h>
#include <asm/system.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/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 };
 
/*
* 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, int pri)
{
struct sk_buff *skb;
int hdr = 64;
 
if ((skb = alloc_skb(size + hdr, pri)) == NULL)
return NULL;
 
skb->protocol = __constant_htons(ETH_P_DNA_RT);
skb->pkt_type = PACKET_OUTGOING;
 
if (sk)
skb_set_owner_w(skb, sk);
 
skb_reserve(skb, hdr);
 
return skb;
}
 
/*
* Wrapper for the above, for allocs of data skbs. We try and get the
* whole size thats been asked for (plus 11 bytes of header). If this
* fails, then we try for any size over 16 bytes for SOCK_STREAMS.
*/
struct sk_buff *dn_alloc_send_skb(struct sock *sk, int *size, int noblock, int *err)
{
int space;
int len;
struct sk_buff *skb = NULL;
 
*err = 0;
 
while(skb == NULL) {
if (signal_pending(current)) {
*err = ERESTARTSYS;
break;
}
 
if (sk->shutdown & SEND_SHUTDOWN) {
*err = EINVAL;
break;
}
 
if (sk->err)
break;
 
len = *size + 11;
space = sk->sndbuf - atomic_read(&sk->wmem_alloc);
 
if (space < len) {
if ((sk->socket->type == SOCK_STREAM) && (space >= (16 + 11)))
len = space;
}
 
if (space < len) {
set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
if (noblock) {
*err = EWOULDBLOCK;
break;
}
 
clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
SOCK_SLEEP_PRE(sk)
 
if ((sk->sndbuf - atomic_read(&sk->wmem_alloc)) < len)
schedule();
 
SOCK_SLEEP_POST(sk)
continue;
}
 
if ((skb = dn_alloc_skb(sk, len, sk->allocation)) == NULL)
continue;
 
*size = len - 11;
}
 
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 dn_nsp_clone_and_send(struct sk_buff *skb, int 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
* @gfp: The memory allocation flags
*
* 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 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_len(&scp->data_xmit_queue) || skb_queue_len(&scp->other_xmit_queue))
scp->persist = dn_nsp_persist(sk);
 
return 0;
}
 
static inline unsigned char *dn_mk_common_header(struct dn_scp *scp, struct sk_buff *skb, unsigned char msgflag, int len)
{
unsigned char *ptr = skb_push(skb, len);
 
if (len < 5)
BUG();
 
*ptr++ = msgflag;
*((unsigned short *)ptr) = scp->addrrem;
ptr += 2;
*((unsigned short *)ptr) = scp->addrloc;
ptr += 2;
return ptr;
}
 
static unsigned short *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;
unsigned short *ptr;
 
if (hlen < 9)
BUG();
 
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 = (unsigned short *)dn_mk_common_header(scp, skb, msgflag, hlen);
 
*ptr++ = dn_htons(acknum);
*ptr++ = dn_htons(ackcrs);
 
return ptr;
}
 
void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb, int 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;
 
/*
* 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;
 
/* printk(KERN_DEBUG "Window: %lu\n", scp->snd_window); */
 
cb->xmit_count = 0;
 
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, *list, *ack = NULL;
int wakeup = 0;
int try_retrans = 0;
unsigned long reftime = cb->stamp;
unsigned long pkttime;
unsigned short xmit_count;
unsigned short segnum;
 
skb2 = q->next;
list = (struct sk_buff *)q;
while(list != skb2) {
struct dn_skb_cb *cb2 = DN_SKB_CB(skb2);
 
if (before_or_equal(cb2->segnum, acknum))
ack = skb2;
 
/* printk(KERN_DEBUG "ack: %s %04x %04x\n", ack ? "ACK" : "SKIP", (int)cb2->segnum, (int)acknum); */
 
skb2 = skb2->next;
 
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);
kfree_skb(ack);
ack = NULL;
 
/*
* We don't expect to see acknowledgements for packets we
* haven't sent yet.
*/
if (xmit_count == 0)
BUG();
/*
* 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 (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->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, int gfp)
{
struct dn_scp *scp = DN_SK(sk);
struct sk_buff *skb = NULL;
struct nsp_conn_init_msg *msg;
unsigned char len = scp->conndata_out.opt_optl;
 
if ((skb = dn_alloc_skb(sk, 50 + scp->conndata_out.opt_optl, 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 = dn_htons(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, int gfp, struct dst_entry *dst,
int ddl, unsigned char *dd, __u16 rem, __u16 loc)
{
struct sk_buff *skb = NULL;
int size = 7 + ddl + ((msgflg == NSP_DISCINIT) ? 1 : 0);
unsigned char *msg;
 
if ((dst == NULL) || (rem == 0)) {
if (net_ratelimit())
printk(KERN_DEBUG "DECnet: dn_nsp_do_disc: BUG! Please report this to SteveW@ACM.org rem=%u dst=%p\n", (unsigned)rem, dst);
return;
}
 
if ((skb = dn_alloc_skb(sk, size, gfp)) == NULL)
return;
 
msg = skb_put(skb, size);
*msg++ = msgflg;
*(__u16 *)msg = rem;
msg += 2;
*(__u16 *)msg = loc;
msg += 2;
*(__u16 *)msg = dn_htons(reason);
msg += 2;
if (msgflg == NSP_DISCINIT)
*msg++ = ddl;
 
if (ddl) {
memcpy(msg, dd, ddl);
}
 
/*
* This doesn't go via the dn_nsp_send() fucntion since we need
* to be able to send disc packets out which have no socket
* associations.
*/
skb->dst = dst_clone(dst);
skb->dst->output(skb);
}
 
 
void dn_nsp_send_disc(struct sock *sk, unsigned char msgflg,
unsigned short reason, int gfp)
{
struct dn_scp *scp = DN_SK(sk);
int ddl = 0;
 
if (msgflg == NSP_DISCINIT)
ddl = scp->discdata_out.opt_optl;
 
if (reason == 0)
reason = scp->discdata_out.opt_status;
 
dn_nsp_do_disc(sk, msgflg, reason, gfp, sk->dst_cache, 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;
int gfp = GFP_ATOMIC;
 
dn_nsp_do_disc(NULL, msgflg, reason, gfp, skb->dst, 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 short *segnum;
unsigned char *ptr;
int gfp = GFP_ATOMIC;
 
if ((skb = dn_alloc_skb(sk, 13, gfp)) == NULL)
return;
 
skb_reserve(skb, 13);
segnum = dn_mk_ack_header(sk, skb, 0x10, 13, 1);
*segnum = dn_htons(scp->numoth);
DN_SKB_CB(skb)->segnum = scp->numoth;
seq_add(&scp->numoth, 1);
ptr = (unsigned char *)(segnum + 1);
*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 sk_buff *skb = NULL;
struct nsp_conn_init_msg *msg;
unsigned char aux;
unsigned char menuver;
struct dn_skb_cb *cb;
unsigned char type = 1;
 
if ((skb = dn_alloc_skb(sk, 200, (msgflg == NSP_CI) ? sk->allocation : GFP_ATOMIC)) == NULL)
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 = dn_htons(scp->segsize_loc); /* Max segment size */
 
if (scp->peer.sdn_objnum)
type = 0;
 
skb_put(skb, dn_sockaddr2username(&scp->peer, skb->tail, type));
skb_put(skb, dn_sockaddr2username(&scp->addr, skb->tail, 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 = 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);
}
 
/dn_table.c
0,0 → 1,901
/*
* 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/config.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.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 <asm/atomic.h>
#include <asm/uaccess.h>
#include <linux/route.h> /* RTF_xxx */
#include <net/neighbour.h>
#include <net/dst.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;
u32 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 dz_prefix(key,dz) ((key).datum)
 
#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)
 
 
static rwlock_t dn_fib_tables_lock = RW_LOCK_UNLOCKED;
static struct dn_fib_table *dn_fib_tables[DN_NUM_TABLES + 1];
 
static kmem_cache_t *dn_hash_kmem;
static int dn_fib_hash_zombies;
 
static __inline__ dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz)
{
u32 h = ntohs(key.datum)>>(16 - dz->dz_order);
h ^= (h >> 10);
h ^= (h >> 6);
h ^= (h >> 3);
h &= DZ_HASHMASK(dz);
return *(dn_fib_idx_t *)&h;
}
 
static __inline__ dn_fib_key_t dz_key(u16 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)
{
int i;
struct dn_fib_node *f, **fp, *next;
 
for(i = 0; i < old_divisor; i++) {
for(f = old_ht[i]; f; f = f->fn_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 = kmalloc(new_divisor*sizeof(struct dn_fib_node*), GFP_KERNEL);
 
if (ht == NULL)
return;
 
memset(ht, 0, new_divisor*sizeof(struct dn_fib_node *));
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 = kmalloc(sizeof(struct dn_zone), GFP_KERNEL);
if (!dz)
return NULL;
 
memset(dz, 0, sizeof(struct dn_zone));
if (z) {
dz->dz_divisor = 16;
dz->dz_hashmask = 0x0F;
} else {
dz->dz_divisor = 1;
dz->dz_hashmask = 0;
}
 
dz->dz_hash = kmalloc(dz->dz_divisor*sizeof(struct dn_fib_node *), GFP_KERNEL);
 
if (!dz->dz_hash) {
kfree(dz);
return NULL;
}
 
memset(dz->dz_hash, 0, dz->dz_divisor*sizeof(struct dn_fib_node*));
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 dn_kern_rta *rta, struct dn_fib_info *fi)
{
struct rtnexthop *nhp;
int nhlen;
 
if (rta->rta_priority && *rta->rta_priority != fi->fib_priority)
return 1;
 
if (rta->rta_oif || rta->rta_gw) {
if ((!rta->rta_oif || *rta->rta_oif == fi->fib_nh->nh_oif) &&
(!rta->rta_gw || memcmp(rta->rta_gw, &fi->fib_nh->nh_gw, 2) == 0))
return 0;
return 1;
}
 
if (rta->rta_mp == NULL)
return 0;
 
nhp = RTA_DATA(rta->rta_mp);
nhlen = RTA_PAYLOAD(rta->rta_mp);
 
for_nexthops(fi) {
int attrlen = nhlen - sizeof(struct rtnexthop);
dn_address 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) {
gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
 
if (gw && gw != nh->nh_gw)
return 1;
}
nhp = RTNH_NEXT(nhp);
} endfor_nexthops(fi);
 
return 0;
}
 
static int dn_fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
u8 tb_id, u8 type, u8 scope, void *dst, int dst_len,
struct dn_fib_info *fi)
{
struct rtmsg *rtm;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
 
nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*rtm));
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;
if (rtm->rtm_dst_len)
RTA_PUT(skb, RTA_DST, 2, dst);
rtm->rtm_protocol = fi->fib_protocol;
if (fi->fib_priority)
RTA_PUT(skb, RTA_PRIORITY, 4, &fi->fib_priority);
if (fi->fib_nhs == 1) {
if (fi->fib_nh->nh_gw)
RTA_PUT(skb, RTA_GATEWAY, 2, &fi->fib_nh->nh_gw);
if (fi->fib_nh->nh_oif)
RTA_PUT(skb, RTA_OIF, sizeof(int), &fi->fib_nh->nh_oif);
}
if (fi->fib_nhs > 1) {
struct rtnexthop *nhp;
struct rtattr *mp_head;
if (skb_tailroom(skb) <= RTA_SPACE(0))
goto rtattr_failure;
mp_head = (struct rtattr *)skb_put(skb, RTA_SPACE(0));
 
for_nexthops(fi) {
if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
goto rtattr_failure;
nhp = (struct rtnexthop *)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
nhp->rtnh_flags = nh->nh_flags & 0xFF;
nhp->rtnh_hops = nh->nh_weight - 1;
nhp->rtnh_ifindex = nh->nh_oif;
if (nh->nh_gw)
RTA_PUT(skb, RTA_GATEWAY, 2, &nh->nh_gw);
nhp->rtnh_len = skb->tail - (unsigned char *)nhp;
} endfor_nexthops(fi);
mp_head->rta_type = RTA_MULTIPATH;
mp_head->rta_len = skb->tail - (u8*)mp_head;
}
 
nlh->nlmsg_len = skb->tail - b;
return skb->len;
 
 
nlmsg_failure:
rtattr_failure:
skb_trim(skb, b - skb->data);
return -1;
}
 
 
static void dn_rtmsg_fib(int event, struct dn_fib_node *f, int z, int tb_id,
struct nlmsghdr *nlh, struct netlink_skb_parms *req)
{
struct sk_buff *skb;
u32 pid = req ? req->pid : 0;
int size = NLMSG_SPACE(sizeof(struct rtmsg) + 256);
 
skb = alloc_skb(size, GFP_KERNEL);
if (!skb)
return;
 
if (dn_fib_dump_info(skb, pid, nlh->nlmsg_seq, event, tb_id,
f->fn_type, f->fn_scope, &f->fn_key, z,
DN_FIB_INFO(f)) < 0) {
kfree_skb(skb);
return;
}
NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_ROUTE;
if (nlh->nlmsg_flags & NLM_F_ECHO)
atomic_inc(&skb->users);
netlink_broadcast(rtnl, skb, pid, RTMGRP_DECnet_ROUTE, GFP_KERNEL);
if (nlh->nlmsg_flags & NLM_F_ECHO)
netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
}
 
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[3];
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).pid,
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) < 0) {
cb->args[3] = i;
return -1;
}
}
cb->args[3] = 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[2];
for(h = 0; h < dz->dz_divisor; h++) {
if (h < s_h)
continue;
if (h > s_h)
memset(&cb->args[3], 0, sizeof(cb->args) - 3*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[2] = h;
return -1;
}
}
cb->args[2] = 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[1];
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[2], 0, sizeof(cb->args) - 2*sizeof(cb->args[0]));
 
if (dn_hash_dump_zone(skb, cb, tb, dz) < 0) {
cb->args[1] = m;
read_unlock(&dn_fib_tables_lock);
return -1;
}
}
read_unlock(&dn_fib_tables_lock);
cb->args[1] = m;
 
return skb->len;
}
 
static int dn_fib_table_insert(struct dn_fib_table *tb, struct rtmsg *r, struct dn_kern_rta *rta, 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 (rta->rta_dst) {
dn_address dst;
memcpy(&dst, rta->rta_dst, 2);
if (dst & ~DZ_MASK(dz))
return -EINVAL;
key = dz_key(dst, dz);
}
 
if ((fi = dn_fib_create_info(r, rta, 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_alloc(dn_hash_kmem, SLAB_KERNEL);
if (new_f == NULL)
goto out;
 
memset(new_f, 0, sizeof(struct dn_fib_node));
 
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 dn_kern_rta *rta, 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 (rta->rta_dst) {
dn_address dst;
memcpy(&dst, rta->rta_dst, 2);
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, rta, 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 dn_fib_key *
key, 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(key->dst, dz);
 
for(f = dz_chain(k, dz); f; f = f->fn_next) {
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 < key->scope)
continue;
 
err = dn_fib_semantic_match(f->fn_type, DN_FIB_INFO(f), key, 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;
}
 
#ifdef CONFIG_PROC_FS
 
static unsigned dn_fib_flag_trans(int type, int dead, u16 mask, struct dn_fib_info *fi)
{
static unsigned type2flags[RTN_MAX+1] = {
0, 0, 0, 0, 0, 0, 0, RTF_REJECT, RTF_REJECT, 0, 0, 0
};
unsigned flags = type2flags[type];
 
if (fi && fi->fib_nh->nh_gw)
flags |= RTF_GATEWAY;
if (mask == 0xFFFF)
flags |= RTF_HOST;
if (dead)
flags |= RTF_UP;
return flags;
}
 
static void dn_fib_node_get_info(int type, int dead, struct dn_fib_info *fi, u16 prefix, u16 mask, char *buffer)
{
int len;
unsigned flags = dn_fib_flag_trans(type, dead, mask, fi);
 
if (fi) {
len = sprintf(buffer, "%s\t%04x\t%04x\t%04x\t%d\t%u\t%d\t%04x\t%d\t%u\t%u",
fi->fib_dev ? fi->fib_dev->name : "*", prefix,
fi->fib_nh->nh_gw, flags, 0, 0, fi->fib_priority,
mask, 0, 0, 0);
} else {
len = sprintf(buffer, "*\t%04x\t%04x\t%04x\t%d\t%u\t%d\t%04x\t%d\t%u\t%u",
prefix, 0,
flags, 0, 0, 0,
mask, 0, 0, 0);
}
memset(buffer+len, ' ', 127-len);
buffer[127] = '\n';
}
 
static int dn_fib_table_get_info(struct dn_fib_table *tb, char *buffer, int first, int count)
{
struct dn_hash *table = (struct dn_hash *)tb->data;
struct dn_zone *dz;
int pos = 0;
int n = 0;
 
read_lock(&dn_fib_tables_lock);
for(dz = table->dh_zone_list; dz; dz = dz->dz_next) {
int i;
struct dn_fib_node *f;
int maxslot = dz->dz_divisor;
struct dn_fib_node **fp = dz->dz_hash;
 
if (dz->dz_nent == 0)
continue;
 
if (pos + dz->dz_nent < first) {
pos += dz->dz_nent;
continue;
}
 
for(i = 0; i < maxslot; i++, fp++) {
for(f = *fp; f ; f = f->fn_next) {
if (++pos <= first)
continue;
dn_fib_node_get_info(f->fn_type,
f->fn_state & DN_S_ZOMBIE,
DN_FIB_INFO(f),
dz_prefix(f->fn_key, dz),
DZ_MASK(dz), buffer);
buffer += 128;
if (++n >= count)
goto out;
}
}
}
out:
read_unlock(&dn_fib_tables_lock);
return n;
}
#endif /* CONFIG_PROC_FS */
 
struct dn_fib_table *dn_fib_get_table(int n, int create)
{
struct dn_fib_table *t;
 
if (n < DN_MIN_TABLE)
return NULL;
 
if (n > DN_NUM_TABLES)
return NULL;
 
if (dn_fib_tables[n])
return dn_fib_tables[n];
 
if (!create)
return NULL;
 
if (in_interrupt() && net_ratelimit()) {
printk(KERN_DEBUG "DECnet: BUG! Attempt to create routing table from interrupt\n");
return NULL;
}
if ((t = kmalloc(sizeof(struct dn_fib_table), GFP_KERNEL)) == NULL)
return NULL;
 
memset(t, 0, sizeof(struct dn_fib_table));
 
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;
#ifdef CONFIG_PROC_FS
t->get_info = dn_fib_table_get_info;
#endif
t->dump = dn_fib_table_dump;
dn_fib_tables[n] = t;
 
return t;
}
 
static void dn_fib_del_tree(int n)
{
struct dn_fib_table *t;
 
write_lock(&dn_fib_tables_lock);
t = dn_fib_tables[n];
dn_fib_tables[n] = NULL;
write_unlock(&dn_fib_tables_lock);
 
if (t) {
kfree(t);
}
}
 
struct dn_fib_table *dn_fib_empty_table(void)
{
int id;
 
for(id = DN_MIN_TABLE; id <= DN_NUM_TABLES; id++)
if (dn_fib_tables[id] == NULL)
return dn_fib_get_table(id, 1);
return NULL;
}
 
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, NULL);
}
 
void __exit dn_fib_table_cleanup(void)
{
int i;
 
for (i = 0; i < DN_NUM_TABLES + 1; ++i)
dn_fib_del_tree(i);
 
return;
}
/sysctl_net_decnet.c
0,0 → 1,389
/*
* 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:
*
*/
#include <linux/config.h>
#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 <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;
 
#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;
case 0:
return;
}
str++;
}
}
 
/*
* Simple routine to parse an ascii DECnet address
* into a network order address.
*/
static int parse_addr(dn_address *addr, char *str)
{
dn_address 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 = dn_htons((area << 10) | node);
 
return 0;
}
 
 
static int dn_node_address_strategy(ctl_table *table, int *name, int nlen,
void *oldval, size_t *oldlenp,
void *newval, size_t newlen,
void **context)
{
size_t len;
dn_address addr;
 
if (oldval && oldlenp) {
if (get_user(len, oldlenp))
return -EFAULT;
if (len) {
if (len != sizeof(unsigned short))
return -EINVAL;
if (put_user(decnet_address, (unsigned short *)oldval))
return -EFAULT;
}
}
if (newval && newlen) {
if (newlen != sizeof(unsigned short))
return -EINVAL;
if (get_user(addr, (unsigned short *)newval))
return -EFAULT;
 
dn_dev_devices_off();
 
decnet_address = addr;
dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
 
dn_dev_devices_on();
}
return 0;
}
 
static int dn_node_address_handler(ctl_table *table, int write,
struct file *filp,
void *buffer, size_t *lenp)
{
char addr[DN_ASCBUF_LEN];
size_t len;
dn_address dnaddr;
 
if (!*lenp || (filp->f_pos && !write)) {
*lenp = 0;
return 0;
}
 
if (write) {
int 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_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
 
dn_dev_devices_on();
 
filp->f_pos += len;
 
return 0;
}
 
dn_addr2asc(dn_ntohs(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;
filp->f_pos += len;
 
return 0;
}
 
 
static int dn_def_dev_strategy(ctl_table *table, int *name, int nlen,
void *oldval, size_t *oldlenp,
void *newval, size_t newlen,
void **context)
{
size_t len;
struct net_device *dev = decnet_default_device;
char devname[17];
size_t namel;
 
devname[0] = 0;
 
if (oldval && oldlenp) {
if (get_user(len, oldlenp))
return -EFAULT;
if (len) {
if (dev)
strcpy(devname, dev->name);
 
namel = strlen(devname) + 1;
if (len > namel) len = namel;
 
if (copy_to_user(oldval, devname, len))
return -EFAULT;
 
if (put_user(len, oldlenp))
return -EFAULT;
}
}
 
if (newval && newlen) {
if (newlen > 16)
return -E2BIG;
 
if (copy_from_user(devname, newval, newlen))
return -EFAULT;
 
devname[newlen] = 0;
 
if ((dev = __dev_get_by_name(devname)) == NULL)
return -ENODEV;
 
if (dev->dn_ptr == NULL)
return -ENODEV;
 
decnet_default_device = dev;
}
 
return 0;
}
 
 
static int dn_def_dev_handler(ctl_table *table, int write,
struct file * filp,
void *buffer, size_t *lenp)
{
size_t len;
struct net_device *dev = decnet_default_device;
char devname[17];
 
if (!*lenp || (filp->f_pos && !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);
 
if ((dev = __dev_get_by_name(devname)) == NULL)
return -ENODEV;
 
if (dev->dn_ptr == NULL)
return -ENODEV;
 
decnet_default_device = dev;
filp->f_pos += *lenp;
 
return 0;
}
 
if (dev == NULL) {
*lenp = 0;
return 0;
}
 
strcpy(devname, dev->name);
len = strlen(devname);
devname[len++] = '\n';
 
if (len > *lenp) len = *lenp;
 
if (copy_to_user(buffer, devname, len))
return -EFAULT;
 
*lenp = len;
filp->f_pos += len;
 
return 0;
}
 
static ctl_table dn_table[] = {
{NET_DECNET_NODE_ADDRESS, "node_address", NULL, 7, 0644, NULL,
dn_node_address_handler, dn_node_address_strategy, NULL,
NULL, NULL},
{NET_DECNET_NODE_NAME, "node_name", node_name, 7, 0644, NULL,
&proc_dostring, &sysctl_string, NULL, NULL, NULL},
{NET_DECNET_DEFAULT_DEVICE, "default_device", NULL, 16, 0644, NULL,
dn_def_dev_handler, dn_def_dev_strategy, NULL, NULL, NULL},
{NET_DECNET_TIME_WAIT, "time_wait", &decnet_time_wait,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_decnet_time_wait, &max_decnet_time_wait},
{NET_DECNET_DN_COUNT, "dn_count", &decnet_dn_count,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_state_count, &max_state_count},
{NET_DECNET_DI_COUNT, "di_count", &decnet_di_count,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_state_count, &max_state_count},
{NET_DECNET_DR_COUNT, "dr_count", &decnet_dr_count,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_state_count, &max_state_count},
{NET_DECNET_DST_GC_INTERVAL, "dst_gc_interval", &decnet_dst_gc_interval,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_decnet_dst_gc_interval, &max_decnet_dst_gc_interval},
{NET_DECNET_NO_FC_MAX_CWND, "no_fc_max_cwnd", &decnet_no_fc_max_cwnd,
sizeof(int), 0644,
NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
&min_decnet_no_fc_max_cwnd, &max_decnet_no_fc_max_cwnd},
{NET_DECNET_DEBUG_LEVEL, "debug", &decnet_debug_level,
sizeof(int), 0644,
NULL, &proc_dointvec, &sysctl_intvec, NULL,
NULL, NULL},
{0}
};
 
static ctl_table dn_dir_table[] = {
{NET_DECNET, "decnet", NULL, 0, 0555, dn_table},
{0}
};
 
static ctl_table dn_root_table[] = {
{CTL_NET, "net", NULL, 0, 0555, dn_dir_table},
{0}
};
 
void dn_register_sysctl(void)
{
dn_table_header = register_sysctl_table(dn_root_table, 1);
}
 
void dn_unregister_sysctl(void)
{
unregister_sysctl_table(dn_table_header);
}
 
#else /* CONFIG_SYSCTL */
void dn_unregister_sysctl(void)
{
}
void dn_register_sysctl(void)
{
}
 
#endif
/Config.in
0,0 → 1,12
#
# DECnet configuration
#
bool ' DECnet: SIOCGIFCONF support' CONFIG_DECNET_SIOCGIFCONF
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
bool ' DECnet: router support (EXPERIMENTAL)' CONFIG_DECNET_ROUTER
if [ "$CONFIG_DECNET_ROUTER" = "y" ]; then
if [ "$CONFIG_NETFILTER" = "y" ]; then
bool ' DECnet: use FWMARK value as routing key (EXPERIMENTAL)' CONFIG_DECNET_ROUTE_FWMARK
fi
fi
fi
/dn_neigh.c
0,0 → 1,600
/*
* 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.
*
*/
 
#include <linux/config.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/if_arp.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 <asm/atomic.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/dn.h>
#include <net/dn_dev.h>
#include <net/dn_neigh.h>
#include <net/dn_route.h>
 
static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev);
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 sk_buff *);
static int dn_short_output(struct sk_buff *);
static int dn_phase3_output(struct sk_buff *);
 
 
/*
* For talking to broadcast devices: Ethernet & PPP
*/
static struct neigh_ops dn_long_ops = {
family: AF_DECnet,
error_report: dn_long_error_report,
output: dn_long_output,
connected_output: dn_long_output,
hh_output: dev_queue_xmit,
queue_xmit: dev_queue_xmit,
};
 
/*
* For talking to pointopoint and multidrop devices: DDCMP and X.25
*/
static struct neigh_ops dn_short_ops = {
family: AF_DECnet,
error_report: dn_short_error_report,
output: dn_short_output,
connected_output: dn_short_output,
hh_output: dev_queue_xmit,
queue_xmit: dev_queue_xmit,
};
 
/*
* For talking to DECnet phase III nodes
*/
static 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,
hh_output: dev_queue_xmit,
queue_xmit: dev_queue_xmit
};
 
struct neigh_table dn_neigh_table = {
family: PF_DECnet,
entry_size: sizeof(struct dn_neigh),
key_len: sizeof(dn_address),
hash: dn_neigh_hash,
constructor: dn_neigh_construct,
id: "dn_neigh_cache",
parms: {
tbl: &dn_neigh_table,
entries: 0,
base_reachable_time: 30 * HZ,
retrans_time: 1 * HZ,
gc_staletime: 60 * HZ,
reachable_time: 30 * HZ,
delay_probe_time: 5 * HZ,
queue_len: 3,
ucast_probes: 0,
app_probes: 0,
mcast_probes: 0,
anycast_delay: 0,
proxy_delay: 0,
proxy_qlen: 0,
locktime: 1 * HZ,
},
gc_interval: 30 * HZ,
gc_thresh1: 128,
gc_thresh2: 512,
gc_thresh3: 1024,
};
 
static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev)
{
u32 hash_val;
 
hash_val = *(dn_address *)pkey;
hash_val ^= (hash_val >> 10);
hash_val ^= (hash_val >> 3);
 
return hash_val & NEIGH_HASHMASK;
}
 
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 dn_dev *)dev->dn_ptr;
 
if (dn_db == NULL)
return -EINVAL;
 
if (dn_db->neigh_parms)
neigh->parms = dn_db->neigh_parms;
 
if (dn_db->use_long)
neigh->ops = &dn_long_ops;
else
neigh->ops = &dn_short_ops;
 
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 {
if (net_ratelimit())
printk(KERN_DEBUG "Trying to create neigh for hw %d\n", dev->type);
return -EINVAL;
}
 
dn->blksize = 230;
 
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;
struct neighbour *neigh = dst->neighbour;
struct net_device *dev = neigh->dev;
 
if (!dev->hard_header || dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len) >= 0)
return neigh->ops->queue_xmit(skb);
 
if (net_ratelimit())
printk(KERN_DEBUG "dn_neigh_output_packet: oops, can't send packet\n");
 
kfree_skb(skb);
return -EINVAL;
}
 
static int dn_long_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct neighbour *neigh = dst->neighbour;
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) {
if (net_ratelimit())
printk(KERN_CRIT "dn_long_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
kfree_skb(skb);
skb = skb2;
if (net_ratelimit())
printk(KERN_INFO "dn_long_output: Increasing headroom\n");
}
 
data = skb_push(skb, sizeof(struct dn_long_packet) + 3);
lp = (struct dn_long_packet *)(data+3);
 
*((unsigned short *)data) = dn_htons(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, dn_ntohs(cb->dst));
lp->s_area = lp->s_subarea = 0;
dn_dn2eth(lp->s_id, dn_ntohs(cb->src));
lp->nl2 = 0;
lp->visit_ct = cb->hops & 0x3f;
lp->s_class = 0;
lp->pt = 0;
 
skb->nh.raw = skb->data;
 
return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
}
 
static int dn_short_output(struct sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct neighbour *neigh = dst->neighbour;
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) {
if (net_ratelimit())
printk(KERN_CRIT "dn_short_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
kfree_skb(skb);
skb = skb2;
if (net_ratelimit())
printk(KERN_INFO "dn_short_output: Increasing headroom\n");
}
 
data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
*((unsigned short *)data) = dn_htons(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->nh.raw = skb->data;
 
return NF_HOOK(PF_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 sk_buff *skb)
{
struct dst_entry *dst = skb->dst;
struct neighbour *neigh = dst->neighbour;
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) {
if (net_ratelimit())
printk(KERN_CRIT "dn_phase3_output: no memory\n");
kfree_skb(skb);
return -ENOBUFS;
}
kfree_skb(skb);
skb = skb2;
if (net_ratelimit())
printk(KERN_INFO "dn_phase3_output: Increasing headroom\n");
}
 
data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
*((unsigned short *)data) = dn_htons(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 & dn_htons(0x03ff);
sp->srcnode = cb->src & dn_htons(0x03ff);
sp->forward = cb->hops & 0x3f;
 
skb->nh.raw = skb->data;
 
return NF_HOOK(PF_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
*/
struct neighbour *dn_neigh_lookup(struct neigh_table *tbl, void *ptr)
{
struct neighbour *neigh;
u32 hash_val;
 
hash_val = tbl->hash(ptr, NULL);
 
read_lock_bh(&tbl->lock);
for(neigh = tbl->hash_buckets[hash_val]; neigh != NULL; neigh = neigh->next) {
if (memcmp(neigh->primary_key, ptr, tbl->key_len) == 0) {
atomic_inc(&neigh->refcnt);
read_unlock_bh(&tbl->lock);
return neigh;
}
}
read_unlock_bh(&tbl->lock);
 
return NULL;
}
 
 
/*
* Any traffic on a pointopoint link causes the timer to be reset
* for the entry in the neighbour table.
*/
void dn_neigh_pointopoint_notify(struct sk_buff *skb)
{
return;
}
 
/*
* 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;
dn_address src;
 
src = dn_htons(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 = (struct dn_dev *)neigh->dev->dn_ptr;
 
if (!(neigh->nud_state & NUD_PERMANENT)) {
neigh->updated = jiffies;
 
if (neigh->dev->type == ARPHRD_ETHER)
memcpy(neigh->ha, &skb->mac.ethernet->h_source, ETH_ALEN);
 
dn->blksize = dn_ntohs(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;
}
}
 
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;
dn_address src;
 
src = dn_htons(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, &skb->mac.ethernet->h_source, ETH_ALEN);
dn->flags &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
dn->blksize = dn_ntohs(msg->blksize);
dn->priority = 0;
}
 
write_unlock(&neigh->lock);
neigh_release(neigh);
}
 
kfree_skb(skb);
return 0;
}
 
 
#ifdef CONFIG_DECNET_ROUTER
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;
}
 
int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n)
{
int t = 0;
int i;
struct neighbour *neigh;
struct dn_neigh *dn;
struct neigh_table *tbl = &dn_neigh_table;
unsigned char *rs = ptr;
struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
 
read_lock_bh(&tbl->lock);
 
for(i = 0; i < NEIGH_HASHMASK; i++) {
for(neigh = tbl->hash_buckets[i]; neigh != NULL; neigh = neigh->next) {
if (neigh->dev != dev)
continue;
dn = (struct dn_neigh *)neigh;
if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
continue;
if (dn_db->parms.forwarding == 1 && (dn->flags & DN_NDFLAG_R2))
continue;
if (t == n)
rs = dn_find_slot(ptr, n, dn->priority);
else
t++;
if (rs == NULL)
continue;
dn_dn2eth(rs, dn->addr);
rs += 6;
*rs = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
*rs |= dn->priority;
rs++;
}
}
 
read_unlock_bh(&tbl->lock);
 
return t;
}
#endif /* CONFIG_DECNET_ROUTER */
 
 
 
#ifdef CONFIG_PROC_FS
static int dn_neigh_get_info(char *buffer, char **start, off_t offset, int length)
{
int len = 0;
off_t pos = 0;
off_t begin = 0;
struct neighbour *n;
int i;
char buf[DN_ASCBUF_LEN];
 
len += sprintf(buffer + len, "Addr Flags State Use Blksize Dev\n");
 
for(i=0;i <= NEIGH_HASHMASK; i++) {
read_lock_bh(&dn_neigh_table.lock);
n = dn_neigh_table.hash_buckets[i];
for(; n != NULL; n = n->next) {
struct dn_neigh *dn = (struct dn_neigh *)n;
 
read_lock(&n->lock);
len += sprintf(buffer+len, "%-7s %s%s%s %02x %02d %07ld %-8s\n",
dn_addr2asc(dn_ntohs(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);
 
pos = begin + len;
 
if (pos < offset) {
len = 0;
begin = pos;
}
 
if (pos > offset + length) {
read_unlock_bh(&dn_neigh_table.lock);
goto done;
}
}
read_unlock_bh(&dn_neigh_table.lock);
}
 
done:
 
*start = buffer + (offset - begin);
len -= offset - begin;
 
if (len > length) len = length;
 
return len;
}
 
#endif
 
void __init dn_neigh_init(void)
{
neigh_table_init(&dn_neigh_table);
 
#ifdef CONFIG_PROC_FS
proc_net_create("decnet_neigh",0,dn_neigh_get_info);
#endif /* CONFIG_PROC_FS */
}
 
void __exit dn_neigh_cleanup(void)
{
proc_net_remove("decnet_neigh");
neigh_table_clear(&dn_neigh_table);
}
/dn_rules.c
0,0 → 1,371
 
/*
* 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:
*
*/
#include <linux/config.h>
#include <linux/string.h>
#include <linux/net.h>
#include <linux/socket.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 <asm/atomic.h>
#include <asm/uaccess.h>
#include <net/neighbour.h>
#include <net/dst.h>
#include <net/dn.h>
#include <net/dn_fib.h>
#include <net/dn_neigh.h>
#include <net/dn_dev.h>
 
struct dn_fib_rule
{
struct dn_fib_rule *r_next;
atomic_t r_clntref;
u32 r_preference;
unsigned char r_table;
unsigned char r_action;
unsigned char r_dst_len;
unsigned char r_src_len;
dn_address r_src;
dn_address r_srcmask;
dn_address r_dst;
dn_address r_dstmask;
u8 r_flags;
#ifdef CONFIG_DECNET_ROUTE_FWMARK
u32 r_fwmark;
#endif
int r_ifindex;
char r_ifname[IFNAMSIZ];
int r_dead;
};
 
static struct dn_fib_rule default_rule = {
r_clntref: ATOMIC_INIT(2),
r_preference: 0x7fff,
r_table: DN_DEFAULT_TABLE,
r_action: RTN_UNICAST
};
 
static struct dn_fib_rule *dn_fib_rules = &default_rule;
static rwlock_t dn_fib_rules_lock = RW_LOCK_UNLOCKED;
 
 
int dn_fib_rtm_delrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct rtattr **rta = arg;
struct rtmsg *rtm = NLMSG_DATA(nlh);
struct dn_fib_rule *r, **rp;
int err = -ESRCH;
 
for(rp=&dn_fib_rules; (r=*rp) != NULL; rp = &r->r_next) {
if ((!rta[RTA_SRC-1] || memcmp(RTA_DATA(rta[RTA_SRC-1]), &r->r_src, 2) == 0) &&
rtm->rtm_src_len == r->r_src_len &&
rtm->rtm_dst_len == r->r_dst_len &&
(!rta[RTA_DST-1] || memcmp(RTA_DATA(rta[RTA_DST-1]), &r->r_dst, 2) == 0) &&
#ifdef CONFIG_DECNET_ROUTE_FWMARK
(!rta[RTA_PROTOINFO-1] || memcmp(RTA_DATA(rta[RTA_PROTOINFO-1]), &r->r_fwmark, 4) == 0) &&
#endif
(!rtm->rtm_type || rtm->rtm_type == r->r_action) &&
(!rta[RTA_PRIORITY-1] || memcmp(RTA_DATA(rta[RTA_PRIORITY-1]), &r->r_preference, 4) == 0) &&
(!rta[RTA_IIF-1] || strcmp(RTA_DATA(rta[RTA_IIF-1]), r->r_ifname) == 0) &&
(!rtm->rtm_table || (r && rtm->rtm_table == r->r_table))) {
 
err = -EPERM;
if (r == &default_rule)
break;
 
write_lock_bh(&dn_fib_rules_lock);
*rp = r->r_next;
r->r_dead = 1;
write_unlock_bh(&dn_fib_rules_lock);
dn_fib_rule_put(r);
err = 0;
break;
}
}
 
return err;
}
 
void dn_fib_rule_put(struct dn_fib_rule *r)
{
if (atomic_dec_and_test(&r->r_clntref)) {
if (r->r_dead)
kfree(r);
else
printk(KERN_DEBUG "Attempt to free alive dn_fib_rule\n");
}
}
 
 
int dn_fib_rtm_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
struct rtattr **rta = arg;
struct rtmsg *rtm = NLMSG_DATA(nlh);
struct dn_fib_rule *r, *new_r, **rp;
unsigned char table_id;
 
if (rtm->rtm_src_len > 16 || rtm->rtm_dst_len > 16)
return -EINVAL;
 
if (rta[RTA_IIF-1] && RTA_PAYLOAD(rta[RTA_IIF-1]) > IFNAMSIZ)
return -EINVAL;
 
if (rtm->rtm_type == RTN_NAT)
return -EINVAL;
 
table_id = rtm->rtm_table;
if (table_id == RT_TABLE_UNSPEC) {
struct dn_fib_table *tb;
if (rtm->rtm_type == RTN_UNICAST) {
if ((tb = dn_fib_empty_table()) == NULL)
return -ENOBUFS;
table_id = tb->n;
}
}
 
new_r = kmalloc(sizeof(*new_r), GFP_KERNEL);
if (!new_r)
return -ENOMEM;
memset(new_r, 0, sizeof(*new_r));
if (rta[RTA_SRC-1])
memcpy(&new_r->r_src, RTA_DATA(rta[RTA_SRC-1]), 2);
if (rta[RTA_DST-1])
memcpy(&new_r->r_dst, RTA_DATA(rta[RTA_DST-1]), 2);
new_r->r_src_len = rtm->rtm_src_len;
new_r->r_dst_len = rtm->rtm_dst_len;
new_r->r_srcmask = dnet_make_mask(rtm->rtm_src_len);
new_r->r_dstmask = dnet_make_mask(rtm->rtm_dst_len);
#ifdef CONFIG_DECNET_ROUTE_FWMARK
if (rta[RTA_PROTOINFO-1])
memcpy(&new_r->r_fwmark, RTA_DATA(rta[RTA_PROTOINFO-1]), 4);
#endif
new_r->r_action = rtm->rtm_type;
new_r->r_flags = rtm->rtm_flags;
if (rta[RTA_PRIORITY-1])
memcpy(&new_r->r_preference, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
new_r->r_table = table_id;
if (rta[RTA_IIF-1]) {
struct net_device *dev;
memcpy(new_r->r_ifname, RTA_DATA(rta[RTA_IIF-1]), IFNAMSIZ);
new_r->r_ifname[IFNAMSIZ-1] = 0;
new_r->r_ifindex = -1;
dev = __dev_get_by_name(new_r->r_ifname);
if (dev)
new_r->r_ifindex = dev->ifindex;
}
 
rp = &dn_fib_rules;
if (!new_r->r_preference) {
r = dn_fib_rules;
if (r && (r = r->r_next) != NULL) {
rp = &dn_fib_rules->r_next;
if (r->r_preference)
new_r->r_preference = r->r_preference - 1;
}
}
 
while((r=*rp) != NULL) {
if (r->r_preference > new_r->r_preference)
break;
rp = &r->r_next;
}
 
new_r->r_next = r;
atomic_inc(&new_r->r_clntref);
write_lock_bh(&dn_fib_rules_lock);
*rp = new_r;
write_unlock_bh(&dn_fib_rules_lock);
return 0;
}
 
 
int dn_fib_lookup(struct dn_fib_key *key, struct dn_fib_res *res)
{
struct dn_fib_rule *r, *policy;
struct dn_fib_table *tb;
dn_address saddr = key->src;
dn_address daddr = key->dst;
int err;
 
read_lock(&dn_fib_rules_lock);
for(r = dn_fib_rules; r; r = r->r_next) {
if (((saddr^r->r_src) & r->r_srcmask) ||
((daddr^r->r_dst) & r->r_dstmask) ||
#ifdef CONFIG_DECNET_ROUTE_FWMARK
(r->r_fwmark && r->r_fwmark != key->fwmark) ||
#endif
(r->r_ifindex && r->r_ifindex != key->iif))
continue;
 
switch(r->r_action) {
case RTN_UNICAST:
policy = r;
break;
case RTN_UNREACHABLE:
read_unlock(&dn_fib_rules_lock);
return -ENETUNREACH;
default:
case RTN_BLACKHOLE:
read_unlock(&dn_fib_rules_lock);
return -EINVAL;
case RTN_PROHIBIT:
read_unlock(&dn_fib_rules_lock);
return -EACCES;
}
 
if ((tb = dn_fib_get_table(r->r_table, 0)) == NULL)
continue;
err = tb->lookup(tb, key, res);
if (err == 0) {
res->r = policy;
if (policy)
atomic_inc(&policy->r_clntref);
read_unlock(&dn_fib_rules_lock);
return 0;
}
if (err < 0 && err != -EAGAIN) {
read_unlock(&dn_fib_rules_lock);
return err;
}
}
 
read_unlock(&dn_fib_rules_lock);
return -ESRCH;
}
 
static void dn_fib_rules_detach(struct net_device *dev)
{
struct dn_fib_rule *r;
 
for(r = dn_fib_rules; r; r = r->r_next) {
if (r->r_ifindex == dev->ifindex) {
write_lock_bh(&dn_fib_rules_lock);
r->r_ifindex = -1;
write_unlock_bh(&dn_fib_rules_lock);
}
}
}
 
static void dn_fib_rules_attach(struct net_device *dev)
{
struct dn_fib_rule *r;
 
for(r = dn_fib_rules; r; r = r->r_next) {
if (r->r_ifindex == -1 && strcmp(dev->name, r->r_ifname) == 0) {
write_lock_bh(&dn_fib_rules_lock);
r->r_ifindex = dev->ifindex;
write_unlock_bh(&dn_fib_rules_lock);
}
}
}
 
static int dn_fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
 
switch(event) {
case NETDEV_UNREGISTER:
dn_fib_rules_detach(dev);
dn_fib_sync_down(0, dev, 1);
case NETDEV_REGISTER:
dn_fib_rules_attach(dev);
dn_fib_sync_up(dev);
}
 
return NOTIFY_DONE;
}
 
 
static struct notifier_block dn_fib_rules_notifier = {
notifier_call: dn_fib_rules_event,
};
 
static int dn_fib_fill_rule(struct sk_buff *skb, struct dn_fib_rule *r, struct netlink_callback *cb)
{
struct rtmsg *rtm;
struct nlmsghdr *nlh;
unsigned char *b = skb->tail;
 
 
nlh = NLMSG_PUT(skb, NETLINK_CREDS(cb->skb)->pid, cb->nlh->nlmsg_seq, RTM_NEWRULE, sizeof(*rtm));
rtm = NLMSG_DATA(nlh);
rtm->rtm_family = AF_DECnet;
rtm->rtm_dst_len = r->r_dst_len;
rtm->rtm_src_len = r->r_src_len;
rtm->rtm_tos = 0;
#ifdef CONFIG_DECNET_ROUTE_FWMARK
if (r->r_fwmark)
RTA_PUT(skb, RTA_PROTOINFO, 4, &r->r_fwmark);
#endif
rtm->rtm_table = r->r_table;
rtm->rtm_protocol = 0;
rtm->rtm_scope = 0;
rtm->rtm_type = r->r_action;
rtm->rtm_flags = r->r_flags;
 
if (r->r_dst_len)
RTA_PUT(skb, RTA_DST, 2, &r->r_dst);
if (r->r_src_len)
RTA_PUT(skb, RTA_SRC, 2, &r->r_src);
if (r->r_ifname[0])
RTA_PUT(skb, RTA_IIF, IFNAMSIZ, &r->r_ifname);
if (r->r_preference)
RTA_PUT(skb, RTA_PRIORITY, 4, &r->r_preference);
nlh->nlmsg_len = skb->tail - b;
return skb->len;
 
nlmsg_failure:
rtattr_failure:
skb_trim(skb, b - skb->data);
return -1;
}
 
int dn_fib_dump_rules(struct sk_buff *skb, struct netlink_callback *cb)
{
int idx;
int s_idx = cb->args[0];
struct dn_fib_rule *r;
 
read_lock(&dn_fib_rules_lock);
for(r = dn_fib_rules, idx = 0; r; r = r->r_next, idx++) {
if (idx < s_idx)
continue;
if (dn_fib_fill_rule(skb, r, cb) < 0)
break;
}
read_unlock(&dn_fib_rules_lock);
cb->args[0] = idx;
 
return skb->len;
}
 
void __init dn_fib_rules_init(void)
{
register_netdevice_notifier(&dn_fib_rules_notifier);
}
 
void __exit dn_fib_rules_cleanup(void)
{
unregister_netdevice_notifier(&dn_fib_rules_notifier);
}
 
 
/Makefile
0,0 → 1,13
# Note 2! The CFLAGS definition is now in the main makefile...
 
O_TARGET := decnet.o
obj-y := af_decnet.o dn_nsp_in.o dn_nsp_out.o dn_route.o dn_dev.o dn_neigh.o dn_timer.o
obj-m := $(O_TARGET)
 
obj-$(CONFIG_DECNET_ROUTER) += dn_fib.o dn_rules.o dn_table.o
obj-$(CONFIG_DECNET_FW) += dn_fw.o
 
obj-y += sysctl_net_decnet.o
 
include $(TOPDIR)/Rules.make
 
/README
0,0 → 1,8
Linux DECnet Project
======================
 
The documentation for this kernel subsystem is available in the
Documentation/networking subdirctory of this distribution and also
on line at http://www.chygwyn.com/DECnet/
 
Steve Whitehouse <SteveW@ACM.org>

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.