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> |