forked from Minki/linux
b67bfe0d42
I'm not sure why, but the hlist for each entry iterators were conceived list_for_each_entry(pos, head, member) The hlist ones were greedy and wanted an extra parameter: hlist_for_each_entry(tpos, pos, head, member) Why did they need an extra pos parameter? I'm not quite sure. Not only they don't really need it, it also prevents the iterator from looking exactly like the list iterator, which is unfortunate. Besides the semantic patch, there was some manual work required: - Fix up the actual hlist iterators in linux/list.h - Fix up the declaration of other iterators based on the hlist ones. - A very small amount of places were using the 'node' parameter, this was modified to use 'obj->member' instead. - Coccinelle didn't handle the hlist_for_each_entry_safe iterator properly, so those had to be fixed up manually. The semantic patch which is mostly the work of Peter Senna Tschudin is here: @@ iterator name hlist_for_each_entry, hlist_for_each_entry_continue, hlist_for_each_entry_from, hlist_for_each_entry_rcu, hlist_for_each_entry_rcu_bh, hlist_for_each_entry_continue_rcu_bh, for_each_busy_worker, ax25_uid_for_each, ax25_for_each, inet_bind_bucket_for_each, sctp_for_each_hentry, sk_for_each, sk_for_each_rcu, sk_for_each_from, sk_for_each_safe, sk_for_each_bound, hlist_for_each_entry_safe, hlist_for_each_entry_continue_rcu, nr_neigh_for_each, nr_neigh_for_each_safe, nr_node_for_each, nr_node_for_each_safe, for_each_gfn_indirect_valid_sp, for_each_gfn_sp, for_each_host; type T; expression a,c,d,e; identifier b; statement S; @@ -T b; <+... when != b ( hlist_for_each_entry(a, - b, c, d) S | hlist_for_each_entry_continue(a, - b, c) S | hlist_for_each_entry_from(a, - b, c) S | hlist_for_each_entry_rcu(a, - b, c, d) S | hlist_for_each_entry_rcu_bh(a, - b, c, d) S | hlist_for_each_entry_continue_rcu_bh(a, - b, c) S | for_each_busy_worker(a, c, - b, d) S | ax25_uid_for_each(a, - b, c) S | ax25_for_each(a, - b, c) S | inet_bind_bucket_for_each(a, - b, c) S | sctp_for_each_hentry(a, - b, c) S | sk_for_each(a, - b, c) S | sk_for_each_rcu(a, - b, c) S | sk_for_each_from -(a, b) +(a) S + sk_for_each_from(a) S | sk_for_each_safe(a, - b, c, d) S | sk_for_each_bound(a, - b, c) S | hlist_for_each_entry_safe(a, - b, c, d, e) S | hlist_for_each_entry_continue_rcu(a, - b, c) S | nr_neigh_for_each(a, - b, c) S | nr_neigh_for_each_safe(a, - b, c, d) S | nr_node_for_each(a, - b, c) S | nr_node_for_each_safe(a, - b, c, d) S | - for_each_gfn_sp(a, c, d, b) S + for_each_gfn_sp(a, c, d) S | - for_each_gfn_indirect_valid_sp(a, c, d, b) S + for_each_gfn_indirect_valid_sp(a, c, d) S | for_each_host(a, - b, c) S | for_each_host_safe(a, - b, c, d) S | for_each_mesh_entry(a, - b, c, d) S ) ...+> [akpm@linux-foundation.org: drop bogus change from net/ipv4/raw.c] [akpm@linux-foundation.org: drop bogus hunk from net/ipv6/raw.c] [akpm@linux-foundation.org: checkpatch fixes] [akpm@linux-foundation.org: fix warnings] [akpm@linux-foudnation.org: redo intrusive kvm changes] Tested-by: Peter Senna Tschudin <peter.senna@gmail.com> Acked-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com> Signed-off-by: Sasha Levin <sasha.levin@oracle.com> Cc: Wu Fengguang <fengguang.wu@intel.com> Cc: Marcelo Tosatti <mtosatti@redhat.com> Cc: Gleb Natapov <gleb@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1031 lines
23 KiB
C
1031 lines
23 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Copyright Jonathan Naylor G4KLX (g4klx@g4klx.demon.co.uk)
|
|
* Copyright Alan Cox GW4PTS (alan@lxorguk.ukuu.org.uk)
|
|
* Copyright Tomi Manninen OH2BNS (oh2bns@sral.fi)
|
|
*/
|
|
#include <linux/errno.h>
|
|
#include <linux/types.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/in.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/string.h>
|
|
#include <linux/sockios.h>
|
|
#include <linux/net.h>
|
|
#include <linux/slab.h>
|
|
#include <net/ax25.h>
|
|
#include <linux/inet.h>
|
|
#include <linux/netdevice.h>
|
|
#include <net/arp.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <net/sock.h>
|
|
#include <asm/uaccess.h>
|
|
#include <linux/fcntl.h>
|
|
#include <linux/termios.h> /* For TIOCINQ/OUTQ */
|
|
#include <linux/mm.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/notifier.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/init.h>
|
|
#include <linux/spinlock.h>
|
|
#include <net/netrom.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/export.h>
|
|
|
|
static unsigned int nr_neigh_no = 1;
|
|
|
|
static HLIST_HEAD(nr_node_list);
|
|
static DEFINE_SPINLOCK(nr_node_list_lock);
|
|
static HLIST_HEAD(nr_neigh_list);
|
|
static DEFINE_SPINLOCK(nr_neigh_list_lock);
|
|
|
|
static struct nr_node *nr_node_get(ax25_address *callsign)
|
|
{
|
|
struct nr_node *found = NULL;
|
|
struct nr_node *nr_node;
|
|
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each(nr_node, &nr_node_list)
|
|
if (ax25cmp(callsign, &nr_node->callsign) == 0) {
|
|
nr_node_hold(nr_node);
|
|
found = nr_node;
|
|
break;
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
return found;
|
|
}
|
|
|
|
static struct nr_neigh *nr_neigh_get_dev(ax25_address *callsign,
|
|
struct net_device *dev)
|
|
{
|
|
struct nr_neigh *found = NULL;
|
|
struct nr_neigh *nr_neigh;
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
nr_neigh_for_each(nr_neigh, &nr_neigh_list)
|
|
if (ax25cmp(callsign, &nr_neigh->callsign) == 0 &&
|
|
nr_neigh->dev == dev) {
|
|
nr_neigh_hold(nr_neigh);
|
|
found = nr_neigh;
|
|
break;
|
|
}
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
return found;
|
|
}
|
|
|
|
static void nr_remove_neigh(struct nr_neigh *);
|
|
|
|
/*
|
|
* Add a new route to a node, and in the process add the node and the
|
|
* neighbour if it is new.
|
|
*/
|
|
static int __must_check nr_add_node(ax25_address *nr, const char *mnemonic,
|
|
ax25_address *ax25, ax25_digi *ax25_digi, struct net_device *dev,
|
|
int quality, int obs_count)
|
|
{
|
|
struct nr_node *nr_node;
|
|
struct nr_neigh *nr_neigh;
|
|
struct nr_route nr_route;
|
|
int i, found;
|
|
struct net_device *odev;
|
|
|
|
if ((odev=nr_dev_get(nr)) != NULL) { /* Can't add routes to ourself */
|
|
dev_put(odev);
|
|
return -EINVAL;
|
|
}
|
|
|
|
nr_node = nr_node_get(nr);
|
|
|
|
nr_neigh = nr_neigh_get_dev(ax25, dev);
|
|
|
|
/*
|
|
* The L2 link to a neighbour has failed in the past
|
|
* and now a frame comes from this neighbour. We assume
|
|
* it was a temporary trouble with the link and reset the
|
|
* routes now (and not wait for a node broadcast).
|
|
*/
|
|
if (nr_neigh != NULL && nr_neigh->failed != 0 && quality == 0) {
|
|
struct nr_node *nr_nodet;
|
|
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each(nr_nodet, &nr_node_list) {
|
|
nr_node_lock(nr_nodet);
|
|
for (i = 0; i < nr_nodet->count; i++)
|
|
if (nr_nodet->routes[i].neighbour == nr_neigh)
|
|
if (i < nr_nodet->which)
|
|
nr_nodet->which = i;
|
|
nr_node_unlock(nr_nodet);
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
}
|
|
|
|
if (nr_neigh != NULL)
|
|
nr_neigh->failed = 0;
|
|
|
|
if (quality == 0 && nr_neigh != NULL && nr_node != NULL) {
|
|
nr_neigh_put(nr_neigh);
|
|
nr_node_put(nr_node);
|
|
return 0;
|
|
}
|
|
|
|
if (nr_neigh == NULL) {
|
|
if ((nr_neigh = kmalloc(sizeof(*nr_neigh), GFP_ATOMIC)) == NULL) {
|
|
if (nr_node)
|
|
nr_node_put(nr_node);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nr_neigh->callsign = *ax25;
|
|
nr_neigh->digipeat = NULL;
|
|
nr_neigh->ax25 = NULL;
|
|
nr_neigh->dev = dev;
|
|
nr_neigh->quality = sysctl_netrom_default_path_quality;
|
|
nr_neigh->locked = 0;
|
|
nr_neigh->count = 0;
|
|
nr_neigh->number = nr_neigh_no++;
|
|
nr_neigh->failed = 0;
|
|
atomic_set(&nr_neigh->refcount, 1);
|
|
|
|
if (ax25_digi != NULL && ax25_digi->ndigi > 0) {
|
|
nr_neigh->digipeat = kmemdup(ax25_digi,
|
|
sizeof(*ax25_digi),
|
|
GFP_KERNEL);
|
|
if (nr_neigh->digipeat == NULL) {
|
|
kfree(nr_neigh);
|
|
if (nr_node)
|
|
nr_node_put(nr_node);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
hlist_add_head(&nr_neigh->neigh_node, &nr_neigh_list);
|
|
nr_neigh_hold(nr_neigh);
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
}
|
|
|
|
if (quality != 0 && ax25cmp(nr, ax25) == 0 && !nr_neigh->locked)
|
|
nr_neigh->quality = quality;
|
|
|
|
if (nr_node == NULL) {
|
|
if ((nr_node = kmalloc(sizeof(*nr_node), GFP_ATOMIC)) == NULL) {
|
|
if (nr_neigh)
|
|
nr_neigh_put(nr_neigh);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
nr_node->callsign = *nr;
|
|
strcpy(nr_node->mnemonic, mnemonic);
|
|
|
|
nr_node->which = 0;
|
|
nr_node->count = 1;
|
|
atomic_set(&nr_node->refcount, 1);
|
|
spin_lock_init(&nr_node->node_lock);
|
|
|
|
nr_node->routes[0].quality = quality;
|
|
nr_node->routes[0].obs_count = obs_count;
|
|
nr_node->routes[0].neighbour = nr_neigh;
|
|
|
|
nr_neigh_hold(nr_neigh);
|
|
nr_neigh->count++;
|
|
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
hlist_add_head(&nr_node->node_node, &nr_node_list);
|
|
/* refcount initialized at 1 */
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
nr_node_lock(nr_node);
|
|
|
|
if (quality != 0)
|
|
strcpy(nr_node->mnemonic, mnemonic);
|
|
|
|
for (found = 0, i = 0; i < nr_node->count; i++) {
|
|
if (nr_node->routes[i].neighbour == nr_neigh) {
|
|
nr_node->routes[i].quality = quality;
|
|
nr_node->routes[i].obs_count = obs_count;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
/* We have space at the bottom, slot it in */
|
|
if (nr_node->count < 3) {
|
|
nr_node->routes[2] = nr_node->routes[1];
|
|
nr_node->routes[1] = nr_node->routes[0];
|
|
|
|
nr_node->routes[0].quality = quality;
|
|
nr_node->routes[0].obs_count = obs_count;
|
|
nr_node->routes[0].neighbour = nr_neigh;
|
|
|
|
nr_node->which++;
|
|
nr_node->count++;
|
|
nr_neigh_hold(nr_neigh);
|
|
nr_neigh->count++;
|
|
} else {
|
|
/* It must be better than the worst */
|
|
if (quality > nr_node->routes[2].quality) {
|
|
nr_node->routes[2].neighbour->count--;
|
|
nr_neigh_put(nr_node->routes[2].neighbour);
|
|
|
|
if (nr_node->routes[2].neighbour->count == 0 && !nr_node->routes[2].neighbour->locked)
|
|
nr_remove_neigh(nr_node->routes[2].neighbour);
|
|
|
|
nr_node->routes[2].quality = quality;
|
|
nr_node->routes[2].obs_count = obs_count;
|
|
nr_node->routes[2].neighbour = nr_neigh;
|
|
|
|
nr_neigh_hold(nr_neigh);
|
|
nr_neigh->count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Now re-sort the routes in quality order */
|
|
switch (nr_node->count) {
|
|
case 3:
|
|
if (nr_node->routes[1].quality > nr_node->routes[0].quality) {
|
|
switch (nr_node->which) {
|
|
case 0:
|
|
nr_node->which = 1;
|
|
break;
|
|
case 1:
|
|
nr_node->which = 0;
|
|
break;
|
|
}
|
|
nr_route = nr_node->routes[0];
|
|
nr_node->routes[0] = nr_node->routes[1];
|
|
nr_node->routes[1] = nr_route;
|
|
}
|
|
if (nr_node->routes[2].quality > nr_node->routes[1].quality) {
|
|
switch (nr_node->which) {
|
|
case 1: nr_node->which = 2;
|
|
break;
|
|
|
|
case 2: nr_node->which = 1;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
nr_route = nr_node->routes[1];
|
|
nr_node->routes[1] = nr_node->routes[2];
|
|
nr_node->routes[2] = nr_route;
|
|
}
|
|
case 2:
|
|
if (nr_node->routes[1].quality > nr_node->routes[0].quality) {
|
|
switch (nr_node->which) {
|
|
case 0: nr_node->which = 1;
|
|
break;
|
|
|
|
case 1: nr_node->which = 0;
|
|
break;
|
|
|
|
default: break;
|
|
}
|
|
nr_route = nr_node->routes[0];
|
|
nr_node->routes[0] = nr_node->routes[1];
|
|
nr_node->routes[1] = nr_route;
|
|
}
|
|
case 1:
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < nr_node->count; i++) {
|
|
if (nr_node->routes[i].neighbour == nr_neigh) {
|
|
if (i < nr_node->which)
|
|
nr_node->which = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
nr_neigh_put(nr_neigh);
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
return 0;
|
|
}
|
|
|
|
static inline void __nr_remove_node(struct nr_node *nr_node)
|
|
{
|
|
hlist_del_init(&nr_node->node_node);
|
|
nr_node_put(nr_node);
|
|
}
|
|
|
|
#define nr_remove_node_locked(__node) \
|
|
__nr_remove_node(__node)
|
|
|
|
static void nr_remove_node(struct nr_node *nr_node)
|
|
{
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
__nr_remove_node(nr_node);
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
}
|
|
|
|
static inline void __nr_remove_neigh(struct nr_neigh *nr_neigh)
|
|
{
|
|
hlist_del_init(&nr_neigh->neigh_node);
|
|
nr_neigh_put(nr_neigh);
|
|
}
|
|
|
|
#define nr_remove_neigh_locked(__neigh) \
|
|
__nr_remove_neigh(__neigh)
|
|
|
|
static void nr_remove_neigh(struct nr_neigh *nr_neigh)
|
|
{
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
__nr_remove_neigh(nr_neigh);
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
}
|
|
|
|
/*
|
|
* "Delete" a node. Strictly speaking remove a route to a node. The node
|
|
* is only deleted if no routes are left to it.
|
|
*/
|
|
static int nr_del_node(ax25_address *callsign, ax25_address *neighbour, struct net_device *dev)
|
|
{
|
|
struct nr_node *nr_node;
|
|
struct nr_neigh *nr_neigh;
|
|
int i;
|
|
|
|
nr_node = nr_node_get(callsign);
|
|
|
|
if (nr_node == NULL)
|
|
return -EINVAL;
|
|
|
|
nr_neigh = nr_neigh_get_dev(neighbour, dev);
|
|
|
|
if (nr_neigh == NULL) {
|
|
nr_node_put(nr_node);
|
|
return -EINVAL;
|
|
}
|
|
|
|
nr_node_lock(nr_node);
|
|
for (i = 0; i < nr_node->count; i++) {
|
|
if (nr_node->routes[i].neighbour == nr_neigh) {
|
|
nr_neigh->count--;
|
|
nr_neigh_put(nr_neigh);
|
|
|
|
if (nr_neigh->count == 0 && !nr_neigh->locked)
|
|
nr_remove_neigh(nr_neigh);
|
|
nr_neigh_put(nr_neigh);
|
|
|
|
nr_node->count--;
|
|
|
|
if (nr_node->count == 0) {
|
|
nr_remove_node(nr_node);
|
|
} else {
|
|
switch (i) {
|
|
case 0:
|
|
nr_node->routes[0] = nr_node->routes[1];
|
|
case 1:
|
|
nr_node->routes[1] = nr_node->routes[2];
|
|
case 2:
|
|
break;
|
|
}
|
|
nr_node_put(nr_node);
|
|
}
|
|
nr_node_unlock(nr_node);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
nr_neigh_put(nr_neigh);
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Lock a neighbour with a quality.
|
|
*/
|
|
static int __must_check nr_add_neigh(ax25_address *callsign,
|
|
ax25_digi *ax25_digi, struct net_device *dev, unsigned int quality)
|
|
{
|
|
struct nr_neigh *nr_neigh;
|
|
|
|
nr_neigh = nr_neigh_get_dev(callsign, dev);
|
|
if (nr_neigh) {
|
|
nr_neigh->quality = quality;
|
|
nr_neigh->locked = 1;
|
|
nr_neigh_put(nr_neigh);
|
|
return 0;
|
|
}
|
|
|
|
if ((nr_neigh = kmalloc(sizeof(*nr_neigh), GFP_ATOMIC)) == NULL)
|
|
return -ENOMEM;
|
|
|
|
nr_neigh->callsign = *callsign;
|
|
nr_neigh->digipeat = NULL;
|
|
nr_neigh->ax25 = NULL;
|
|
nr_neigh->dev = dev;
|
|
nr_neigh->quality = quality;
|
|
nr_neigh->locked = 1;
|
|
nr_neigh->count = 0;
|
|
nr_neigh->number = nr_neigh_no++;
|
|
nr_neigh->failed = 0;
|
|
atomic_set(&nr_neigh->refcount, 1);
|
|
|
|
if (ax25_digi != NULL && ax25_digi->ndigi > 0) {
|
|
nr_neigh->digipeat = kmemdup(ax25_digi, sizeof(*ax25_digi),
|
|
GFP_KERNEL);
|
|
if (nr_neigh->digipeat == NULL) {
|
|
kfree(nr_neigh);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
hlist_add_head(&nr_neigh->neigh_node, &nr_neigh_list);
|
|
/* refcount is initialized at 1 */
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* "Delete" a neighbour. The neighbour is only removed if the number
|
|
* of nodes that may use it is zero.
|
|
*/
|
|
static int nr_del_neigh(ax25_address *callsign, struct net_device *dev, unsigned int quality)
|
|
{
|
|
struct nr_neigh *nr_neigh;
|
|
|
|
nr_neigh = nr_neigh_get_dev(callsign, dev);
|
|
|
|
if (nr_neigh == NULL) return -EINVAL;
|
|
|
|
nr_neigh->quality = quality;
|
|
nr_neigh->locked = 0;
|
|
|
|
if (nr_neigh->count == 0)
|
|
nr_remove_neigh(nr_neigh);
|
|
nr_neigh_put(nr_neigh);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Decrement the obsolescence count by one. If a route is reduced to a
|
|
* count of zero, remove it. Also remove any unlocked neighbours with
|
|
* zero nodes routing via it.
|
|
*/
|
|
static int nr_dec_obs(void)
|
|
{
|
|
struct nr_neigh *nr_neigh;
|
|
struct nr_node *s;
|
|
struct hlist_node *nodet;
|
|
int i;
|
|
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each_safe(s, nodet, &nr_node_list) {
|
|
nr_node_lock(s);
|
|
for (i = 0; i < s->count; i++) {
|
|
switch (s->routes[i].obs_count) {
|
|
case 0: /* A locked entry */
|
|
break;
|
|
|
|
case 1: /* From 1 -> 0 */
|
|
nr_neigh = s->routes[i].neighbour;
|
|
|
|
nr_neigh->count--;
|
|
nr_neigh_put(nr_neigh);
|
|
|
|
if (nr_neigh->count == 0 && !nr_neigh->locked)
|
|
nr_remove_neigh(nr_neigh);
|
|
|
|
s->count--;
|
|
|
|
switch (i) {
|
|
case 0:
|
|
s->routes[0] = s->routes[1];
|
|
/* Fallthrough */
|
|
case 1:
|
|
s->routes[1] = s->routes[2];
|
|
case 2:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
s->routes[i].obs_count--;
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
if (s->count <= 0)
|
|
nr_remove_node_locked(s);
|
|
nr_node_unlock(s);
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A device has been removed. Remove its routes and neighbours.
|
|
*/
|
|
void nr_rt_device_down(struct net_device *dev)
|
|
{
|
|
struct nr_neigh *s;
|
|
struct hlist_node *nodet, *node2t;
|
|
struct nr_node *t;
|
|
int i;
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
nr_neigh_for_each_safe(s, nodet, &nr_neigh_list) {
|
|
if (s->dev == dev) {
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each_safe(t, node2t, &nr_node_list) {
|
|
nr_node_lock(t);
|
|
for (i = 0; i < t->count; i++) {
|
|
if (t->routes[i].neighbour == s) {
|
|
t->count--;
|
|
|
|
switch (i) {
|
|
case 0:
|
|
t->routes[0] = t->routes[1];
|
|
case 1:
|
|
t->routes[1] = t->routes[2];
|
|
case 2:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (t->count <= 0)
|
|
nr_remove_node_locked(t);
|
|
nr_node_unlock(t);
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
|
|
nr_remove_neigh_locked(s);
|
|
}
|
|
}
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
}
|
|
|
|
/*
|
|
* Check that the device given is a valid AX.25 interface that is "up".
|
|
* Or a valid ethernet interface with an AX.25 callsign binding.
|
|
*/
|
|
static struct net_device *nr_ax25_dev_get(char *devname)
|
|
{
|
|
struct net_device *dev;
|
|
|
|
if ((dev = dev_get_by_name(&init_net, devname)) == NULL)
|
|
return NULL;
|
|
|
|
if ((dev->flags & IFF_UP) && dev->type == ARPHRD_AX25)
|
|
return dev;
|
|
|
|
dev_put(dev);
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Find the first active NET/ROM device, usually "nr0".
|
|
*/
|
|
struct net_device *nr_dev_first(void)
|
|
{
|
|
struct net_device *dev, *first = NULL;
|
|
|
|
rcu_read_lock();
|
|
for_each_netdev_rcu(&init_net, dev) {
|
|
if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM)
|
|
if (first == NULL || strncmp(dev->name, first->name, 3) < 0)
|
|
first = dev;
|
|
}
|
|
if (first)
|
|
dev_hold(first);
|
|
rcu_read_unlock();
|
|
|
|
return first;
|
|
}
|
|
|
|
/*
|
|
* Find the NET/ROM device for the given callsign.
|
|
*/
|
|
struct net_device *nr_dev_get(ax25_address *addr)
|
|
{
|
|
struct net_device *dev;
|
|
|
|
rcu_read_lock();
|
|
for_each_netdev_rcu(&init_net, dev) {
|
|
if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM &&
|
|
ax25cmp(addr, (ax25_address *)dev->dev_addr) == 0) {
|
|
dev_hold(dev);
|
|
goto out;
|
|
}
|
|
}
|
|
dev = NULL;
|
|
out:
|
|
rcu_read_unlock();
|
|
return dev;
|
|
}
|
|
|
|
static ax25_digi *nr_call_to_digi(ax25_digi *digi, int ndigis,
|
|
ax25_address *digipeaters)
|
|
{
|
|
int i;
|
|
|
|
if (ndigis == 0)
|
|
return NULL;
|
|
|
|
for (i = 0; i < ndigis; i++) {
|
|
digi->calls[i] = digipeaters[i];
|
|
digi->repeated[i] = 0;
|
|
}
|
|
|
|
digi->ndigi = ndigis;
|
|
digi->lastrepeat = -1;
|
|
|
|
return digi;
|
|
}
|
|
|
|
/*
|
|
* Handle the ioctls that control the routing functions.
|
|
*/
|
|
int nr_rt_ioctl(unsigned int cmd, void __user *arg)
|
|
{
|
|
struct nr_route_struct nr_route;
|
|
struct net_device *dev;
|
|
ax25_digi digi;
|
|
int ret;
|
|
|
|
switch (cmd) {
|
|
case SIOCADDRT:
|
|
if (copy_from_user(&nr_route, arg, sizeof(struct nr_route_struct)))
|
|
return -EFAULT;
|
|
if (nr_route.ndigis > AX25_MAX_DIGIS)
|
|
return -EINVAL;
|
|
if ((dev = nr_ax25_dev_get(nr_route.device)) == NULL)
|
|
return -EINVAL;
|
|
switch (nr_route.type) {
|
|
case NETROM_NODE:
|
|
if (strnlen(nr_route.mnemonic, 7) == 7) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
ret = nr_add_node(&nr_route.callsign,
|
|
nr_route.mnemonic,
|
|
&nr_route.neighbour,
|
|
nr_call_to_digi(&digi, nr_route.ndigis,
|
|
nr_route.digipeaters),
|
|
dev, nr_route.quality,
|
|
nr_route.obs_count);
|
|
break;
|
|
case NETROM_NEIGH:
|
|
ret = nr_add_neigh(&nr_route.callsign,
|
|
nr_call_to_digi(&digi, nr_route.ndigis,
|
|
nr_route.digipeaters),
|
|
dev, nr_route.quality);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
dev_put(dev);
|
|
return ret;
|
|
|
|
case SIOCDELRT:
|
|
if (copy_from_user(&nr_route, arg, sizeof(struct nr_route_struct)))
|
|
return -EFAULT;
|
|
if ((dev = nr_ax25_dev_get(nr_route.device)) == NULL)
|
|
return -EINVAL;
|
|
switch (nr_route.type) {
|
|
case NETROM_NODE:
|
|
ret = nr_del_node(&nr_route.callsign,
|
|
&nr_route.neighbour, dev);
|
|
break;
|
|
case NETROM_NEIGH:
|
|
ret = nr_del_neigh(&nr_route.callsign,
|
|
dev, nr_route.quality);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
}
|
|
dev_put(dev);
|
|
return ret;
|
|
|
|
case SIOCNRDECOBS:
|
|
return nr_dec_obs();
|
|
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* A level 2 link has timed out, therefore it appears to be a poor link,
|
|
* then don't use that neighbour until it is reset.
|
|
*/
|
|
void nr_link_failed(ax25_cb *ax25, int reason)
|
|
{
|
|
struct nr_neigh *s, *nr_neigh = NULL;
|
|
struct nr_node *nr_node = NULL;
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
nr_neigh_for_each(s, &nr_neigh_list) {
|
|
if (s->ax25 == ax25) {
|
|
nr_neigh_hold(s);
|
|
nr_neigh = s;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
|
|
if (nr_neigh == NULL)
|
|
return;
|
|
|
|
nr_neigh->ax25 = NULL;
|
|
ax25_cb_put(ax25);
|
|
|
|
if (++nr_neigh->failed < sysctl_netrom_link_fails_count) {
|
|
nr_neigh_put(nr_neigh);
|
|
return;
|
|
}
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each(nr_node, &nr_node_list) {
|
|
nr_node_lock(nr_node);
|
|
if (nr_node->which < nr_node->count &&
|
|
nr_node->routes[nr_node->which].neighbour == nr_neigh)
|
|
nr_node->which++;
|
|
nr_node_unlock(nr_node);
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
nr_neigh_put(nr_neigh);
|
|
}
|
|
|
|
/*
|
|
* Route a frame to an appropriate AX.25 connection. A NULL ax25_cb
|
|
* indicates an internally generated frame.
|
|
*/
|
|
int nr_route_frame(struct sk_buff *skb, ax25_cb *ax25)
|
|
{
|
|
ax25_address *nr_src, *nr_dest;
|
|
struct nr_neigh *nr_neigh;
|
|
struct nr_node *nr_node;
|
|
struct net_device *dev;
|
|
unsigned char *dptr;
|
|
ax25_cb *ax25s;
|
|
int ret;
|
|
struct sk_buff *skbn;
|
|
|
|
|
|
nr_src = (ax25_address *)(skb->data + 0);
|
|
nr_dest = (ax25_address *)(skb->data + 7);
|
|
|
|
if (ax25 != NULL) {
|
|
ret = nr_add_node(nr_src, "", &ax25->dest_addr, ax25->digipeat,
|
|
ax25->ax25_dev->dev, 0,
|
|
sysctl_netrom_obsolescence_count_initialiser);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if ((dev = nr_dev_get(nr_dest)) != NULL) { /* Its for me */
|
|
if (ax25 == NULL) /* Its from me */
|
|
ret = nr_loopback_queue(skb);
|
|
else
|
|
ret = nr_rx_frame(skb, dev);
|
|
dev_put(dev);
|
|
return ret;
|
|
}
|
|
|
|
if (!sysctl_netrom_routing_control && ax25 != NULL)
|
|
return 0;
|
|
|
|
/* Its Time-To-Live has expired */
|
|
if (skb->data[14] == 1) {
|
|
return 0;
|
|
}
|
|
|
|
nr_node = nr_node_get(nr_dest);
|
|
if (nr_node == NULL)
|
|
return 0;
|
|
nr_node_lock(nr_node);
|
|
|
|
if (nr_node->which >= nr_node->count) {
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
return 0;
|
|
}
|
|
|
|
nr_neigh = nr_node->routes[nr_node->which].neighbour;
|
|
|
|
if ((dev = nr_dev_first()) == NULL) {
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
return 0;
|
|
}
|
|
|
|
/* We are going to change the netrom headers so we should get our
|
|
own skb, we also did not know until now how much header space
|
|
we had to reserve... - RXQ */
|
|
if ((skbn=skb_copy_expand(skb, dev->hard_header_len, 0, GFP_ATOMIC)) == NULL) {
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
dev_put(dev);
|
|
return 0;
|
|
}
|
|
kfree_skb(skb);
|
|
skb=skbn;
|
|
skb->data[14]--;
|
|
|
|
dptr = skb_push(skb, 1);
|
|
*dptr = AX25_P_NETROM;
|
|
|
|
ax25s = nr_neigh->ax25;
|
|
nr_neigh->ax25 = ax25_send_frame(skb, 256,
|
|
(ax25_address *)dev->dev_addr,
|
|
&nr_neigh->callsign,
|
|
nr_neigh->digipeat, nr_neigh->dev);
|
|
if (ax25s)
|
|
ax25_cb_put(ax25s);
|
|
|
|
dev_put(dev);
|
|
ret = (nr_neigh->ax25 != NULL);
|
|
nr_node_unlock(nr_node);
|
|
nr_node_put(nr_node);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
static void *nr_node_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
return seq_hlist_start_head(&nr_node_list, *pos);
|
|
}
|
|
|
|
static void *nr_node_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return seq_hlist_next(v, &nr_node_list, pos);
|
|
}
|
|
|
|
static void nr_node_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
}
|
|
|
|
static int nr_node_show(struct seq_file *seq, void *v)
|
|
{
|
|
char buf[11];
|
|
int i;
|
|
|
|
if (v == SEQ_START_TOKEN)
|
|
seq_puts(seq,
|
|
"callsign mnemonic w n qual obs neigh qual obs neigh qual obs neigh\n");
|
|
else {
|
|
struct nr_node *nr_node = hlist_entry(v, struct nr_node,
|
|
node_node);
|
|
|
|
nr_node_lock(nr_node);
|
|
seq_printf(seq, "%-9s %-7s %d %d",
|
|
ax2asc(buf, &nr_node->callsign),
|
|
(nr_node->mnemonic[0] == '\0') ? "*" : nr_node->mnemonic,
|
|
nr_node->which + 1,
|
|
nr_node->count);
|
|
|
|
for (i = 0; i < nr_node->count; i++) {
|
|
seq_printf(seq, " %3d %d %05d",
|
|
nr_node->routes[i].quality,
|
|
nr_node->routes[i].obs_count,
|
|
nr_node->routes[i].neighbour->number);
|
|
}
|
|
nr_node_unlock(nr_node);
|
|
|
|
seq_puts(seq, "\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations nr_node_seqops = {
|
|
.start = nr_node_start,
|
|
.next = nr_node_next,
|
|
.stop = nr_node_stop,
|
|
.show = nr_node_show,
|
|
};
|
|
|
|
static int nr_node_info_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &nr_node_seqops);
|
|
}
|
|
|
|
const struct file_operations nr_nodes_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = nr_node_info_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
static void *nr_neigh_start(struct seq_file *seq, loff_t *pos)
|
|
{
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
return seq_hlist_start_head(&nr_neigh_list, *pos);
|
|
}
|
|
|
|
static void *nr_neigh_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
{
|
|
return seq_hlist_next(v, &nr_neigh_list, pos);
|
|
}
|
|
|
|
static void nr_neigh_stop(struct seq_file *seq, void *v)
|
|
{
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
}
|
|
|
|
static int nr_neigh_show(struct seq_file *seq, void *v)
|
|
{
|
|
char buf[11];
|
|
int i;
|
|
|
|
if (v == SEQ_START_TOKEN)
|
|
seq_puts(seq, "addr callsign dev qual lock count failed digipeaters\n");
|
|
else {
|
|
struct nr_neigh *nr_neigh;
|
|
|
|
nr_neigh = hlist_entry(v, struct nr_neigh, neigh_node);
|
|
seq_printf(seq, "%05d %-9s %-4s %3d %d %3d %3d",
|
|
nr_neigh->number,
|
|
ax2asc(buf, &nr_neigh->callsign),
|
|
nr_neigh->dev ? nr_neigh->dev->name : "???",
|
|
nr_neigh->quality,
|
|
nr_neigh->locked,
|
|
nr_neigh->count,
|
|
nr_neigh->failed);
|
|
|
|
if (nr_neigh->digipeat != NULL) {
|
|
for (i = 0; i < nr_neigh->digipeat->ndigi; i++)
|
|
seq_printf(seq, " %s",
|
|
ax2asc(buf, &nr_neigh->digipeat->calls[i]));
|
|
}
|
|
|
|
seq_puts(seq, "\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct seq_operations nr_neigh_seqops = {
|
|
.start = nr_neigh_start,
|
|
.next = nr_neigh_next,
|
|
.stop = nr_neigh_stop,
|
|
.show = nr_neigh_show,
|
|
};
|
|
|
|
static int nr_neigh_info_open(struct inode *inode, struct file *file)
|
|
{
|
|
return seq_open(file, &nr_neigh_seqops);
|
|
}
|
|
|
|
const struct file_operations nr_neigh_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = nr_neigh_info_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
#endif
|
|
|
|
/*
|
|
* Free all memory associated with the nodes and routes lists.
|
|
*/
|
|
void __exit nr_rt_free(void)
|
|
{
|
|
struct nr_neigh *s = NULL;
|
|
struct nr_node *t = NULL;
|
|
struct hlist_node *nodet;
|
|
|
|
spin_lock_bh(&nr_neigh_list_lock);
|
|
spin_lock_bh(&nr_node_list_lock);
|
|
nr_node_for_each_safe(t, nodet, &nr_node_list) {
|
|
nr_node_lock(t);
|
|
nr_remove_node_locked(t);
|
|
nr_node_unlock(t);
|
|
}
|
|
nr_neigh_for_each_safe(s, nodet, &nr_neigh_list) {
|
|
while(s->count) {
|
|
s->count--;
|
|
nr_neigh_put(s);
|
|
}
|
|
nr_remove_neigh_locked(s);
|
|
}
|
|
spin_unlock_bh(&nr_node_list_lock);
|
|
spin_unlock_bh(&nr_neigh_list_lock);
|
|
}
|