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/netrom
- from Rev 1275 to Rev 1765
- ↔ Reverse comparison
Rev 1275 → Rev 1765
/nr_timer.c
0,0 → 1,245
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_timer.c |
* NET/ROM 007 Jonathan(G4KLX) New timer architecture. |
* Implemented idle timer. |
*/ |
|
#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 <net/ax25.h> |
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/skbuff.h> |
#include <net/sock.h> |
#include <asm/uaccess.h> |
#include <asm/system.h> |
#include <linux/fcntl.h> |
#include <linux/mm.h> |
#include <linux/interrupt.h> |
#include <net/netrom.h> |
|
static void nr_heartbeat_expiry(unsigned long); |
static void nr_t1timer_expiry(unsigned long); |
static void nr_t2timer_expiry(unsigned long); |
static void nr_t4timer_expiry(unsigned long); |
static void nr_idletimer_expiry(unsigned long); |
|
void nr_start_t1timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t1timer); |
|
sk->protinfo.nr->t1timer.data = (unsigned long)sk; |
sk->protinfo.nr->t1timer.function = &nr_t1timer_expiry; |
sk->protinfo.nr->t1timer.expires = jiffies + sk->protinfo.nr->t1; |
|
add_timer(&sk->protinfo.nr->t1timer); |
} |
|
void nr_start_t2timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t2timer); |
|
sk->protinfo.nr->t2timer.data = (unsigned long)sk; |
sk->protinfo.nr->t2timer.function = &nr_t2timer_expiry; |
sk->protinfo.nr->t2timer.expires = jiffies + sk->protinfo.nr->t2; |
|
add_timer(&sk->protinfo.nr->t2timer); |
} |
|
void nr_start_t4timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t4timer); |
|
sk->protinfo.nr->t4timer.data = (unsigned long)sk; |
sk->protinfo.nr->t4timer.function = &nr_t4timer_expiry; |
sk->protinfo.nr->t4timer.expires = jiffies + sk->protinfo.nr->t4; |
|
add_timer(&sk->protinfo.nr->t4timer); |
} |
|
void nr_start_idletimer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->idletimer); |
|
if (sk->protinfo.nr->idle > 0) { |
sk->protinfo.nr->idletimer.data = (unsigned long)sk; |
sk->protinfo.nr->idletimer.function = &nr_idletimer_expiry; |
sk->protinfo.nr->idletimer.expires = jiffies + sk->protinfo.nr->idle; |
|
add_timer(&sk->protinfo.nr->idletimer); |
} |
} |
|
void nr_start_heartbeat(struct sock *sk) |
{ |
del_timer(&sk->timer); |
|
sk->timer.data = (unsigned long)sk; |
sk->timer.function = &nr_heartbeat_expiry; |
sk->timer.expires = jiffies + 5 * HZ; |
|
add_timer(&sk->timer); |
} |
|
void nr_stop_t1timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t1timer); |
} |
|
void nr_stop_t2timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t2timer); |
} |
|
void nr_stop_t4timer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->t4timer); |
} |
|
void nr_stop_idletimer(struct sock *sk) |
{ |
del_timer(&sk->protinfo.nr->idletimer); |
} |
|
void nr_stop_heartbeat(struct sock *sk) |
{ |
del_timer(&sk->timer); |
} |
|
int nr_t1timer_running(struct sock *sk) |
{ |
return timer_pending(&sk->protinfo.nr->t1timer); |
} |
|
static void nr_heartbeat_expiry(unsigned long param) |
{ |
struct sock *sk = (struct sock *)param; |
|
switch (sk->protinfo.nr->state) { |
|
case NR_STATE_0: |
/* Magic here: If we listen() and a new link dies before it |
is accepted() it isn't 'dead' so doesn't get removed. */ |
if (sk->destroy || (sk->state == TCP_LISTEN && sk->dead)) { |
nr_destroy_socket(sk); |
return; |
} |
break; |
|
case NR_STATE_3: |
/* |
* Check for the state of the receive buffer. |
*/ |
if (atomic_read(&sk->rmem_alloc) < (sk->rcvbuf / 2) && |
(sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY)) { |
sk->protinfo.nr->condition &= ~NR_COND_OWN_RX_BUSY; |
sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING; |
sk->protinfo.nr->vl = sk->protinfo.nr->vr; |
nr_write_internal(sk, NR_INFOACK); |
break; |
} |
break; |
} |
|
nr_start_heartbeat(sk); |
} |
|
static void nr_t2timer_expiry(unsigned long param) |
{ |
struct sock *sk = (struct sock *)param; |
|
if (sk->protinfo.nr->condition & NR_COND_ACK_PENDING) { |
sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING; |
nr_enquiry_response(sk); |
} |
} |
|
static void nr_t4timer_expiry(unsigned long param) |
{ |
struct sock *sk = (struct sock *)param; |
|
sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY; |
} |
|
static void nr_idletimer_expiry(unsigned long param) |
{ |
struct sock *sk = (struct sock *)param; |
|
nr_clear_queues(sk); |
|
sk->protinfo.nr->n2count = 0; |
nr_write_internal(sk, NR_DISCREQ); |
sk->protinfo.nr->state = NR_STATE_2; |
|
nr_start_t1timer(sk); |
nr_stop_t2timer(sk); |
nr_stop_t4timer(sk); |
|
sk->state = TCP_CLOSE; |
sk->err = 0; |
sk->shutdown |= SEND_SHUTDOWN; |
|
if (!sk->dead) |
sk->state_change(sk); |
|
sk->dead = 1; |
} |
|
static void nr_t1timer_expiry(unsigned long param) |
{ |
struct sock *sk = (struct sock *)param; |
|
switch (sk->protinfo.nr->state) { |
|
case NR_STATE_1: |
if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) { |
nr_disconnect(sk, ETIMEDOUT); |
return; |
} else { |
sk->protinfo.nr->n2count++; |
nr_write_internal(sk, NR_CONNREQ); |
} |
break; |
|
case NR_STATE_2: |
if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) { |
nr_disconnect(sk, ETIMEDOUT); |
return; |
} else { |
sk->protinfo.nr->n2count++; |
nr_write_internal(sk, NR_DISCREQ); |
} |
break; |
|
case NR_STATE_3: |
if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) { |
nr_disconnect(sk, ETIMEDOUT); |
return; |
} else { |
sk->protinfo.nr->n2count++; |
nr_requeue_frames(sk); |
} |
break; |
} |
|
nr_start_t1timer(sk); |
} |
/nr_in.c
0,0 → 1,304
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* Most of this code is based on the SDL diagrams published in the 7th |
* ARRL Computer Networking Conference papers. The diagrams have mistakes |
* in them, but are mostly correct. Before you modify the code could you |
* read the SDL diagrams as the code is not obvious and probably very |
* easy to break; |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_in.c |
* NET/ROM 003 Jonathan(G4KLX) Added NET/ROM fragment reception. |
* Darryl(G7LED) Added missing INFO with NAK case, optimized |
* INFOACK handling, removed reconnect on error. |
* NET/ROM 006 Jonathan(G4KLX) Hdrincl removal changes. |
* NET/ROM 007 Jonathan(G4KLX) New timer architecture. |
*/ |
|
#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 <net/ax25.h> |
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/skbuff.h> |
#include <net/sock.h> |
#include <net/ip.h> /* For ip_rcv */ |
#include <asm/uaccess.h> |
#include <asm/system.h> |
#include <linux/fcntl.h> |
#include <linux/mm.h> |
#include <linux/interrupt.h> |
#include <net/netrom.h> |
|
static int nr_queue_rx_frame(struct sock *sk, struct sk_buff *skb, int more) |
{ |
struct sk_buff *skbo, *skbn = skb; |
|
skb_pull(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN); |
|
nr_start_idletimer(sk); |
|
if (more) { |
sk->protinfo.nr->fraglen += skb->len; |
skb_queue_tail(&sk->protinfo.nr->frag_queue, skb); |
return 0; |
} |
|
if (!more && sk->protinfo.nr->fraglen > 0) { /* End of fragment */ |
sk->protinfo.nr->fraglen += skb->len; |
skb_queue_tail(&sk->protinfo.nr->frag_queue, skb); |
|
if ((skbn = alloc_skb(sk->protinfo.nr->fraglen, GFP_ATOMIC)) == NULL) |
return 1; |
|
skbn->h.raw = skbn->data; |
|
while ((skbo = skb_dequeue(&sk->protinfo.nr->frag_queue)) != NULL) { |
memcpy(skb_put(skbn, skbo->len), skbo->data, skbo->len); |
kfree_skb(skbo); |
} |
|
sk->protinfo.nr->fraglen = 0; |
} |
|
return sock_queue_rcv_skb(sk, skbn); |
} |
|
/* |
* State machine for state 1, Awaiting Connection State. |
* The handling of the timer(s) is in file nr_timer.c. |
* Handling of state 0 and connection release is in netrom.c. |
*/ |
static int nr_state1_machine(struct sock *sk, struct sk_buff *skb, int frametype) |
{ |
switch (frametype) { |
|
case NR_CONNACK: |
nr_stop_t1timer(sk); |
nr_start_idletimer(sk); |
sk->protinfo.nr->your_index = skb->data[17]; |
sk->protinfo.nr->your_id = skb->data[18]; |
sk->protinfo.nr->vs = 0; |
sk->protinfo.nr->va = 0; |
sk->protinfo.nr->vr = 0; |
sk->protinfo.nr->vl = 0; |
sk->protinfo.nr->state = NR_STATE_3; |
sk->protinfo.nr->n2count = 0; |
sk->protinfo.nr->window = skb->data[20]; |
sk->state = TCP_ESTABLISHED; |
if (!sk->dead) |
sk->state_change(sk); |
break; |
|
case NR_CONNACK | NR_CHOKE_FLAG: |
nr_disconnect(sk, ECONNREFUSED); |
break; |
|
default: |
break; |
} |
|
return 0; |
} |
|
/* |
* State machine for state 2, Awaiting Release State. |
* The handling of the timer(s) is in file nr_timer.c |
* Handling of state 0 and connection release is in netrom.c. |
*/ |
static int nr_state2_machine(struct sock *sk, struct sk_buff *skb, int frametype) |
{ |
switch (frametype) { |
|
case NR_CONNACK | NR_CHOKE_FLAG: |
nr_disconnect(sk, ECONNRESET); |
break; |
|
case NR_DISCREQ: |
nr_write_internal(sk, NR_DISCACK); |
|
case NR_DISCACK: |
nr_disconnect(sk, 0); |
break; |
|
default: |
break; |
} |
|
return 0; |
} |
|
/* |
* State machine for state 3, Connected State. |
* The handling of the timer(s) is in file nr_timer.c |
* Handling of state 0 and connection release is in netrom.c. |
*/ |
static int nr_state3_machine(struct sock *sk, struct sk_buff *skb, int frametype) |
{ |
struct sk_buff_head temp_queue; |
struct sk_buff *skbn; |
unsigned short save_vr; |
unsigned short nr, ns; |
int queued = 0; |
|
nr = skb->data[18]; |
ns = skb->data[17]; |
|
switch (frametype) { |
|
case NR_CONNREQ: |
nr_write_internal(sk, NR_CONNACK); |
break; |
|
case NR_DISCREQ: |
nr_write_internal(sk, NR_DISCACK); |
nr_disconnect(sk, 0); |
break; |
|
case NR_CONNACK | NR_CHOKE_FLAG: |
case NR_DISCACK: |
nr_disconnect(sk, ECONNRESET); |
break; |
|
case NR_INFOACK: |
case NR_INFOACK | NR_CHOKE_FLAG: |
case NR_INFOACK | NR_NAK_FLAG: |
case NR_INFOACK | NR_NAK_FLAG | NR_CHOKE_FLAG: |
if (frametype & NR_CHOKE_FLAG) { |
sk->protinfo.nr->condition |= NR_COND_PEER_RX_BUSY; |
nr_start_t4timer(sk); |
} else { |
sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY; |
nr_stop_t4timer(sk); |
} |
if (!nr_validate_nr(sk, nr)) { |
break; |
} |
if (frametype & NR_NAK_FLAG) { |
nr_frames_acked(sk, nr); |
nr_send_nak_frame(sk); |
} else { |
if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY) { |
nr_frames_acked(sk, nr); |
} else { |
nr_check_iframes_acked(sk, nr); |
} |
} |
break; |
|
case NR_INFO: |
case NR_INFO | NR_NAK_FLAG: |
case NR_INFO | NR_CHOKE_FLAG: |
case NR_INFO | NR_MORE_FLAG: |
case NR_INFO | NR_NAK_FLAG | NR_CHOKE_FLAG: |
case NR_INFO | NR_CHOKE_FLAG | NR_MORE_FLAG: |
case NR_INFO | NR_NAK_FLAG | NR_MORE_FLAG: |
case NR_INFO | NR_NAK_FLAG | NR_CHOKE_FLAG | NR_MORE_FLAG: |
if (frametype & NR_CHOKE_FLAG) { |
sk->protinfo.nr->condition |= NR_COND_PEER_RX_BUSY; |
nr_start_t4timer(sk); |
} else { |
sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY; |
nr_stop_t4timer(sk); |
} |
if (nr_validate_nr(sk, nr)) { |
if (frametype & NR_NAK_FLAG) { |
nr_frames_acked(sk, nr); |
nr_send_nak_frame(sk); |
} else { |
if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY) { |
nr_frames_acked(sk, nr); |
} else { |
nr_check_iframes_acked(sk, nr); |
} |
} |
} |
queued = 1; |
skb_queue_head(&sk->protinfo.nr->reseq_queue, skb); |
if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY) |
break; |
skb_queue_head_init(&temp_queue); |
do { |
save_vr = sk->protinfo.nr->vr; |
while ((skbn = skb_dequeue(&sk->protinfo.nr->reseq_queue)) != NULL) { |
ns = skbn->data[17]; |
if (ns == sk->protinfo.nr->vr) { |
if (nr_queue_rx_frame(sk, skbn, frametype & NR_MORE_FLAG) == 0) { |
sk->protinfo.nr->vr = (sk->protinfo.nr->vr + 1) % NR_MODULUS; |
} else { |
sk->protinfo.nr->condition |= NR_COND_OWN_RX_BUSY; |
skb_queue_tail(&temp_queue, skbn); |
} |
} else if (nr_in_rx_window(sk, ns)) { |
skb_queue_tail(&temp_queue, skbn); |
} else { |
kfree_skb(skbn); |
} |
} |
while ((skbn = skb_dequeue(&temp_queue)) != NULL) { |
skb_queue_tail(&sk->protinfo.nr->reseq_queue, skbn); |
} |
} while (save_vr != sk->protinfo.nr->vr); |
/* |
* Window is full, ack it immediately. |
*/ |
if (((sk->protinfo.nr->vl + sk->protinfo.nr->window) % NR_MODULUS) == sk->protinfo.nr->vr) { |
nr_enquiry_response(sk); |
} else { |
if (!(sk->protinfo.nr->condition & NR_COND_ACK_PENDING)) { |
sk->protinfo.nr->condition |= NR_COND_ACK_PENDING; |
nr_start_t2timer(sk); |
} |
} |
break; |
|
default: |
break; |
} |
|
return queued; |
} |
|
/* Higher level upcall for a LAPB frame */ |
int nr_process_rx_frame(struct sock *sk, struct sk_buff *skb) |
{ |
int queued = 0, frametype; |
|
if (sk->protinfo.nr->state == NR_STATE_0) |
return 0; |
|
frametype = skb->data[19]; |
|
switch (sk->protinfo.nr->state) { |
case NR_STATE_1: |
queued = nr_state1_machine(sk, skb, frametype); |
break; |
case NR_STATE_2: |
queued = nr_state2_machine(sk, skb, frametype); |
break; |
case NR_STATE_3: |
queued = nr_state3_machine(sk, skb, frametype); |
break; |
} |
|
nr_kick(sk); |
|
return queued; |
} |
/nr_out.c
0,0 → 1,272
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_out.c |
* NET/ROM 003 Jonathan(G4KLX) Added NET/ROM fragmentation. |
* Darryl(G7LED) Fixed NAK, to give out correct reponse. |
* NET/ROM 007 Jonathan(G4KLX) New timer architecture. |
*/ |
|
#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 <net/ax25.h> |
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/skbuff.h> |
#include <net/sock.h> |
#include <asm/uaccess.h> |
#include <asm/system.h> |
#include <linux/fcntl.h> |
#include <linux/mm.h> |
#include <linux/interrupt.h> |
#include <net/netrom.h> |
|
/* |
* This is where all NET/ROM frames pass, except for IP-over-NET/ROM which |
* cannot be fragmented in this manner. |
*/ |
void nr_output(struct sock *sk, struct sk_buff *skb) |
{ |
struct sk_buff *skbn; |
unsigned char transport[NR_TRANSPORT_LEN]; |
int err, frontlen, len; |
|
if (skb->len - NR_TRANSPORT_LEN > NR_MAX_PACKET_SIZE) { |
/* Save a copy of the Transport Header */ |
memcpy(transport, skb->data, NR_TRANSPORT_LEN); |
skb_pull(skb, NR_TRANSPORT_LEN); |
|
frontlen = skb_headroom(skb); |
|
while (skb->len > 0) { |
if ((skbn = sock_alloc_send_skb(sk, frontlen + NR_MAX_PACKET_SIZE, 0, &err)) == NULL) |
return; |
|
skb_reserve(skbn, frontlen); |
|
len = (NR_MAX_PACKET_SIZE > skb->len) ? skb->len : NR_MAX_PACKET_SIZE; |
|
/* Copy the user data */ |
memcpy(skb_put(skbn, len), skb->data, len); |
skb_pull(skb, len); |
|
/* Duplicate the Transport Header */ |
skb_push(skbn, NR_TRANSPORT_LEN); |
memcpy(skbn->data, transport, NR_TRANSPORT_LEN); |
|
if (skb->len > 0) |
skbn->data[4] |= NR_MORE_FLAG; |
|
skb_queue_tail(&sk->write_queue, skbn); /* Throw it on the queue */ |
} |
|
kfree_skb(skb); |
} else { |
skb_queue_tail(&sk->write_queue, skb); /* Throw it on the queue */ |
} |
|
nr_kick(sk); |
} |
|
/* |
* This procedure is passed a buffer descriptor for an iframe. It builds |
* the rest of the control part of the frame and then writes it out. |
*/ |
static void nr_send_iframe(struct sock *sk, struct sk_buff *skb) |
{ |
if (skb == NULL) |
return; |
|
skb->data[2] = sk->protinfo.nr->vs; |
skb->data[3] = sk->protinfo.nr->vr; |
|
if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY) |
skb->data[4] |= NR_CHOKE_FLAG; |
|
nr_start_idletimer(sk); |
|
nr_transmit_buffer(sk, skb); |
} |
|
void nr_send_nak_frame(struct sock *sk) |
{ |
struct sk_buff *skb, *skbn; |
|
if ((skb = skb_peek(&sk->protinfo.nr->ack_queue)) == NULL) |
return; |
|
if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) |
return; |
|
skbn->data[2] = sk->protinfo.nr->va; |
skbn->data[3] = sk->protinfo.nr->vr; |
|
if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY) |
skbn->data[4] |= NR_CHOKE_FLAG; |
|
nr_transmit_buffer(sk, skbn); |
|
sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING; |
sk->protinfo.nr->vl = sk->protinfo.nr->vr; |
|
nr_stop_t1timer(sk); |
} |
|
void nr_kick(struct sock *sk) |
{ |
struct sk_buff *skb, *skbn; |
unsigned short start, end; |
|
if (sk->protinfo.nr->state != NR_STATE_3) |
return; |
|
if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY) |
return; |
|
if (skb_peek(&sk->write_queue) == NULL) |
return; |
|
start = (skb_peek(&sk->protinfo.nr->ack_queue) == NULL) ? sk->protinfo.nr->va : sk->protinfo.nr->vs; |
end = (sk->protinfo.nr->va + sk->protinfo.nr->window) % NR_MODULUS; |
|
if (start == end) |
return; |
|
sk->protinfo.nr->vs = start; |
|
/* |
* Transmit data until either we're out of data to send or |
* the window is full. |
*/ |
|
/* |
* Dequeue the frame and copy it. |
*/ |
skb = skb_dequeue(&sk->write_queue); |
|
do { |
if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) { |
skb_queue_head(&sk->write_queue, skb); |
break; |
} |
|
skb_set_owner_w(skbn, sk); |
|
/* |
* Transmit the frame copy. |
*/ |
nr_send_iframe(sk, skbn); |
|
sk->protinfo.nr->vs = (sk->protinfo.nr->vs + 1) % NR_MODULUS; |
|
/* |
* Requeue the original data frame. |
*/ |
skb_queue_tail(&sk->protinfo.nr->ack_queue, skb); |
|
} while (sk->protinfo.nr->vs != end && (skb = skb_dequeue(&sk->write_queue)) != NULL); |
|
sk->protinfo.nr->vl = sk->protinfo.nr->vr; |
sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING; |
|
if (!nr_t1timer_running(sk)) |
nr_start_t1timer(sk); |
} |
|
void nr_transmit_buffer(struct sock *sk, struct sk_buff *skb) |
{ |
unsigned char *dptr; |
|
/* |
* Add the protocol byte and network header. |
*/ |
dptr = skb_push(skb, NR_NETWORK_LEN); |
|
memcpy(dptr, &sk->protinfo.nr->source_addr, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] &= ~AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
|
memcpy(dptr, &sk->protinfo.nr->dest_addr, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] |= AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
|
*dptr++ = sysctl_netrom_network_ttl_initialiser; |
|
if (!nr_route_frame(skb, NULL)) { |
kfree_skb(skb); |
nr_disconnect(sk, ENETUNREACH); |
} |
} |
|
/* |
* The following routines are taken from page 170 of the 7th ARRL Computer |
* Networking Conference paper, as is the whole state machine. |
*/ |
|
void nr_establish_data_link(struct sock *sk) |
{ |
sk->protinfo.nr->condition = 0x00; |
sk->protinfo.nr->n2count = 0; |
|
nr_write_internal(sk, NR_CONNREQ); |
|
nr_stop_t2timer(sk); |
nr_stop_t4timer(sk); |
nr_stop_idletimer(sk); |
nr_start_t1timer(sk); |
} |
|
/* |
* Never send a NAK when we are CHOKEd. |
*/ |
void nr_enquiry_response(struct sock *sk) |
{ |
int frametype = NR_INFOACK; |
|
if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY) { |
frametype |= NR_CHOKE_FLAG; |
} else { |
if (skb_peek(&sk->protinfo.nr->reseq_queue) != NULL) |
frametype |= NR_NAK_FLAG; |
} |
|
nr_write_internal(sk, frametype); |
|
sk->protinfo.nr->vl = sk->protinfo.nr->vr; |
sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING; |
} |
|
void nr_check_iframes_acked(struct sock *sk, unsigned short nr) |
{ |
if (sk->protinfo.nr->vs == nr) { |
nr_frames_acked(sk, nr); |
nr_stop_t1timer(sk); |
sk->protinfo.nr->n2count = 0; |
} else { |
if (sk->protinfo.nr->va != nr) { |
nr_frames_acked(sk, nr); |
nr_start_t1timer(sk); |
} |
} |
} |
/nr_loopback.c
0,0 → 1,100
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 007 Tomi(OH2BNS) Created this file. |
* Small change in nr_loopback_queue(). |
* |
*/ |
|
#include <linux/types.h> |
#include <linux/socket.h> |
#include <linux/timer.h> |
#include <net/ax25.h> |
#include <linux/skbuff.h> |
#include <net/netrom.h> |
#include <linux/init.h> |
|
static struct sk_buff_head loopback_queue; |
static struct timer_list loopback_timer; |
|
static void nr_set_loopback_timer(void); |
|
void nr_loopback_init(void) |
{ |
skb_queue_head_init(&loopback_queue); |
|
init_timer(&loopback_timer); |
} |
|
static int nr_loopback_running(void) |
{ |
return timer_pending(&loopback_timer); |
} |
|
int nr_loopback_queue(struct sk_buff *skb) |
{ |
struct sk_buff *skbn; |
|
if ((skbn = alloc_skb(skb->len, GFP_ATOMIC)) != NULL) { |
memcpy(skb_put(skbn, skb->len), skb->data, skb->len); |
skbn->h.raw = skbn->data; |
|
skb_queue_tail(&loopback_queue, skbn); |
|
if (!nr_loopback_running()) |
nr_set_loopback_timer(); |
} |
|
kfree_skb(skb); |
return 1; |
} |
|
static void nr_loopback_timer(unsigned long); |
|
static void nr_set_loopback_timer(void) |
{ |
del_timer(&loopback_timer); |
|
loopback_timer.data = 0; |
loopback_timer.function = &nr_loopback_timer; |
loopback_timer.expires = jiffies + 10; |
|
add_timer(&loopback_timer); |
} |
|
static void nr_loopback_timer(unsigned long param) |
{ |
struct sk_buff *skb; |
ax25_address *nr_dest; |
struct net_device *dev; |
|
if ((skb = skb_dequeue(&loopback_queue)) != NULL) { |
nr_dest = (ax25_address *)(skb->data + 7); |
|
dev = nr_dev_get(nr_dest); |
|
if (dev == NULL || nr_rx_frame(skb, dev) == 0) |
kfree_skb(skb); |
|
if (dev != NULL) |
dev_put(dev); |
|
if (!skb_queue_empty(&loopback_queue) && !nr_loopback_running()) |
nr_set_loopback_timer(); |
} |
} |
|
void __exit nr_loopback_clear(void) |
{ |
del_timer(&loopback_timer); |
skb_queue_purge(&loopback_queue); |
} |
/nr_subr.c
0,0 → 1,288
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_subr.c |
* NET/ROM 003 Jonathan(G4KLX) Added G8BPQ NET/ROM extensions. |
* NET/ROM 007 Jonathan(G4KLX) New timer architecture. |
*/ |
|
#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 <net/ax25.h> |
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/skbuff.h> |
#include <net/sock.h> |
#include <asm/uaccess.h> |
#include <asm/system.h> |
#include <linux/fcntl.h> |
#include <linux/mm.h> |
#include <linux/interrupt.h> |
#include <net/netrom.h> |
|
/* |
* This routine purges all of the queues of frames. |
*/ |
void nr_clear_queues(struct sock *sk) |
{ |
skb_queue_purge(&sk->write_queue); |
skb_queue_purge(&sk->protinfo.nr->ack_queue); |
skb_queue_purge(&sk->protinfo.nr->reseq_queue); |
skb_queue_purge(&sk->protinfo.nr->frag_queue); |
} |
|
/* |
* This routine purges the input queue of those frames that have been |
* acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the |
* SDL diagram. |
*/ |
void nr_frames_acked(struct sock *sk, unsigned short nr) |
{ |
struct sk_buff *skb; |
|
/* |
* Remove all the ack-ed frames from the ack queue. |
*/ |
if (sk->protinfo.nr->va != nr) { |
while (skb_peek(&sk->protinfo.nr->ack_queue) != NULL && sk->protinfo.nr->va != nr) { |
skb = skb_dequeue(&sk->protinfo.nr->ack_queue); |
kfree_skb(skb); |
sk->protinfo.nr->va = (sk->protinfo.nr->va + 1) % NR_MODULUS; |
} |
} |
} |
|
/* |
* Requeue all the un-ack-ed frames on the output queue to be picked |
* up by nr_kick called from the timer. This arrangement handles the |
* possibility of an empty output queue. |
*/ |
void nr_requeue_frames(struct sock *sk) |
{ |
struct sk_buff *skb, *skb_prev = NULL; |
|
while ((skb = skb_dequeue(&sk->protinfo.nr->ack_queue)) != NULL) { |
if (skb_prev == NULL) |
skb_queue_head(&sk->write_queue, skb); |
else |
skb_append(skb_prev, skb); |
skb_prev = skb; |
} |
} |
|
/* |
* Validate that the value of nr is between va and vs. Return true or |
* false for testing. |
*/ |
int nr_validate_nr(struct sock *sk, unsigned short nr) |
{ |
unsigned short vc = sk->protinfo.nr->va; |
|
while (vc != sk->protinfo.nr->vs) { |
if (nr == vc) return 1; |
vc = (vc + 1) % NR_MODULUS; |
} |
|
if (nr == sk->protinfo.nr->vs) return 1; |
|
return 0; |
} |
|
/* |
* Check that ns is within the receive window. |
*/ |
int nr_in_rx_window(struct sock *sk, unsigned short ns) |
{ |
unsigned short vc = sk->protinfo.nr->vr; |
unsigned short vt = (sk->protinfo.nr->vl + sk->protinfo.nr->window) % NR_MODULUS; |
|
while (vc != vt) { |
if (ns == vc) return 1; |
vc = (vc + 1) % NR_MODULUS; |
} |
|
return 0; |
} |
|
/* |
* This routine is called when the HDLC layer internally generates a |
* control frame. |
*/ |
void nr_write_internal(struct sock *sk, int frametype) |
{ |
struct sk_buff *skb; |
unsigned char *dptr; |
int len, timeout; |
|
len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN; |
|
switch (frametype & 0x0F) { |
case NR_CONNREQ: |
len += 17; |
break; |
case NR_CONNACK: |
len += (sk->protinfo.nr->bpqext) ? 2 : 1; |
break; |
case NR_DISCREQ: |
case NR_DISCACK: |
case NR_INFOACK: |
break; |
default: |
printk(KERN_ERR "NET/ROM: nr_write_internal - invalid frame type %d\n", frametype); |
return; |
} |
|
if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL) |
return; |
|
/* |
* Space for AX.25 and NET/ROM network header |
*/ |
skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN); |
|
dptr = skb_put(skb, skb_tailroom(skb)); |
|
switch (frametype & 0x0F) { |
|
case NR_CONNREQ: |
timeout = sk->protinfo.nr->t1 / HZ; |
*dptr++ = sk->protinfo.nr->my_index; |
*dptr++ = sk->protinfo.nr->my_id; |
*dptr++ = 0; |
*dptr++ = 0; |
*dptr++ = frametype; |
*dptr++ = sk->protinfo.nr->window; |
memcpy(dptr, &sk->protinfo.nr->user_addr, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] &= ~AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
memcpy(dptr, &sk->protinfo.nr->source_addr, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] &= ~AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
*dptr++ = timeout % 256; |
*dptr++ = timeout / 256; |
break; |
|
case NR_CONNACK: |
*dptr++ = sk->protinfo.nr->your_index; |
*dptr++ = sk->protinfo.nr->your_id; |
*dptr++ = sk->protinfo.nr->my_index; |
*dptr++ = sk->protinfo.nr->my_id; |
*dptr++ = frametype; |
*dptr++ = sk->protinfo.nr->window; |
if (sk->protinfo.nr->bpqext) *dptr++ = sysctl_netrom_network_ttl_initialiser; |
break; |
|
case NR_DISCREQ: |
case NR_DISCACK: |
*dptr++ = sk->protinfo.nr->your_index; |
*dptr++ = sk->protinfo.nr->your_id; |
*dptr++ = 0; |
*dptr++ = 0; |
*dptr++ = frametype; |
break; |
|
case NR_INFOACK: |
*dptr++ = sk->protinfo.nr->your_index; |
*dptr++ = sk->protinfo.nr->your_id; |
*dptr++ = 0; |
*dptr++ = sk->protinfo.nr->vr; |
*dptr++ = frametype; |
break; |
} |
|
nr_transmit_buffer(sk, skb); |
} |
|
/* |
* This routine is called when a Connect Acknowledge with the Choke Flag |
* set is needed to refuse a connection. |
*/ |
void nr_transmit_refusal(struct sk_buff *skb, int mine) |
{ |
struct sk_buff *skbn; |
unsigned char *dptr; |
int len; |
|
len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN + 1; |
|
if ((skbn = alloc_skb(len, GFP_ATOMIC)) == NULL) |
return; |
|
skb_reserve(skbn, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN); |
|
dptr = skb_put(skbn, NR_NETWORK_LEN + NR_TRANSPORT_LEN); |
|
memcpy(dptr, skb->data + 7, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] &= ~AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
|
memcpy(dptr, skb->data + 0, AX25_ADDR_LEN); |
dptr[6] &= ~AX25_CBIT; |
dptr[6] |= AX25_EBIT; |
dptr[6] |= AX25_SSSID_SPARE; |
dptr += AX25_ADDR_LEN; |
|
*dptr++ = sysctl_netrom_network_ttl_initialiser; |
|
if (mine) { |
*dptr++ = 0; |
*dptr++ = 0; |
*dptr++ = skb->data[15]; |
*dptr++ = skb->data[16]; |
} else { |
*dptr++ = skb->data[15]; |
*dptr++ = skb->data[16]; |
*dptr++ = 0; |
*dptr++ = 0; |
} |
|
*dptr++ = NR_CONNACK | NR_CHOKE_FLAG; |
*dptr++ = 0; |
|
if (!nr_route_frame(skbn, NULL)) |
kfree_skb(skbn); |
} |
|
void nr_disconnect(struct sock *sk, int reason) |
{ |
nr_stop_t1timer(sk); |
nr_stop_t2timer(sk); |
nr_stop_t4timer(sk); |
nr_stop_idletimer(sk); |
|
nr_clear_queues(sk); |
|
sk->protinfo.nr->state = NR_STATE_0; |
|
sk->state = TCP_CLOSE; |
sk->err = reason; |
sk->shutdown |= SEND_SHUTDOWN; |
|
if (!sk->dead) |
sk->state_change(sk); |
|
sk->dead = 1; |
} |
/af_netrom.c
0,0 → 1,1384
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from the AX25 code. |
* NET/ROM 002 Darryl(G7LED) Fixes and address enhancement. |
* Jonathan(G4KLX) Complete bind re-think. |
* Alan(GW4PTS) Trivial tweaks into new format. |
* NET/ROM 003 Jonathan(G4KLX) Added G8BPQ extensions. |
* Added NET/ROM routing ioctl. |
* Darryl(G7LED) Fix autobinding (on connect). |
* Fixed nr_release(), set TCP_CLOSE, wakeup app |
* context, THEN make the sock dead. |
* Circuit ID check before allocating it on |
* a connection. |
* Alan(GW4PTS) sendmsg/recvmsg only. Fixed connect clear bug |
* inherited from AX.25 |
* NET/ROM 004 Jonathan(G4KLX) Converted to module. |
* NET/ROM 005 Jonathan(G4KLX) Linux 2.1 |
* Alan(GW4PTS) Started POSIXisms |
* NET/ROM 006 Alan(GW4PTS) Brought in line with the ANK changes |
* Jonathan(G4KLX) Removed hdrincl. |
* NET/ROM 007 Jonathan(G4KLX) New timer architecture. |
* Impmented Idle timer. |
* Arnaldo C. Melo s/suser/capable/, micro cleanups |
* Jeroen(PE1RXQ) Use sock_orphan() on release. |
* Tomi(OH2BNS) Better frame type checking. |
* Device refcnt 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/stat.h> |
#include <net/ax25.h> |
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/if_arp.h> |
#include <linux/skbuff.h> |
#include <net/sock.h> |
#include <asm/uaccess.h> |
#include <asm/system.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 <net/netrom.h> |
#include <linux/proc_fs.h> |
#include <net/ip.h> |
#include <net/arp.h> |
#include <linux/init.h> |
|
int nr_ndevs = 4; |
|
int sysctl_netrom_default_path_quality = NR_DEFAULT_QUAL; |
int sysctl_netrom_obsolescence_count_initialiser = NR_DEFAULT_OBS; |
int sysctl_netrom_network_ttl_initialiser = NR_DEFAULT_TTL; |
int sysctl_netrom_transport_timeout = NR_DEFAULT_T1; |
int sysctl_netrom_transport_maximum_tries = NR_DEFAULT_N2; |
int sysctl_netrom_transport_acknowledge_delay = NR_DEFAULT_T2; |
int sysctl_netrom_transport_busy_delay = NR_DEFAULT_T4; |
int sysctl_netrom_transport_requested_window_size = NR_DEFAULT_WINDOW; |
int sysctl_netrom_transport_no_activity_timeout = NR_DEFAULT_IDLE; |
int sysctl_netrom_routing_control = NR_DEFAULT_ROUTING; |
int sysctl_netrom_link_fails_count = NR_DEFAULT_FAILS; |
|
static unsigned short circuit = 0x101; |
|
static struct sock *volatile nr_list; |
|
static struct proto_ops nr_proto_ops; |
|
static void nr_free_sock(struct sock *sk) |
{ |
sk_free(sk); |
|
MOD_DEC_USE_COUNT; |
} |
|
static struct sock *nr_alloc_sock(void) |
{ |
struct sock *sk; |
nr_cb *nr; |
|
if ((sk = sk_alloc(PF_NETROM, GFP_ATOMIC, 1)) == NULL) |
return NULL; |
|
if ((nr = kmalloc(sizeof(*nr), GFP_ATOMIC)) == NULL) { |
sk_free(sk); |
return NULL; |
} |
|
MOD_INC_USE_COUNT; |
|
memset(nr, 0x00, sizeof(*nr)); |
|
sk->protinfo.nr = nr; |
nr->sk = sk; |
|
return sk; |
} |
|
/* |
* Socket removal during an interrupt is now safe. |
*/ |
static void nr_remove_socket(struct sock *sk) |
{ |
struct sock *s; |
unsigned long flags; |
|
save_flags(flags); cli(); |
|
if ((s = nr_list) == sk) { |
nr_list = s->next; |
dev_put(sk->protinfo.nr->device); |
restore_flags(flags); |
return; |
} |
|
while (s != NULL && s->next != NULL) { |
if (s->next == sk) { |
s->next = sk->next; |
dev_put(sk->protinfo.nr->device); |
restore_flags(flags); |
return; |
} |
|
s = s->next; |
} |
|
restore_flags(flags); |
} |
|
/* |
* Kill all bound sockets on a dropped device. |
*/ |
static void nr_kill_by_device(struct net_device *dev) |
{ |
struct sock *s; |
|
for (s = nr_list; s != NULL; s = s->next) { |
if (s->protinfo.nr->device == dev) |
nr_disconnect(s, ENETUNREACH); |
} |
} |
|
/* |
* Handle device status changes. |
*/ |
static int nr_device_event(struct notifier_block *this, unsigned long event, void *ptr) |
{ |
struct net_device *dev = (struct net_device *)ptr; |
|
if (event != NETDEV_DOWN) |
return NOTIFY_DONE; |
|
nr_kill_by_device(dev); |
nr_rt_device_down(dev); |
|
return NOTIFY_DONE; |
} |
|
/* |
* Add a socket to the bound sockets list. |
*/ |
static void nr_insert_socket(struct sock *sk) |
{ |
unsigned long flags; |
|
save_flags(flags); cli(); |
|
sk->next = nr_list; |
nr_list = sk; |
|
restore_flags(flags); |
} |
|
/* |
* Find a socket that wants to accept the Connect Request we just |
* received. |
*/ |
static struct sock *nr_find_listener(ax25_address *addr) |
{ |
unsigned long flags; |
struct sock *s; |
|
save_flags(flags); |
cli(); |
|
for (s = nr_list; s != NULL; s = s->next) { |
if (ax25cmp(&s->protinfo.nr->source_addr, addr) == 0 && s->state == TCP_LISTEN) { |
restore_flags(flags); |
return s; |
} |
} |
|
restore_flags(flags); |
return NULL; |
} |
|
/* |
* Find a connected NET/ROM socket given my circuit IDs. |
*/ |
static struct sock *nr_find_socket(unsigned char index, unsigned char id) |
{ |
struct sock *s; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
for (s = nr_list; s != NULL; s = s->next) { |
if (s->protinfo.nr->my_index == index && s->protinfo.nr->my_id == id) { |
restore_flags(flags); |
return s; |
} |
} |
|
restore_flags(flags); |
|
return NULL; |
} |
|
/* |
* Find a connected NET/ROM socket given their circuit IDs. |
*/ |
static struct sock *nr_find_peer(unsigned char index, unsigned char id, ax25_address *dest) |
{ |
struct sock *s; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
for (s = nr_list; s != NULL; s = s->next) { |
if (s->protinfo.nr->your_index == index && s->protinfo.nr->your_id == id && ax25cmp(&s->protinfo.nr->dest_addr, dest) == 0) { |
restore_flags(flags); |
return s; |
} |
} |
|
restore_flags(flags); |
|
return NULL; |
} |
|
/* |
* Find next free circuit ID. |
*/ |
static unsigned short nr_find_next_circuit(void) |
{ |
unsigned short id = circuit; |
unsigned char i, j; |
|
for (;;) { |
i = id / 256; |
j = id % 256; |
|
if (i != 0 && j != 0) |
if (nr_find_socket(i, j) == NULL) |
break; |
|
id++; |
} |
|
return id; |
} |
|
/* |
* Deferred destroy. |
*/ |
void nr_destroy_socket(struct sock *); |
|
/* |
* Handler for deferred kills. |
*/ |
static void nr_destroy_timer(unsigned long data) |
{ |
nr_destroy_socket((struct sock *)data); |
} |
|
/* |
* This is called from user mode and the timers. Thus it protects itself against |
* interrupt users but doesn't worry about being called during work. |
* Once it is removed from the queue no interrupt or bottom half will |
* touch it and we are (fairly 8-) ) safe. |
*/ |
void nr_destroy_socket(struct sock *sk) /* Not static as it's used by the timer */ |
{ |
struct sk_buff *skb; |
unsigned long flags; |
|
save_flags(flags); cli(); |
|
nr_stop_heartbeat(sk); |
nr_stop_t1timer(sk); |
nr_stop_t2timer(sk); |
nr_stop_t4timer(sk); |
nr_stop_idletimer(sk); |
|
nr_remove_socket(sk); |
nr_clear_queues(sk); /* Flush the queues */ |
|
while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) { |
if (skb->sk != sk) { /* A pending connection */ |
skb->sk->dead = 1; /* Queue the unaccepted socket for death */ |
nr_start_heartbeat(skb->sk); |
skb->sk->protinfo.nr->state = NR_STATE_0; |
} |
|
kfree_skb(skb); |
} |
|
if (atomic_read(&sk->wmem_alloc) != 0 || atomic_read(&sk->rmem_alloc) != 0) { |
/* Defer: outstanding buffers */ |
init_timer(&sk->timer); |
sk->timer.expires = jiffies + 10 * HZ; |
sk->timer.function = nr_destroy_timer; |
sk->timer.data = (unsigned long)sk; |
add_timer(&sk->timer); |
} else { |
nr_free_sock(sk); |
} |
|
restore_flags(flags); |
} |
|
/* |
* Handling for system calls applied via the various interfaces to a |
* NET/ROM socket object. |
*/ |
|
static int nr_setsockopt(struct socket *sock, int level, int optname, |
char *optval, int optlen) |
{ |
struct sock *sk = sock->sk; |
int opt; |
|
if (level != SOL_NETROM) |
return -ENOPROTOOPT; |
|
if (optlen < sizeof(int)) |
return -EINVAL; |
|
if (get_user(opt, (int *)optval)) |
return -EFAULT; |
|
switch (optname) { |
case NETROM_T1: |
if (opt < 1) |
return -EINVAL; |
sk->protinfo.nr->t1 = opt * HZ; |
return 0; |
|
case NETROM_T2: |
if (opt < 1) |
return -EINVAL; |
sk->protinfo.nr->t2 = opt * HZ; |
return 0; |
|
case NETROM_N2: |
if (opt < 1 || opt > 31) |
return -EINVAL; |
sk->protinfo.nr->n2 = opt; |
return 0; |
|
case NETROM_T4: |
if (opt < 1) |
return -EINVAL; |
sk->protinfo.nr->t4 = opt * HZ; |
return 0; |
|
case NETROM_IDLE: |
if (opt < 0) |
return -EINVAL; |
sk->protinfo.nr->idle = opt * 60 * HZ; |
return 0; |
|
default: |
return -ENOPROTOOPT; |
} |
} |
|
static int nr_getsockopt(struct socket *sock, int level, int optname, |
char *optval, int *optlen) |
{ |
struct sock *sk = sock->sk; |
int val = 0; |
int len; |
|
if (level != SOL_NETROM) |
return -ENOPROTOOPT; |
|
if (get_user(len, optlen)) |
return -EFAULT; |
|
if (len < 0) |
return -EINVAL; |
|
switch (optname) { |
case NETROM_T1: |
val = sk->protinfo.nr->t1 / HZ; |
break; |
|
case NETROM_T2: |
val = sk->protinfo.nr->t2 / HZ; |
break; |
|
case NETROM_N2: |
val = sk->protinfo.nr->n2; |
break; |
|
case NETROM_T4: |
val = sk->protinfo.nr->t4 / HZ; |
break; |
|
case NETROM_IDLE: |
val = sk->protinfo.nr->idle / (60 * HZ); |
break; |
|
default: |
return -ENOPROTOOPT; |
} |
|
len = min_t(unsigned int, len, sizeof(int)); |
|
if (put_user(len, optlen)) |
return -EFAULT; |
|
return copy_to_user(optval, &val, len) ? -EFAULT : 0; |
} |
|
static int nr_listen(struct socket *sock, int backlog) |
{ |
struct sock *sk = sock->sk; |
|
if (sk->state != TCP_LISTEN) { |
memset(&sk->protinfo.nr->user_addr, '\0', AX25_ADDR_LEN); |
sk->max_ack_backlog = backlog; |
sk->state = TCP_LISTEN; |
return 0; |
} |
|
return -EOPNOTSUPP; |
} |
|
static int nr_create(struct socket *sock, int protocol) |
{ |
struct sock *sk; |
nr_cb *nr; |
|
if (sock->type != SOCK_SEQPACKET || protocol != 0) |
return -ESOCKTNOSUPPORT; |
|
if ((sk = nr_alloc_sock()) == NULL) |
return -ENOMEM; |
|
nr = sk->protinfo.nr; |
|
sock_init_data(sock, sk); |
|
sock->ops = &nr_proto_ops; |
sk->protocol = protocol; |
|
skb_queue_head_init(&nr->ack_queue); |
skb_queue_head_init(&nr->reseq_queue); |
skb_queue_head_init(&nr->frag_queue); |
|
init_timer(&nr->t1timer); |
init_timer(&nr->t2timer); |
init_timer(&nr->t4timer); |
init_timer(&nr->idletimer); |
|
nr->t1 = sysctl_netrom_transport_timeout; |
nr->t2 = sysctl_netrom_transport_acknowledge_delay; |
nr->n2 = sysctl_netrom_transport_maximum_tries; |
nr->t4 = sysctl_netrom_transport_busy_delay; |
nr->idle = sysctl_netrom_transport_no_activity_timeout; |
nr->window = sysctl_netrom_transport_requested_window_size; |
|
nr->bpqext = 1; |
nr->state = NR_STATE_0; |
|
return 0; |
} |
|
static struct sock *nr_make_new(struct sock *osk) |
{ |
struct sock *sk; |
nr_cb *nr; |
|
if (osk->type != SOCK_SEQPACKET) |
return NULL; |
|
if ((sk = nr_alloc_sock()) == NULL) |
return NULL; |
|
nr = sk->protinfo.nr; |
|
sock_init_data(NULL, sk); |
|
sk->type = osk->type; |
sk->socket = osk->socket; |
sk->priority = osk->priority; |
sk->protocol = osk->protocol; |
sk->rcvbuf = osk->rcvbuf; |
sk->sndbuf = osk->sndbuf; |
sk->debug = osk->debug; |
sk->state = TCP_ESTABLISHED; |
sk->sleep = osk->sleep; |
sk->zapped = osk->zapped; |
|
skb_queue_head_init(&nr->ack_queue); |
skb_queue_head_init(&nr->reseq_queue); |
skb_queue_head_init(&nr->frag_queue); |
|
init_timer(&nr->t1timer); |
init_timer(&nr->t2timer); |
init_timer(&nr->t4timer); |
init_timer(&nr->idletimer); |
|
nr->t1 = osk->protinfo.nr->t1; |
nr->t2 = osk->protinfo.nr->t2; |
nr->n2 = osk->protinfo.nr->n2; |
nr->t4 = osk->protinfo.nr->t4; |
nr->idle = osk->protinfo.nr->idle; |
nr->window = osk->protinfo.nr->window; |
|
nr->device = osk->protinfo.nr->device; |
nr->bpqext = osk->protinfo.nr->bpqext; |
|
return sk; |
} |
|
static int nr_release(struct socket *sock) |
{ |
struct sock *sk = sock->sk; |
|
if (sk == NULL) return 0; |
|
switch (sk->protinfo.nr->state) { |
|
case NR_STATE_0: |
case NR_STATE_1: |
case NR_STATE_2: |
nr_disconnect(sk, 0); |
nr_destroy_socket(sk); |
break; |
|
case NR_STATE_3: |
nr_clear_queues(sk); |
sk->protinfo.nr->n2count = 0; |
nr_write_internal(sk, NR_DISCREQ); |
nr_start_t1timer(sk); |
nr_stop_t2timer(sk); |
nr_stop_t4timer(sk); |
nr_stop_idletimer(sk); |
sk->protinfo.nr->state = NR_STATE_2; |
sk->state = TCP_CLOSE; |
sk->shutdown |= SEND_SHUTDOWN; |
sk->state_change(sk); |
sock_orphan(sk); |
sk->destroy = 1; |
break; |
|
default: |
sk->socket = NULL; |
break; |
} |
|
sock->sk = NULL; |
|
return 0; |
} |
|
static int nr_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) |
{ |
struct sock *sk = sock->sk; |
struct full_sockaddr_ax25 *addr = (struct full_sockaddr_ax25 *)uaddr; |
struct net_device *dev; |
ax25_address *user, *source; |
|
if (sk->zapped == 0) |
return -EINVAL; |
|
if (addr_len < sizeof(struct sockaddr_ax25) || addr_len > sizeof(struct |
full_sockaddr_ax25)) |
return -EINVAL; |
|
if (addr_len < (addr->fsa_ax25.sax25_ndigis * sizeof(ax25_address) + sizeof(struct sockaddr_ax25))) |
return -EINVAL; |
|
if (addr->fsa_ax25.sax25_family != AF_NETROM) |
return -EINVAL; |
|
if ((dev = nr_dev_get(&addr->fsa_ax25.sax25_call)) == NULL) { |
SOCK_DEBUG(sk, "NET/ROM: bind failed: invalid node callsign\n"); |
return -EADDRNOTAVAIL; |
} |
|
/* |
* Only the super user can set an arbitrary user callsign. |
*/ |
if (addr->fsa_ax25.sax25_ndigis == 1) { |
if (!capable(CAP_NET_BIND_SERVICE)) { |
dev_put(dev); |
return -EACCES; |
} |
sk->protinfo.nr->user_addr = addr->fsa_digipeater[0]; |
sk->protinfo.nr->source_addr = addr->fsa_ax25.sax25_call; |
} else { |
source = &addr->fsa_ax25.sax25_call; |
|
if ((user = ax25_findbyuid(current->euid)) == NULL) { |
if (ax25_uid_policy && !capable(CAP_NET_BIND_SERVICE)) { |
dev_put(dev); |
return -EPERM; |
} |
user = source; |
} |
|
sk->protinfo.nr->user_addr = *user; |
sk->protinfo.nr->source_addr = *source; |
} |
|
sk->protinfo.nr->device = dev; |
nr_insert_socket(sk); |
|
sk->zapped = 0; |
SOCK_DEBUG(sk, "NET/ROM: socket is bound\n"); |
return 0; |
} |
|
static int nr_connect(struct socket *sock, struct sockaddr *uaddr, |
int addr_len, int flags) |
{ |
struct sock *sk = sock->sk; |
struct sockaddr_ax25 *addr = (struct sockaddr_ax25 *)uaddr; |
ax25_address *user, *source = NULL; |
struct net_device *dev; |
|
if (sk->state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) { |
sock->state = SS_CONNECTED; |
return 0; /* Connect completed during a ERESTARTSYS event */ |
} |
|
if (sk->state == TCP_CLOSE && sock->state == SS_CONNECTING) { |
sock->state = SS_UNCONNECTED; |
return -ECONNREFUSED; |
} |
|
if (sk->state == TCP_ESTABLISHED) |
return -EISCONN; /* No reconnect on a seqpacket socket */ |
|
sk->state = TCP_CLOSE; |
sock->state = SS_UNCONNECTED; |
|
if (addr_len != sizeof(struct sockaddr_ax25) && addr_len != sizeof(struct full_sockaddr_ax25)) |
return -EINVAL; |
|
if (addr->sax25_family != AF_NETROM) |
return -EINVAL; |
|
if (sk->zapped) { /* Must bind first - autobinding in this may or may not work */ |
sk->zapped = 0; |
|
if ((dev = nr_dev_first()) == NULL) |
return -ENETUNREACH; |
|
source = (ax25_address *)dev->dev_addr; |
|
if ((user = ax25_findbyuid(current->euid)) == NULL) { |
if (ax25_uid_policy && !capable(CAP_NET_ADMIN)) { |
dev_put(dev); |
return -EPERM; |
} |
user = source; |
} |
|
sk->protinfo.nr->user_addr = *user; |
sk->protinfo.nr->source_addr = *source; |
sk->protinfo.nr->device = dev; |
|
nr_insert_socket(sk); /* Finish the bind */ |
} |
|
sk->protinfo.nr->dest_addr = addr->sax25_call; |
|
circuit = nr_find_next_circuit(); |
|
sk->protinfo.nr->my_index = circuit / 256; |
sk->protinfo.nr->my_id = circuit % 256; |
|
circuit++; |
|
/* Move to connecting socket, start sending Connect Requests */ |
sock->state = SS_CONNECTING; |
sk->state = TCP_SYN_SENT; |
|
nr_establish_data_link(sk); |
|
sk->protinfo.nr->state = NR_STATE_1; |
|
nr_start_heartbeat(sk); |
|
/* Now the loop */ |
if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK)) |
return -EINPROGRESS; |
|
cli(); /* To avoid races on the sleep */ |
|
/* |
* A Connect Ack with Choke or timeout or failed routing will go to closed. |
*/ |
while (sk->state == TCP_SYN_SENT) { |
interruptible_sleep_on(sk->sleep); |
if (signal_pending(current)) { |
sti(); |
return -ERESTARTSYS; |
} |
} |
|
if (sk->state != TCP_ESTABLISHED) { |
sti(); |
sock->state = SS_UNCONNECTED; |
return sock_error(sk); /* Always set at this point */ |
} |
|
sock->state = SS_CONNECTED; |
|
sti(); |
|
return 0; |
} |
|
static int nr_accept(struct socket *sock, struct socket *newsock, int flags) |
{ |
struct sock *sk; |
struct sock *newsk; |
struct sk_buff *skb; |
|
if ((sk = sock->sk) == NULL) |
return -EINVAL; |
|
if (sk->type != SOCK_SEQPACKET) |
return -EOPNOTSUPP; |
|
if (sk->state != TCP_LISTEN) |
return -EINVAL; |
|
/* |
* The write queue this time is holding sockets ready to use |
* hooked into the SABM we saved |
*/ |
do { |
cli(); |
if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) { |
if (flags & O_NONBLOCK) { |
sti(); |
return -EWOULDBLOCK; |
} |
interruptible_sleep_on(sk->sleep); |
if (signal_pending(current)) { |
sti(); |
return -ERESTARTSYS; |
} |
} |
} while (skb == NULL); |
|
newsk = skb->sk; |
newsk->pair = NULL; |
newsk->socket = newsock; |
newsk->sleep = &newsock->wait; |
sti(); |
|
/* Now attach up the new socket */ |
kfree_skb(skb); |
sk->ack_backlog--; |
newsock->sk = newsk; |
|
return 0; |
} |
|
static int nr_getname(struct socket *sock, struct sockaddr *uaddr, |
int *uaddr_len, int peer) |
{ |
struct full_sockaddr_ax25 *sax = (struct full_sockaddr_ax25 *)uaddr; |
struct sock *sk = sock->sk; |
|
if (peer != 0) { |
if (sk->state != TCP_ESTABLISHED) |
return -ENOTCONN; |
sax->fsa_ax25.sax25_family = AF_NETROM; |
sax->fsa_ax25.sax25_ndigis = 1; |
sax->fsa_ax25.sax25_call = sk->protinfo.nr->user_addr; |
sax->fsa_digipeater[0] = sk->protinfo.nr->dest_addr; |
*uaddr_len = sizeof(struct full_sockaddr_ax25); |
} else { |
sax->fsa_ax25.sax25_family = AF_NETROM; |
sax->fsa_ax25.sax25_ndigis = 0; |
sax->fsa_ax25.sax25_call = sk->protinfo.nr->source_addr; |
*uaddr_len = sizeof(struct sockaddr_ax25); |
} |
|
return 0; |
} |
|
int nr_rx_frame(struct sk_buff *skb, struct net_device *dev) |
{ |
struct sock *sk; |
struct sock *make; |
ax25_address *src, *dest, *user; |
unsigned short circuit_index, circuit_id; |
unsigned short peer_circuit_index, peer_circuit_id; |
unsigned short frametype, flags, window, timeout; |
|
skb->sk = NULL; /* Initially we don't know who it's for */ |
|
/* |
* skb->data points to the netrom frame start |
*/ |
|
src = (ax25_address *)(skb->data + 0); |
dest = (ax25_address *)(skb->data + 7); |
|
circuit_index = skb->data[15]; |
circuit_id = skb->data[16]; |
peer_circuit_index = skb->data[17]; |
peer_circuit_id = skb->data[18]; |
frametype = skb->data[19] & 0x0F; |
flags = skb->data[19] & 0xF0; |
|
switch (frametype) { |
case NR_PROTOEXT: |
#ifdef CONFIG_INET |
/* |
* Check for an incoming IP over NET/ROM frame. |
*/ |
if (circuit_index == NR_PROTO_IP && circuit_id == NR_PROTO_IP) { |
skb_pull(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN); |
skb->h.raw = skb->data; |
|
return nr_rx_ip(skb, dev); |
} |
#endif |
return 0; |
|
case NR_CONNREQ: |
case NR_CONNACK: |
case NR_DISCREQ: |
case NR_DISCACK: |
case NR_INFO: |
case NR_INFOACK: |
/* |
* These frame types we understand. |
*/ |
break; |
|
default: |
/* |
* Everything else is ignored. |
*/ |
return 0; |
} |
|
/* |
* Find an existing socket connection, based on circuit ID, if it's |
* a Connect Request base it on their circuit ID. |
* |
* Circuit ID 0/0 is not valid but it could still be a "reset" for a |
* circuit that no longer exists at the other end ... |
*/ |
|
sk = NULL; |
|
if (circuit_index == 0 && circuit_id == 0) { |
if (frametype == NR_CONNACK && flags == NR_CHOKE_FLAG) |
sk = nr_find_peer(peer_circuit_index, peer_circuit_id, src); |
} else { |
if (frametype == NR_CONNREQ) |
sk = nr_find_peer(circuit_index, circuit_id, src); |
else |
sk = nr_find_socket(circuit_index, circuit_id); |
} |
|
if (sk != NULL) { |
skb->h.raw = skb->data; |
|
if (frametype == NR_CONNACK && skb->len == 22) |
sk->protinfo.nr->bpqext = 1; |
else |
sk->protinfo.nr->bpqext = 0; |
|
return nr_process_rx_frame(sk, skb); |
} |
|
/* |
* Now it should be a CONNREQ. |
*/ |
if (frametype != NR_CONNREQ) { |
/* |
* Here it would be nice to be able to send a reset but |
* NET/ROM doesn't have one. The following hack would |
* have been a way to extend the protocol but apparently |
* it kills BPQ boxes... :-( |
*/ |
#if 0 |
/* |
* Never reply to a CONNACK/CHOKE. |
*/ |
if (frametype != NR_CONNACK || flags != NR_CHOKE_FLAG) |
nr_transmit_refusal(skb, 1); |
#endif |
return 0; |
} |
|
sk = nr_find_listener(dest); |
|
user = (ax25_address *)(skb->data + 21); |
|
if (sk == NULL || sk->ack_backlog == sk->max_ack_backlog || (make = nr_make_new(sk)) == NULL) { |
nr_transmit_refusal(skb, 0); |
return 0; |
} |
|
window = skb->data[20]; |
|
skb->sk = make; |
make->state = TCP_ESTABLISHED; |
|
/* Fill in his circuit details */ |
make->protinfo.nr->source_addr = *dest; |
make->protinfo.nr->dest_addr = *src; |
make->protinfo.nr->user_addr = *user; |
|
make->protinfo.nr->your_index = circuit_index; |
make->protinfo.nr->your_id = circuit_id; |
|
circuit = nr_find_next_circuit(); |
|
make->protinfo.nr->my_index = circuit / 256; |
make->protinfo.nr->my_id = circuit % 256; |
|
circuit++; |
|
/* Window negotiation */ |
if (window < make->protinfo.nr->window) |
make->protinfo.nr->window = window; |
|
/* L4 timeout negotiation */ |
if (skb->len == 37) { |
timeout = skb->data[36] * 256 + skb->data[35]; |
if (timeout * HZ < make->protinfo.nr->t1) |
make->protinfo.nr->t1 = timeout * HZ; |
make->protinfo.nr->bpqext = 1; |
} else { |
make->protinfo.nr->bpqext = 0; |
} |
|
nr_write_internal(make, NR_CONNACK); |
|
make->protinfo.nr->condition = 0x00; |
make->protinfo.nr->vs = 0; |
make->protinfo.nr->va = 0; |
make->protinfo.nr->vr = 0; |
make->protinfo.nr->vl = 0; |
make->protinfo.nr->state = NR_STATE_3; |
sk->ack_backlog++; |
make->pair = sk; |
|
dev_hold(make->protinfo.nr->device); |
|
nr_insert_socket(make); |
|
skb_queue_head(&sk->receive_queue, skb); |
|
nr_start_heartbeat(make); |
nr_start_idletimer(make); |
|
if (!sk->dead) |
sk->data_ready(sk, skb->len); |
|
return 1; |
} |
|
static int nr_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
struct sockaddr_ax25 *usax = (struct sockaddr_ax25 *)msg->msg_name; |
int err; |
struct sockaddr_ax25 sax; |
struct sk_buff *skb; |
unsigned char *asmptr; |
int size; |
|
if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR)) |
return -EINVAL; |
|
if (sk->zapped) |
return -EADDRNOTAVAIL; |
|
if (sk->shutdown & SEND_SHUTDOWN) { |
send_sig(SIGPIPE, current, 0); |
return -EPIPE; |
} |
|
if (sk->protinfo.nr->device == NULL) |
return -ENETUNREACH; |
|
if (usax) { |
if (msg->msg_namelen < sizeof(sax)) |
return -EINVAL; |
sax = *usax; |
if (ax25cmp(&sk->protinfo.nr->dest_addr, &sax.sax25_call) != 0) |
return -EISCONN; |
if (sax.sax25_family != AF_NETROM) |
return -EINVAL; |
} else { |
if (sk->state != TCP_ESTABLISHED) |
return -ENOTCONN; |
sax.sax25_family = AF_NETROM; |
sax.sax25_call = sk->protinfo.nr->dest_addr; |
} |
|
SOCK_DEBUG(sk, "NET/ROM: sendto: Addresses built.\n"); |
|
/* Build a packet */ |
SOCK_DEBUG(sk, "NET/ROM: sendto: building packet.\n"); |
size = len + AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN; |
|
if ((skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err)) == NULL) |
return err; |
|
skb_reserve(skb, size - len); |
|
/* |
* Push down the NET/ROM header |
*/ |
|
asmptr = skb_push(skb, NR_TRANSPORT_LEN); |
SOCK_DEBUG(sk, "Building NET/ROM Header.\n"); |
|
/* Build a NET/ROM Transport header */ |
|
*asmptr++ = sk->protinfo.nr->your_index; |
*asmptr++ = sk->protinfo.nr->your_id; |
*asmptr++ = 0; /* To be filled in later */ |
*asmptr++ = 0; /* Ditto */ |
*asmptr++ = NR_INFO; |
SOCK_DEBUG(sk, "Built header.\n"); |
|
/* |
* Put the data on the end |
*/ |
|
skb->h.raw = skb_put(skb, len); |
|
asmptr = skb->h.raw; |
SOCK_DEBUG(sk, "NET/ROM: Appending user data\n"); |
|
/* User data follows immediately after the NET/ROM transport header */ |
memcpy_fromiovec(asmptr, msg->msg_iov, len); |
SOCK_DEBUG(sk, "NET/ROM: Transmitting buffer\n"); |
|
if (sk->state != TCP_ESTABLISHED) { |
kfree_skb(skb); |
return -ENOTCONN; |
} |
|
nr_output(sk, skb); /* Shove it onto the queue */ |
|
return len; |
} |
|
static int nr_recvmsg(struct socket *sock, struct msghdr *msg, int size, |
int flags, struct scm_cookie *scm) |
{ |
struct sock *sk = sock->sk; |
struct sockaddr_ax25 *sax = (struct sockaddr_ax25 *)msg->msg_name; |
int copied; |
struct sk_buff *skb; |
int er; |
|
/* |
* This works for seqpacket too. The receiver has ordered the queue for |
* us! We do one quick check first though |
*/ |
|
if (sk->state != TCP_ESTABLISHED) |
return -ENOTCONN; |
|
/* Now we can treat all alike */ |
if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &er)) == NULL) |
return er; |
|
skb->h.raw = skb->data; |
copied = skb->len; |
|
if (copied > size) { |
copied = size; |
msg->msg_flags |= MSG_TRUNC; |
} |
|
skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied); |
|
if (sax != NULL) { |
sax->sax25_family = AF_NETROM; |
memcpy(sax->sax25_call.ax25_call, skb->data + 7, AX25_ADDR_LEN); |
} |
|
msg->msg_namelen = sizeof(*sax); |
|
skb_free_datagram(sk, skb); |
|
return copied; |
} |
|
|
static int nr_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) |
{ |
struct sock *sk = sock->sk; |
|
switch (cmd) { |
case TIOCOUTQ: { |
long amount; |
amount = sk->sndbuf - atomic_read(&sk->wmem_alloc); |
if (amount < 0) |
amount = 0; |
return put_user(amount, (int *)arg); |
} |
|
case TIOCINQ: { |
struct sk_buff *skb; |
long amount = 0L; |
/* These two are safe on a single CPU system as only user tasks fiddle here */ |
if ((skb = skb_peek(&sk->receive_queue)) != NULL) |
amount = skb->len; |
return put_user(amount, (int *)arg); |
} |
|
case SIOCGSTAMP: |
if (sk != NULL) { |
if (sk->stamp.tv_sec == 0) |
return -ENOENT; |
return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0; |
} |
return -EINVAL; |
|
case SIOCGIFADDR: |
case SIOCSIFADDR: |
case SIOCGIFDSTADDR: |
case SIOCSIFDSTADDR: |
case SIOCGIFBRDADDR: |
case SIOCSIFBRDADDR: |
case SIOCGIFNETMASK: |
case SIOCSIFNETMASK: |
case SIOCGIFMETRIC: |
case SIOCSIFMETRIC: |
return -EINVAL; |
|
case SIOCADDRT: |
case SIOCDELRT: |
case SIOCNRDECOBS: |
if (!capable(CAP_NET_ADMIN)) return -EPERM; |
return nr_rt_ioctl(cmd, (void *)arg); |
|
default: |
return dev_ioctl(cmd, (void *)arg); |
} |
|
/*NOTREACHED*/ |
return 0; |
} |
|
static int nr_get_info(char *buffer, char **start, off_t offset, int length) |
{ |
struct sock *s; |
struct net_device *dev; |
const char *devname; |
int len = 0; |
off_t pos = 0; |
off_t begin = 0; |
|
cli(); |
|
len += sprintf(buffer, "user_addr dest_node src_node dev my your st vs vr va t1 t2 t4 idle n2 wnd Snd-Q Rcv-Q inode\n"); |
|
for (s = nr_list; s != NULL; s = s->next) { |
if ((dev = s->protinfo.nr->device) == NULL) |
devname = "???"; |
else |
devname = dev->name; |
|
len += sprintf(buffer + len, "%-9s ", |
ax2asc(&s->protinfo.nr->user_addr)); |
len += sprintf(buffer + len, "%-9s ", |
ax2asc(&s->protinfo.nr->dest_addr)); |
len += sprintf(buffer + len, "%-9s %-3s %02X/%02X %02X/%02X %2d %3d %3d %3d %3lu/%03lu %2lu/%02lu %3lu/%03lu %3lu/%03lu %2d/%02d %3d %5d %5d %ld\n", |
ax2asc(&s->protinfo.nr->source_addr), |
devname, |
s->protinfo.nr->my_index, |
s->protinfo.nr->my_id, |
s->protinfo.nr->your_index, |
s->protinfo.nr->your_id, |
s->protinfo.nr->state, |
s->protinfo.nr->vs, |
s->protinfo.nr->vr, |
s->protinfo.nr->va, |
ax25_display_timer(&s->protinfo.nr->t1timer) / HZ, |
s->protinfo.nr->t1 / HZ, |
ax25_display_timer(&s->protinfo.nr->t2timer) / HZ, |
s->protinfo.nr->t2 / HZ, |
ax25_display_timer(&s->protinfo.nr->t4timer) / HZ, |
s->protinfo.nr->t4 / HZ, |
ax25_display_timer(&s->protinfo.nr->idletimer) / (60 * HZ), |
s->protinfo.nr->idle / (60 * HZ), |
s->protinfo.nr->n2count, |
s->protinfo.nr->n2, |
s->protinfo.nr->window, |
atomic_read(&s->wmem_alloc), |
atomic_read(&s->rmem_alloc), |
s->socket != NULL ? s->socket->inode->i_ino : 0L); |
|
pos = begin + len; |
|
if (pos < offset) { |
len = 0; |
begin = pos; |
} |
|
if (pos > offset + length) |
break; |
} |
|
sti(); |
|
*start = buffer + (offset - begin); |
len -= (offset - begin); |
|
if (len > length) len = length; |
|
return(len); |
} |
|
static struct net_proto_family nr_family_ops = { |
family: PF_NETROM, |
create: nr_create, |
}; |
|
static struct proto_ops SOCKOPS_WRAPPED(nr_proto_ops) = { |
family: PF_NETROM, |
|
release: nr_release, |
bind: nr_bind, |
connect: nr_connect, |
socketpair: sock_no_socketpair, |
accept: nr_accept, |
getname: nr_getname, |
poll: datagram_poll, |
ioctl: nr_ioctl, |
listen: nr_listen, |
shutdown: sock_no_shutdown, |
setsockopt: nr_setsockopt, |
getsockopt: nr_getsockopt, |
sendmsg: nr_sendmsg, |
recvmsg: nr_recvmsg, |
mmap: sock_no_mmap, |
sendpage: sock_no_sendpage, |
}; |
|
#include <linux/smp_lock.h> |
SOCKOPS_WRAP(nr_proto, PF_NETROM); |
|
static struct notifier_block nr_dev_notifier = { |
notifier_call: nr_device_event, |
}; |
|
static struct net_device *dev_nr; |
|
static char banner[] __initdata = KERN_INFO "G4KLX NET/ROM for Linux. Version 0.7 for AX25.037 Linux 2.4\n"; |
|
static int __init nr_proto_init(void) |
{ |
int i; |
|
if (nr_ndevs > 0x7fffffff/sizeof(struct net_device)) { |
printk(KERN_ERR "NET/ROM: nr_proto_init - nr_ndevs parameter to large\n"); |
return -1; |
} |
|
if ((dev_nr = kmalloc(nr_ndevs * sizeof(struct net_device), GFP_KERNEL)) == NULL) { |
printk(KERN_ERR "NET/ROM: nr_proto_init - unable to allocate device structure\n"); |
return -1; |
} |
|
memset(dev_nr, 0x00, nr_ndevs * sizeof(struct net_device)); |
|
for (i = 0; i < nr_ndevs; i++) { |
sprintf(dev_nr[i].name, "nr%d", i); |
dev_nr[i].init = nr_init; |
register_netdev(&dev_nr[i]); |
} |
|
sock_register(&nr_family_ops); |
register_netdevice_notifier(&nr_dev_notifier); |
printk(banner); |
|
ax25_protocol_register(AX25_P_NETROM, nr_route_frame); |
ax25_linkfail_register(nr_link_failed); |
|
#ifdef CONFIG_SYSCTL |
nr_register_sysctl(); |
#endif |
|
nr_loopback_init(); |
|
proc_net_create("nr", 0, nr_get_info); |
proc_net_create("nr_neigh", 0, nr_neigh_get_info); |
proc_net_create("nr_nodes", 0, nr_nodes_get_info); |
return 0; |
} |
|
module_init(nr_proto_init); |
|
|
EXPORT_NO_SYMBOLS; |
|
MODULE_PARM(nr_ndevs, "i"); |
MODULE_PARM_DESC(nr_ndevs, "number of NET/ROM devices"); |
|
MODULE_AUTHOR("Jonathan Naylor G4KLX <g4klx@g4klx.demon.co.uk>"); |
MODULE_DESCRIPTION("The amateur radio NET/ROM network and transport layer protocol"); |
MODULE_LICENSE("GPL"); |
|
static void __exit nr_exit(void) |
{ |
int i; |
|
proc_net_remove("nr"); |
proc_net_remove("nr_neigh"); |
proc_net_remove("nr_nodes"); |
nr_loopback_clear(); |
|
nr_rt_free(); |
|
ax25_protocol_release(AX25_P_NETROM); |
ax25_linkfail_release(nr_link_failed); |
|
unregister_netdevice_notifier(&nr_dev_notifier); |
|
#ifdef CONFIG_SYSCTL |
nr_unregister_sysctl(); |
#endif |
sock_unregister(PF_NETROM); |
|
for (i = 0; i < nr_ndevs; i++) { |
if (dev_nr[i].priv != NULL) { |
kfree(dev_nr[i].priv); |
dev_nr[i].priv = NULL; |
unregister_netdev(&dev_nr[i]); |
} |
} |
|
kfree(dev_nr); |
} |
module_exit(nr_exit); |
/sysctl_net_netrom.c
0,0 → 1,90
/* -*- linux-c -*- |
* sysctl_net_netrom.c: sysctl interface to net NET/ROM subsystem. |
* |
* Begun April 1, 1996, Mike Shaver. |
* Added /proc/sys/net/netrom directory entry (empty =) ). [MS] |
*/ |
|
#include <linux/mm.h> |
#include <linux/sysctl.h> |
#include <linux/init.h> |
#include <net/ax25.h> |
#include <net/netrom.h> |
|
/* |
* Values taken from NET/ROM documentation. |
*/ |
static int min_quality[] = {0}, max_quality[] = {255}; |
static int min_obs[] = {0}, max_obs[] = {255}; |
static int min_ttl[] = {0}, max_ttl[] = {255}; |
static int min_t1[] = {5 * HZ}; |
static int max_t1[] = {600 * HZ}; |
static int min_n2[] = {2}, max_n2[] = {127}; |
static int min_t2[] = {1 * HZ}; |
static int max_t2[] = {60 * HZ}; |
static int min_t4[] = {1 * HZ}; |
static int max_t4[] = {1000 * HZ}; |
static int min_window[] = {1}, max_window[] = {127}; |
static int min_idle[] = {0 * HZ}; |
static int max_idle[] = {65535 * HZ}; |
static int min_route[] = {0}, max_route[] = {1}; |
static int min_fails[] = {1}, max_fails[] = {10}; |
|
static struct ctl_table_header *nr_table_header; |
|
static ctl_table nr_table[] = { |
{NET_NETROM_DEFAULT_PATH_QUALITY, "default_path_quality", |
&sysctl_netrom_default_path_quality, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_quality, &max_quality}, |
{NET_NETROM_OBSOLESCENCE_COUNT_INITIALISER, "obsolescence_count_initialiser", |
&sysctl_netrom_obsolescence_count_initialiser, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_obs, &max_obs}, |
{NET_NETROM_NETWORK_TTL_INITIALISER, "network_ttl_initialiser", |
&sysctl_netrom_network_ttl_initialiser, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_ttl, &max_ttl}, |
{NET_NETROM_TRANSPORT_TIMEOUT, "transport_timeout", |
&sysctl_netrom_transport_timeout, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t1, &max_t1}, |
{NET_NETROM_TRANSPORT_MAXIMUM_TRIES, "transport_maximum_tries", |
&sysctl_netrom_transport_maximum_tries, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_n2, &max_n2}, |
{NET_NETROM_TRANSPORT_ACKNOWLEDGE_DELAY, "transport_acknowledge_delay", |
&sysctl_netrom_transport_acknowledge_delay, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t2, &max_t2}, |
{NET_NETROM_TRANSPORT_BUSY_DELAY, "transport_busy_delay", |
&sysctl_netrom_transport_busy_delay, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t4, &max_t4}, |
{NET_NETROM_TRANSPORT_REQUESTED_WINDOW_SIZE, "transport_requested_window_size", |
&sysctl_netrom_transport_requested_window_size, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_window, &max_window}, |
{NET_NETROM_TRANSPORT_NO_ACTIVITY_TIMEOUT, "transport_no_activity_timeout", |
&sysctl_netrom_transport_no_activity_timeout, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_idle, &max_idle}, |
{NET_NETROM_ROUTING_CONTROL, "routing_control", |
&sysctl_netrom_routing_control, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_route, &max_route}, |
{NET_NETROM_LINK_FAILS_COUNT, "link_fails_count", |
&sysctl_netrom_link_fails_count, sizeof(int), 0644, NULL, |
&proc_dointvec_minmax, &sysctl_intvec, NULL, &min_fails, &max_fails}, |
{0} |
}; |
|
static ctl_table nr_dir_table[] = { |
{NET_NETROM, "netrom", NULL, 0, 0555, nr_table}, |
{0} |
}; |
|
static ctl_table nr_root_table[] = { |
{CTL_NET, "net", NULL, 0, 0555, nr_dir_table}, |
{0} |
}; |
|
void __init nr_register_sysctl(void) |
{ |
nr_table_header = register_sysctl_table(nr_root_table, 1); |
} |
|
void nr_unregister_sysctl(void) |
{ |
unregister_sysctl_table(nr_table_header); |
} |
/Makefile
0,0 → 1,19
# |
# Makefile for the Linux NET/ROM layer. |
# |
# Note! Dependencies are done automagically by 'make dep', which also |
# removes any old dependencies. DON'T put your own dependencies here |
# unless it's something special (ie not a .c file). |
# |
# Note 2! The CFLAGS definition is now in the main makefile... |
|
O_TARGET := netrom.o |
|
obj-y := af_netrom.o nr_dev.o nr_in.o nr_loopback.o nr_out.o nr_route.o \ |
nr_subr.o nr_timer.o |
obj-m := $(O_TARGET) |
|
obj-$(CONFIG_SYSCTL) += sysctl_net_netrom.o |
|
include $(TOPDIR)/Rules.make |
|
/nr_dev.c
0,0 → 1,237
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) Cloned from loopback.c |
* NET/ROM 002 Steve Whitehouse(GW7RRM) fixed the set_mac_address |
* NET/ROM 003 Jonathan(G4KLX) Put nr_rebuild_header into line with |
* ax25_rebuild_header |
* NET/ROM 004 Jonathan(G4KLX) Callsign registration with AX.25. |
* NET/ROM 006 Hans(PE1AYX) Fixed interface to IP layer. |
*/ |
|
#include <linux/config.h> |
#include <linux/module.h> |
#include <linux/proc_fs.h> |
#include <linux/kernel.h> |
#include <linux/sched.h> |
#include <linux/interrupt.h> |
#include <linux/fs.h> |
#include <linux/types.h> |
#include <linux/sysctl.h> |
#include <linux/string.h> |
#include <linux/socket.h> |
#include <linux/errno.h> |
#include <linux/fcntl.h> |
#include <linux/in.h> |
#include <linux/if_ether.h> /* For the statistics structure. */ |
|
#include <asm/system.h> |
#include <asm/uaccess.h> |
#include <asm/io.h> |
|
#include <linux/inet.h> |
#include <linux/netdevice.h> |
#include <linux/etherdevice.h> |
#include <linux/if_arp.h> |
#include <linux/skbuff.h> |
|
#include <net/ip.h> |
#include <net/arp.h> |
|
#include <net/ax25.h> |
#include <net/netrom.h> |
|
#ifdef CONFIG_INET |
|
/* |
* Only allow IP over NET/ROM frames through if the netrom device is up. |
*/ |
|
int nr_rx_ip(struct sk_buff *skb, struct net_device *dev) |
{ |
struct net_device_stats *stats = (struct net_device_stats *)dev->priv; |
|
if (!netif_running(dev)) { |
stats->rx_errors++; |
return 0; |
} |
|
stats->rx_packets++; |
stats->rx_bytes += skb->len; |
|
skb->protocol = htons(ETH_P_IP); |
|
/* Spoof incoming device */ |
skb->dev = dev; |
skb->h.raw = skb->data; |
skb->nh.raw = skb->data; |
skb->pkt_type = PACKET_HOST; |
|
ip_rcv(skb, skb->dev, NULL); |
|
return 1; |
} |
|
|
static int nr_rebuild_header(struct sk_buff *skb) |
{ |
struct net_device *dev = skb->dev; |
struct net_device_stats *stats = (struct net_device_stats *)dev->priv; |
struct sk_buff *skbn; |
unsigned char *bp = skb->data; |
int len; |
|
if (arp_find(bp + 7, skb)) { |
return 1; |
} |
|
bp[6] &= ~AX25_CBIT; |
bp[6] &= ~AX25_EBIT; |
bp[6] |= AX25_SSSID_SPARE; |
bp += AX25_ADDR_LEN; |
|
bp[6] &= ~AX25_CBIT; |
bp[6] |= AX25_EBIT; |
bp[6] |= AX25_SSSID_SPARE; |
|
if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) { |
kfree_skb(skb); |
return 1; |
} |
|
if (skb->sk != NULL) |
skb_set_owner_w(skbn, skb->sk); |
|
kfree_skb(skb); |
|
len = skbn->len; |
|
if (!nr_route_frame(skbn, NULL)) { |
kfree_skb(skbn); |
stats->tx_errors++; |
} |
|
stats->tx_packets++; |
stats->tx_bytes += len; |
|
return 1; |
} |
|
#else |
|
static int nr_rebuild_header(struct sk_buff *skb) |
{ |
return 1; |
} |
|
#endif |
|
static int nr_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, |
void *daddr, void *saddr, unsigned len) |
{ |
unsigned char *buff = skb_push(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN); |
|
memcpy(buff, (saddr != NULL) ? saddr : dev->dev_addr, dev->addr_len); |
buff[6] &= ~AX25_CBIT; |
buff[6] &= ~AX25_EBIT; |
buff[6] |= AX25_SSSID_SPARE; |
buff += AX25_ADDR_LEN; |
|
if (daddr != NULL) |
memcpy(buff, daddr, dev->addr_len); |
buff[6] &= ~AX25_CBIT; |
buff[6] |= AX25_EBIT; |
buff[6] |= AX25_SSSID_SPARE; |
buff += AX25_ADDR_LEN; |
|
*buff++ = sysctl_netrom_network_ttl_initialiser; |
|
*buff++ = NR_PROTO_IP; |
*buff++ = NR_PROTO_IP; |
*buff++ = 0; |
*buff++ = 0; |
*buff++ = NR_PROTOEXT; |
|
if (daddr != NULL) |
return 37; |
|
return -37; |
} |
|
static int nr_set_mac_address(struct net_device *dev, void *addr) |
{ |
struct sockaddr *sa = addr; |
|
ax25_listen_release((ax25_address *)dev->dev_addr, NULL); |
|
memcpy(dev->dev_addr, sa->sa_data, dev->addr_len); |
|
ax25_listen_register((ax25_address *)dev->dev_addr, NULL); |
|
return 0; |
} |
|
static int nr_open(struct net_device *dev) |
{ |
MOD_INC_USE_COUNT; |
netif_start_queue(dev); |
ax25_listen_register((ax25_address *)dev->dev_addr, NULL); |
return 0; |
} |
|
static int nr_close(struct net_device *dev) |
{ |
netif_stop_queue(dev); |
ax25_listen_release((ax25_address *)dev->dev_addr, NULL); |
MOD_DEC_USE_COUNT; |
return 0; |
} |
|
static int nr_xmit(struct sk_buff *skb, struct net_device *dev) |
{ |
struct net_device_stats *stats = (struct net_device_stats *)dev->priv; |
dev_kfree_skb(skb); |
stats->tx_errors++; |
return 0; |
} |
|
static struct net_device_stats *nr_get_stats(struct net_device *dev) |
{ |
return (struct net_device_stats *)dev->priv; |
} |
|
int nr_init(struct net_device *dev) |
{ |
dev->mtu = NR_MAX_PACKET_SIZE; |
dev->hard_start_xmit = nr_xmit; |
dev->open = nr_open; |
dev->stop = nr_close; |
|
dev->hard_header = nr_header; |
dev->hard_header_len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN; |
dev->addr_len = AX25_ADDR_LEN; |
dev->type = ARPHRD_NETROM; |
dev->rebuild_header = nr_rebuild_header; |
dev->set_mac_address = nr_set_mac_address; |
|
/* New-style flags. */ |
dev->flags = 0; |
|
if ((dev->priv = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL)) == NULL) |
return -ENOMEM; |
|
memset(dev->priv, 0, sizeof(struct net_device_stats)); |
|
dev->get_stats = nr_get_stats; |
|
return 0; |
}; |
/nr_route.c
0,0 → 1,908
/* |
* NET/ROM release 007 |
* |
* This code REQUIRES 2.1.15 or higher/ NET3.038 |
* |
* This module: |
* This module 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. |
* |
* History |
* NET/ROM 001 Jonathan(G4KLX) First attempt. |
* NET/ROM 003 Jonathan(G4KLX) Use SIOCADDRT/SIOCDELRT ioctl values |
* for NET/ROM routes. |
* Use '*' for a blank mnemonic in /proc/net/nr_nodes. |
* Change default quality for new neighbour when same |
* as node callsign. |
* Alan Cox(GW4PTS) Added the firewall hooks. |
* NET/ROM 006 Jonathan(G4KLX) Added the setting of digipeated neighbours. |
* Tomi(OH2BNS) Routing quality and link failure changes. |
* Device refcnt fixes. |
*/ |
|
#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 <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 <asm/system.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 <net/netrom.h> |
|
static unsigned int nr_neigh_no = 1; |
|
static struct nr_node *nr_node_list; |
static struct nr_neigh *nr_neigh_list; |
|
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 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; |
struct net_device *tdev; |
unsigned long flags; |
int i, found; |
|
/* Can't add routes to ourself */ |
if ((tdev = nr_dev_get(nr)) != NULL) { |
dev_put(tdev); |
return -EINVAL; |
} |
|
for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) |
if (ax25cmp(nr, &nr_node->callsign) == 0) |
break; |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) |
if (ax25cmp(ax25, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev) |
break; |
|
/* |
* 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 *node; |
|
for (node = nr_node_list; node != NULL; node = node->next) |
for (i = 0; i < node->count; i++) |
if (node->routes[i].neighbour == nr_neigh) |
if (i < node->which) |
node->which = i; |
} |
|
if (nr_neigh != NULL) |
nr_neigh->failed = 0; |
|
if (quality == 0 && nr_neigh != NULL && nr_node != NULL) |
return 0; |
|
if (nr_neigh == NULL) { |
if ((nr_neigh = kmalloc(sizeof(*nr_neigh), GFP_ATOMIC)) == NULL) |
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; |
|
if (ax25_digi != NULL && ax25_digi->ndigi > 0) { |
if ((nr_neigh->digipeat = kmalloc(sizeof(*ax25_digi), GFP_KERNEL)) == NULL) { |
kfree(nr_neigh); |
return -ENOMEM; |
} |
memcpy(nr_neigh->digipeat, ax25_digi, sizeof(ax25_digi)); |
} |
|
dev_hold(nr_neigh->dev); |
|
save_flags(flags); |
cli(); |
|
nr_neigh->next = nr_neigh_list; |
nr_neigh_list = nr_neigh; |
|
restore_flags(flags); |
} |
|
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) |
return -ENOMEM; |
|
nr_node->callsign = *nr; |
strcpy(nr_node->mnemonic, mnemonic); |
|
nr_node->which = 0; |
nr_node->count = 1; |
|
nr_node->routes[0].quality = quality; |
nr_node->routes[0].obs_count = obs_count; |
nr_node->routes[0].neighbour = nr_neigh; |
|
save_flags(flags); |
cli(); |
|
nr_node->next = nr_node_list; |
nr_node_list = nr_node; |
|
restore_flags(flags); |
|
nr_neigh->count++; |
|
return 0; |
} |
|
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->count++; |
} else { |
/* It must be better than the worst */ |
if (quality > nr_node->routes[2].quality) { |
nr_node->routes[2].neighbour->count--; |
|
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->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; |
default: 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; |
} |
} |
|
return 0; |
} |
|
static void nr_remove_node(struct nr_node *nr_node) |
{ |
struct nr_node *s; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
if ((s = nr_node_list) == nr_node) { |
nr_node_list = nr_node->next; |
restore_flags(flags); |
kfree(nr_node); |
return; |
} |
|
while (s != NULL && s->next != NULL) { |
if (s->next == nr_node) { |
s->next = nr_node->next; |
restore_flags(flags); |
kfree(nr_node); |
return; |
} |
|
s = s->next; |
} |
|
restore_flags(flags); |
} |
|
static void nr_remove_neigh(struct nr_neigh *nr_neigh) |
{ |
struct nr_neigh *s; |
unsigned long flags; |
|
save_flags(flags); |
cli(); |
|
if ((s = nr_neigh_list) == nr_neigh) { |
nr_neigh_list = nr_neigh->next; |
restore_flags(flags); |
dev_put(nr_neigh->dev); |
if (nr_neigh->digipeat != NULL) |
kfree(nr_neigh->digipeat); |
kfree(nr_neigh); |
return; |
} |
|
while (s != NULL && s->next != NULL) { |
if (s->next == nr_neigh) { |
s->next = nr_neigh->next; |
restore_flags(flags); |
dev_put(nr_neigh->dev); |
if (nr_neigh->digipeat != NULL) |
kfree(nr_neigh->digipeat); |
kfree(nr_neigh); |
return; |
} |
|
s = s->next; |
} |
|
restore_flags(flags); |
} |
|
/* |
* "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; |
|
for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) |
if (ax25cmp(callsign, &nr_node->callsign) == 0) |
break; |
|
if (nr_node == NULL) return -EINVAL; |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) |
if (ax25cmp(neighbour, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev) |
break; |
|
if (nr_neigh == NULL) return -EINVAL; |
|
for (i = 0; i < nr_node->count; i++) { |
if (nr_node->routes[i].neighbour == nr_neigh) { |
nr_neigh->count--; |
|
if (nr_neigh->count == 0 && !nr_neigh->locked) |
nr_remove_neigh(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; |
} |
} |
|
return 0; |
} |
} |
|
return -EINVAL; |
} |
|
/* |
* Lock a neighbour with a quality. |
*/ |
static int nr_add_neigh(ax25_address *callsign, ax25_digi *ax25_digi, struct net_device *dev, unsigned int quality) |
{ |
struct nr_neigh *nr_neigh; |
unsigned long flags; |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) { |
if (ax25cmp(callsign, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev) { |
nr_neigh->quality = quality; |
nr_neigh->locked = 1; |
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; |
|
if (ax25_digi != NULL && ax25_digi->ndigi > 0) { |
if ((nr_neigh->digipeat = kmalloc(sizeof(*ax25_digi), GFP_KERNEL)) == NULL) { |
kfree(nr_neigh); |
return -ENOMEM; |
} |
memcpy(nr_neigh->digipeat, ax25_digi, sizeof(ax25_digi)); |
} |
|
dev_hold(nr_neigh->dev); |
|
save_flags(flags); |
cli(); |
|
nr_neigh->next = nr_neigh_list; |
nr_neigh_list = nr_neigh; |
|
restore_flags(flags); |
|
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; |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) |
if (ax25cmp(callsign, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev) |
break; |
|
if (nr_neigh == NULL) return -EINVAL; |
|
nr_neigh->quality = quality; |
nr_neigh->locked = 0; |
|
if (nr_neigh->count == 0) |
nr_remove_neigh(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, *nr_node; |
int i; |
|
nr_node = nr_node_list; |
|
while (nr_node != NULL) { |
s = nr_node; |
nr_node = nr_node->next; |
|
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--; |
|
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]; |
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(s); |
} |
|
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, *nr_neigh = nr_neigh_list; |
struct nr_node *t, *nr_node; |
int i; |
|
while (nr_neigh != NULL) { |
s = nr_neigh; |
nr_neigh = nr_neigh->next; |
|
if (s->dev == dev) { |
nr_node = nr_node_list; |
|
while (nr_node != NULL) { |
t = nr_node; |
nr_node = nr_node->next; |
|
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(t); |
} |
|
nr_remove_neigh(s); |
} |
} |
} |
|
/* |
* 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(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; |
|
read_lock(&dev_base_lock); |
for (dev = dev_base; dev != NULL; dev = dev->next) { |
if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM) |
if (first == NULL || strncmp(dev->name, first->name, 3) < 0) |
first = dev; |
} |
|
if (first != NULL) |
dev_hold(first); |
|
read_unlock(&dev_base_lock); |
|
return first; |
} |
|
/* |
* Find the NET/ROM device for the given callsign. |
*/ |
struct net_device *nr_dev_get(ax25_address *addr) |
{ |
struct net_device *dev; |
|
read_lock(&dev_base_lock); |
for (dev = dev_base; dev != NULL; dev = dev->next) { |
if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM && ax25cmp(addr, (ax25_address *)dev->dev_addr) == 0) { |
dev_hold(dev); |
goto out; |
} |
} |
out: |
read_unlock(&dev_base_lock); |
return dev; |
} |
|
static ax25_digi *nr_call_to_digi(int ndigis, ax25_address *digipeaters) |
{ |
static ax25_digi ax25_digi; |
int i; |
|
if (ndigis == 0) |
return NULL; |
|
for (i = 0; i < ndigis; i++) { |
ax25_digi.calls[i] = digipeaters[i]; |
ax25_digi.repeated[i] = 0; |
} |
|
ax25_digi.ndigi = ndigis; |
ax25_digi.lastrepeat = -1; |
|
return &ax25_digi; |
} |
|
/* |
* Handle the ioctls that control the routing functions. |
*/ |
int nr_rt_ioctl(unsigned int cmd, void *arg) |
{ |
struct nr_route_struct nr_route; |
struct net_device *dev; |
int ret; |
|
switch (cmd) { |
|
case SIOCADDRT: |
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; |
if (nr_route.ndigis < 0 || nr_route.ndigis > AX25_MAX_DIGIS) { |
dev_put(dev); |
return -EINVAL; |
} |
switch (nr_route.type) { |
case NETROM_NODE: |
ret = nr_add_node(&nr_route.callsign, |
nr_route.mnemonic, |
&nr_route.neighbour, |
nr_call_to_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(nr_route.ndigis, nr_route.digipeaters), |
dev, nr_route.quality); |
break; |
default: |
ret = -EINVAL; |
break; |
} |
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; |
break; |
} |
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 *nr_neigh; |
struct nr_node *nr_node; |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) |
if (nr_neigh->ax25 == ax25) |
break; |
|
if (nr_neigh == NULL) return; |
|
nr_neigh->ax25 = NULL; |
|
if (++nr_neigh->failed < sysctl_netrom_link_fails_count) return; |
|
for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) |
if (nr_node->which < nr_node->count && nr_node->routes[nr_node->which].neighbour == nr_neigh) |
nr_node->which++; |
} |
|
/* |
* 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; |
|
|
nr_src = (ax25_address *)(skb->data + 0); |
nr_dest = (ax25_address *)(skb->data + 7); |
|
if (ax25 != NULL) |
nr_add_node(nr_src, "", &ax25->dest_addr, ax25->digipeat, |
ax25->ax25_dev->dev, 0, sysctl_netrom_obsolescence_count_initialiser); |
|
if ((dev = nr_dev_get(nr_dest)) != NULL) { /* Its for me */ |
int ret; |
|
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] == 0) |
return 0; |
|
for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) |
if (ax25cmp(nr_dest, &nr_node->callsign) == 0) |
break; |
|
if (nr_node == NULL || nr_node->which >= nr_node->count) |
return 0; |
|
nr_neigh = nr_node->routes[nr_node->which].neighbour; |
|
if ((dev = nr_dev_first()) == NULL) |
return 0; |
|
dptr = skb_push(skb, 1); |
*dptr = AX25_P_NETROM; |
|
nr_neigh->ax25 = ax25_send_frame(skb, 256, (ax25_address *)dev->dev_addr, &nr_neigh->callsign, nr_neigh->digipeat, nr_neigh->dev); |
|
dev_put(dev); |
|
return (nr_neigh->ax25 != NULL); |
} |
|
int nr_nodes_get_info(char *buffer, char **start, off_t offset, int length) |
{ |
struct nr_node *nr_node; |
int len = 0; |
off_t pos = 0; |
off_t begin = 0; |
int i; |
|
cli(); |
|
len += sprintf(buffer, "callsign mnemonic w n qual obs neigh qual obs neigh qual obs neigh\n"); |
|
for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) { |
len += sprintf(buffer + len, "%-9s %-7s %d %d", |
ax2asc(&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++) { |
len += sprintf(buffer + len, " %3d %d %05d", |
nr_node->routes[i].quality, |
nr_node->routes[i].obs_count, |
nr_node->routes[i].neighbour->number); |
} |
|
len += sprintf(buffer + len, "\n"); |
|
pos = begin + len; |
|
if (pos < offset) { |
len = 0; |
begin = pos; |
} |
|
if (pos > offset + length) |
break; |
} |
|
sti(); |
|
*start = buffer + (offset - begin); |
len -= (offset - begin); |
|
if (len > length) len = length; |
|
return len; |
} |
|
int nr_neigh_get_info(char *buffer, char **start, off_t offset, int length) |
{ |
struct nr_neigh *nr_neigh; |
int len = 0; |
off_t pos = 0; |
off_t begin = 0; |
int i; |
|
cli(); |
|
len += sprintf(buffer, "addr callsign dev qual lock count failed digipeaters\n"); |
|
for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) { |
len += sprintf(buffer + len, "%05d %-9s %-4s %3d %d %3d %3d", |
nr_neigh->number, |
ax2asc(&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++) |
len += sprintf(buffer + len, " %s", ax2asc(&nr_neigh->digipeat->calls[i])); |
} |
|
len += sprintf(buffer + len, "\n"); |
|
pos = begin + len; |
|
if (pos < offset) { |
len = 0; |
begin = pos; |
} |
|
if (pos > offset + length) |
break; |
} |
|
sti(); |
|
*start = buffer + (offset - begin); |
len -= (offset - begin); |
|
if (len > length) len = length; |
|
return len; |
} |
|
/* |
* Free all memory associated with the nodes and routes lists. |
*/ |
void __exit nr_rt_free(void) |
{ |
struct nr_neigh *s, *nr_neigh = nr_neigh_list; |
struct nr_node *t, *nr_node = nr_node_list; |
|
while (nr_node != NULL) { |
t = nr_node; |
nr_node = nr_node->next; |
|
nr_remove_node(t); |
} |
|
while (nr_neigh != NULL) { |
s = nr_neigh; |
nr_neigh = nr_neigh->next; |
|
nr_remove_neigh(s); |
} |
} |