summaryrefslogtreecommitdiffstats
path: root/net/ax25/ax25_core.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ax25/ax25_core.c')
-rw-r--r--net/ax25/ax25_core.c513
1 files changed, 513 insertions, 0 deletions
diff --git a/net/ax25/ax25_core.c b/net/ax25/ax25_core.c
new file mode 100644
index 000000000..37d171d83
--- /dev/null
+++ b/net/ax25/ax25_core.c
@@ -0,0 +1,513 @@
+/*
+ * ax25_core.c: AX.25 core and support functions for NEW-AX.25
+ *
+ * Authors: Jens David (DG1KJD), Matthias Welwarsky (DG2FEF),
+ * Jonathan (G4KLX), Alan Cox (GW4PTS)
+ *
+ * Comment:
+ *
+ * Changelog:
+ *
+ * License: 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.
+ */
+
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/ax25.h>
+#include <linux/spinlock.h>
+#include <net/ax25.h>
+#include <net/ax25dev.h>
+#include <net/sock.h>
+
+#include "af_ax25.h"
+#include "ax25_vj.h"
+#include "ax25_ddi.h"
+#include "ax25_core.h"
+#include "ax25_in.h"
+#include "ax25_subr.h"
+#include "ax25_ddi.h"
+
+
+rwlock_t ax25_list_lock = RW_LOCK_UNLOCKED;
+
+/* ---------------------------------------------------------------------*/
+/*
+ * The null address is defined as a callsign of all spaces with an
+ * SSID of zero.
+ */
+ax25_address null_ax25_address = {{0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00}};
+
+/* --------------------------------------------------------------------- */
+/*
+ * ax25 -> ascii conversion
+ */
+char *ax2asc(ax25_address *a)
+{
+ static char buf[11];
+ char c, *s;
+ int n;
+
+ for (n = 0, s = buf; n < 6; n++) {
+ c = (a->ax25_call[n] >> 1) & 0x7F;
+
+ if (c != ' ') *s++ = c;
+ }
+
+ *s++ = '-';
+
+ if ((n = ((a->ax25_call[6] >> 1) & 0x0F)) > 9) {
+ *s++ = '1';
+ n -= 10;
+ }
+
+ *s++ = n + '0';
+ *s++ = '\0';
+
+ if (*buf == '\0' || *buf == '-')
+ return "*";
+
+ return buf;
+
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * ascii -> ax25 conversion
+ */
+ax25_address *asc2ax(char *callsign)
+{
+ static ax25_address addr;
+ char *s;
+ int n;
+
+ for (s = callsign, n = 0; n < 6; n++) {
+ if (*s != '\0' && *s != '-')
+ addr.ax25_call[n] = *s++;
+ else
+ addr.ax25_call[n] = ' ';
+ addr.ax25_call[n] <<= 1;
+ addr.ax25_call[n] &= 0xFE;
+ }
+
+ if (*s++ == '\0') {
+ addr.ax25_call[6] = 0x00;
+ return &addr;
+ }
+
+ addr.ax25_call[6] = *s++ - '0';
+
+ if (*s != '\0') {
+ addr.ax25_call[6] *= 10;
+ addr.ax25_call[6] += *s++ - '0';
+ }
+
+ addr.ax25_call[6] <<= 1;
+ addr.ax25_call[6] &= 0x1E;
+
+ return &addr;
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Compare two ax.25 addresses
+ */
+int ax25cmp(ax25_address *a, ax25_address *b)
+{
+ int ct = 0;
+
+ while (ct < 6) {
+ if ((a->ax25_call[ct] & 0xFE) != (b->ax25_call[ct] & 0xFE)) /* Clean off repeater bits */
+ return 1;
+ ct++;
+ }
+
+ if ((a->ax25_call[ct] & 0x1E) == (b->ax25_call[ct] & 0x1E)) /* SSID without control bit */
+ return 0;
+
+ return 2; /* Partial match */
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Add a socket to the bound sockets list.
+ */
+void ax25_insert_cb(ax25_cb *ax25)
+{
+ if (ax25->device != NULL) {
+ ax25_dev_insert_cb(ax25);
+ return;
+ }
+
+ ax25->prev = NULL;
+ ax25->next = ax25_list;
+
+ write_lock(&ax25_list_lock);
+
+ if (ax25_list != NULL)
+ ax25_list->prev = ax25;
+ ax25_list = ax25;
+
+ write_unlock(&ax25_list_lock);
+
+ ax25->inserted = 1;
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Socket removal is now protected agains bottom half ints
+ * with a start/end_bh_atomic bracket. There should be no
+ * need to mask interrupts on hardware level.
+ */
+void ax25_remove_cb(ax25_cb *ax25)
+{
+ /*
+ * unbound sockets are not in any list
+ */
+
+ if (!ax25->inserted)
+ return;
+
+ if (ax25->device != NULL) {
+ ax25_dev_remove_cb(ax25);
+ return;
+ }
+
+
+ if (ax25_list != NULL) {
+ write_lock(&ax25_list_lock);
+ if (ax25->prev == NULL)
+ ax25_list = ax25->next;
+ else
+ ax25->prev->next = ax25->next;
+
+ if (ax25->next != NULL)
+ ax25->next->prev = ax25->prev;
+ write_unlock(&ax25_list_lock);
+ }
+
+ ax25->inserted = 0;
+
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Find an AX.25 control block given both ends.
+ */
+ax25_cb *ax25_find_cb(ax25_addr_t *addr, struct net_device *dev)
+{
+ ax25_cb *s;
+
+ read_lock(&ax25_list_lock);
+ for (s = ax25_dev_list(dev) ; s != NULL; s = s->next) {
+
+ if (s->sk != NULL && s->sk->type != SOCK_SEQPACKET)
+ continue;
+
+ if (s->addr.dcount == addr->dcount && !ax25cmp(&s->addr.src, &addr->src)
+ && !ax25cmp(&s->addr.dest, &addr->dest))
+ {
+ int i;
+
+ if (addr->dcount == 0)
+ break;
+ if (addr->lastrepeat != s->addr.lastrepeat)
+ continue;
+ i = addr->dcount;
+ while (i--) {
+ if (ax25cmp(&s->addr.digipeater[i], &addr->digipeater[i]))
+ break;
+ }
+ if (i < 0)
+ break;
+ }
+ }
+ read_unlock(&ax25_list_lock);
+ return s;
+}
+
+/* ---------------------------------------------------------------------*/
+
+void ax25_destroy_cb(ax25_cb *ax25)
+{
+ ax25_remove_cb(ax25);
+ ax25_clear_queues(ax25);
+ ax25_free_cb(ax25);
+}
+
+/* ---------------------------------------------------------------------*/
+
+void ax25_destroy_socket(struct sock *sk)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ /*
+ * this may be a pending SABM, waiting in the receive queue
+ * to become accepted. But the listener just died :-)
+ */
+ if (skb->sk != sk) {
+ /*
+ * signal the peer that we have closed the socket.
+ * if he has already disconnected himself, just mark
+ * the socket dead and move to TCP_CLOSE. This is only
+ * for security, as ax25_disconnect should have already
+ * done this.
+ */
+ if (skb->sk->state == TCP_ESTABLISHED)
+ skb->sk->protinfo.ax25->condition |= AX25_COND_RELEASE;
+ else {
+ printk(KERN_DEBUG "ax25_destroy_socket: TCP_CLOSE\n");
+ skb->sk->state = TCP_CLOSE;
+ }
+ skb->sk->dead = 1;
+ }
+ kfree_skb(skb);
+ }
+ sk_free(sk);
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Fill in a created AX.25 control block with the default
+ * values for a particular device.
+ */
+void ax25_fillin_cb(ax25_cb *ax25, struct net_device *dev)
+{
+ ax25->device = dev;
+ ax25->vs_rtt = -1;
+
+ if (dev != NULL) {
+ ax25->rtt = ax25_dev_get_value(dev, AX25_VALUES_T1) / 4;
+ ax25->t1 = ax25_dev_get_value(dev, AX25_VALUES_T1);
+ ax25->t2 = ax25_dev_get_value(dev, AX25_VALUES_T2);
+ ax25->t3 = ax25_dev_get_value(dev, AX25_VALUES_T3);
+ ax25->n2 = ax25_dev_get_value(dev, AX25_VALUES_N2);
+ ax25->paclen = ax25_dev_get_value(dev, AX25_VALUES_PACLEN);
+ ax25->idle = ax25_dev_get_value(dev, AX25_VALUES_IDLE);
+ ax25->backoff = ax25_dev_get_value(dev, AX25_VALUES_BACKOFF);
+
+ if (ax25_dev_get_value(dev, AX25_VALUES_AXDEFMODE)) {
+ ax25->seqmask = AX25_ESEQMASK;
+ ax25->window = ax25_dev_get_value(dev, AX25_VALUES_EWINDOW);
+ } else {
+ ax25->seqmask = AX25_SEQMASK;
+ ax25->window = ax25_dev_get_value(dev, AX25_VALUES_WINDOW);
+ }
+ } else {
+ ax25->rtt = AX25_DEF_T1 / 4;
+ ax25->t1 = AX25_DEF_T1;
+ ax25->t2 = AX25_DEF_T2;
+ ax25->t3 = AX25_DEF_T3;
+ ax25->n2 = AX25_DEF_N2;
+ ax25->paclen = AX25_DEF_PACLEN;
+ ax25->idle = AX25_DEF_IDLE;
+ ax25->backoff = AX25_DEF_BACKOFF;
+
+ if (AX25_DEF_AXDEFMODE) {
+ ax25->seqmask = AX25_ESEQMASK;
+ ax25->window = AX25_DEF_EWINDOW;
+ } else {
+ ax25->seqmask = AX25_ESEQMASK;
+ ax25->window = AX25_DEF_WINDOW;
+ }
+ }
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Create an empty AX.25 control block.
+ */
+ax25_cb *ax25_create_cb(void)
+{
+ ax25_cb *ax25;
+
+ if ((ax25 = (ax25_cb *)kmalloc(sizeof(ax25_cb), GFP_ATOMIC)) == NULL)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+
+ memset(ax25, 0x00, sizeof(ax25_cb));
+
+ skb_queue_head_init(&ax25->write_queue);
+ skb_queue_head_init(&ax25->frag_queue);
+ skb_queue_head_init(&ax25->ack_queue);
+ skb_queue_head_init(&ax25->rcv_queue);
+
+ ax25->state = AX25_LISTEN;
+ ax25->condition = AX25_COND_SETUP;
+
+ return ax25;
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Free an allocated ax25 control block. This is done to centralise
+ * the MOD count code.
+ */
+void ax25_free_cb(ax25_cb *ax25)
+{
+ if (ax25->slcomp != NULL) {
+ axhc_free(ax25->slcomp);
+ }
+
+ if (ax25->peer != NULL) {
+ ax25->peer->peer = NULL;
+ }
+
+ kfree(ax25);
+
+ MOD_DEC_USE_COUNT;
+}
+
+/* ---------------------------------------------------------------------*/
+
+void ax25_free_sock(struct sock *sk)
+{
+ if (sk->protinfo.ax25 != NULL)
+ ax25_free_cb(sk->protinfo.ax25);
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Given an AX.25 address pull of to, from, digi list, command/response and the start of data
+ *
+ */
+unsigned char *ax25_parse_addr(unsigned char *buf, int len, ax25_pktinfo *pkt_info)
+{
+ int d = 0;
+
+ if (len < 15)
+ return NULL;
+
+ pkt_info->cmdrsp = 0;
+
+ memcpy(&pkt_info->addr.dest, buf, AX25_ADDR_LEN);
+
+ if (buf[6] & AX25_CBIT)
+ pkt_info->cmdrsp = AX25_COMMAND;
+ buf += AX25_ADDR_LEN;
+ len -= AX25_ADDR_LEN;
+
+ memcpy(&pkt_info->addr.src, buf, AX25_ADDR_LEN);
+
+ if (buf[6] & AX25_CBIT)
+ pkt_info->cmdrsp = AX25_RESPONSE;
+
+ pkt_info->dama = !(buf[6] & AX25_DAMA_FLAG);
+
+ pkt_info->addr.lastrepeat = -1;
+ pkt_info->addr.dcount = 0;
+
+ while (!(buf[6] & AX25_EBIT)) {
+ buf += AX25_ADDR_LEN;
+ len -= AX25_ADDR_LEN;
+
+ if (d < AX25_MAX_DIGIS && len >= 7) {
+ memcpy(&pkt_info->addr.digipeater[d], buf, AX25_ADDR_LEN);
+ if (buf[6] & AX25_HBIT)
+ pkt_info->addr.lastrepeat = d;
+ ++d;
+ pkt_info->addr.dcount = d;
+ } else
+ return NULL;
+ }
+
+ return buf + AX25_ADDR_LEN;
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Assemble an AX.25 header from the bits
+ */
+int ax25_build_addr(unsigned char *buf, ax25_addr_t *addr, int flag, int seqmask)
+{
+ int len = 0;
+ int ct = 0;
+
+ memcpy(buf, &addr->dest, AX25_ADDR_LEN);
+ buf[6] &= ~(AX25_EBIT | AX25_CBIT);
+ buf[6] |= AX25_SSSID_SPARE;
+
+ if (flag == AX25_COMMAND) buf[6] |= AX25_CBIT;
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+
+ memcpy(buf, &addr->src, AX25_ADDR_LEN);
+ buf[6] &= ~(AX25_EBIT | AX25_CBIT);
+ buf[6] &= ~AX25_SSSID_SPARE;
+
+ if (seqmask == AX25_SEQMASK)
+ buf[6] |= AX25_SSSID_SPARE;
+ else
+ buf[6] |= AX25_ESSID_SPARE;
+
+ if (flag == AX25_RESPONSE)
+ buf[6] |= AX25_CBIT;
+
+ /*
+ * Fast path the normal digiless path
+ */
+ if (addr->dcount == 0) {
+ buf[6] |= AX25_EBIT;
+ return 2 * AX25_ADDR_LEN;
+ }
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+
+ while (ct < addr->dcount) {
+ memcpy(buf, &addr->digipeater[ct], AX25_ADDR_LEN);
+
+ if (ct <= addr->lastrepeat)
+ buf[6] |= AX25_HBIT;
+ else
+ buf[6] &= ~AX25_HBIT;
+
+ buf[6] &= ~AX25_EBIT;
+ buf[6] |= AX25_SSSID_SPARE;
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+ ct++;
+ }
+
+ buf[-1] |= AX25_EBIT;
+
+ return len;
+}
+
+/* ---------------------------------------------------------------------*/
+
+int ax25_sizeof_addr(ax25_addr_t *addr)
+{
+ return AX25_ADDR_LEN * (addr->dcount+2);
+}
+
+/* ---------------------------------------------------------------------*/
+/*
+ * Invert AX.25 address. May not pass both parameters as same struct
+ */
+void ax25_invert_addr(ax25_addr_t *in, ax25_addr_t *out)
+{
+ ax25_address *ip, *op;
+ int dcount;
+
+ dcount = out->dcount = in->dcount;
+ out->lastrepeat = dcount - in->lastrepeat - 2;
+
+ /* source/destination */
+ out->dest = in->src;
+ out->src = in->dest;
+
+ /* Invert the digipeaters */
+ if (dcount) {
+ ip = in->digipeater;
+ op = out->digipeater + (dcount-1); /* pointer scaled! */
+ while (dcount--)
+ *op-- = *ip++;
+ }
+}