summaryrefslogtreecommitdiffstats
path: root/net/ax25/ax25_out.c
blob: a222ad1e8a0c6ee8ec73328ef5674e0c05393c58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * ax25_out.c:  Subroutines for outgoing packets
 *
 * Authors:     Jens David (DG1KJD), Matthias Welwarsky (DG2FEF), Jonathan (G4KL
 *              Alan Cox (GW4PTS), Joerg (DL1BKE), et al
 *
 * Comment:     Most of this code is based on the SDL diagrams published in the
 *              ARRL Computer Networking Conference papers. The diagrams have mi
 *              in them, but are mostly correct. Before you modify the code coul
 *              read the SDL diagrams as the code is not obvious and probably ve
 *              easy to break;
 *
 * 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/config.h>
#include <linux/skbuff.h>
#include <net/ax25.h>
#include <net/ax25dev.h>
#include <net/sock.h>

#include "ax25_core.h"
#include "ax25_route.h"
#include "ax25_vj.h"
#include "ax25_ddi.h"
#include "ax25_subr.h"

/* ---------------------------------------------------------------------*/
/*
 *      send an iframe on a connection, maybe establish the connection first.
 */
ax25_cb *ax25_send_frame(struct sk_buff *skb, int paclen, ax25_addr_t *addr, struct net_device *dev)
{
	ax25_cb *ax25;

	if (paclen == 0)
		paclen = ax25_dev_get_value(dev, AX25_VALUES_PACLEN);

	/*
	 * Look for an existing connection.
	 */
	if ((ax25 = ax25_find_cb(addr, dev)) != NULL) {
		/* reuse a just disconnected control block */
		if (ax25->state == AX25_STATE_0 || ax25->state == AX25_STATE_2) {
			if (ax25->slcomp) {
				axhc_free(ax25->slcomp);
				ax25->slcomp = NULL;
			}
			ax25->slcomp_enable = ax25_rt_mode_get(&addr->dest) == 'C';
			ax25_establish_data_link(ax25);
		}
		ax25_output(ax25, paclen, skb);
		return ax25;		/* It already existed */
	}

	if ((ax25 = ax25_create_cb()) == NULL)
		return NULL;

	ax25_fillin_cb(ax25, dev);

	ax25->addr = *addr;
	ax25->slcomp_enable = ax25_rt_mode_get(&addr->dest) == 'C';

	ax25_establish_data_link(ax25);
	ax25_insert_cb(ax25);
	ax25_output(ax25, paclen, skb);
	return ax25;			/* We had to create it */
}

/*
 *	All outgoing AX.25 I frames pass via this routine. Therefore this is
 *	where the fragmentation of frames takes place. If fragment is set to
 *	zero then we are not allowed to do fragmentation, even if the frame
 *	is too large.
 */
void ax25_output(ax25_cb *ax25, int paclen, struct sk_buff *skb)
{
	struct sk_buff *skbn;
	unsigned char *p;
	int frontlen, len, fragno, ka9qfrag, first = 1;

	if ((skb->len - 1) > paclen) {
		if (*skb->data == AX25_P_TEXT) {
			skb_pull(skb, 1); /* skip PID */
			ka9qfrag = 0;
		} else {
			paclen -= 2;	/* Allow for fragment control info */
			ka9qfrag = 1;
		}

		fragno = skb->len / paclen;
		if (skb->len % paclen == 0) fragno--;

		frontlen = skb_headroom(skb);	/* Address space + CTRL */

		while (skb->len > 0) {
			if ((skbn = alloc_skb(paclen + 2 + frontlen, GFP_ATOMIC)) == NULL) {
				printk(KERN_CRIT "AX.25: ax25_output - out of memory\n");
				return;
			}

			if (skb->sk != NULL)
				skb_set_owner_w(skbn, skb->sk);
			
			len = (paclen > skb->len) ? skb->len : paclen;

			if (ka9qfrag == 1) {
				skb_reserve(skbn, frontlen + 2);

				memcpy(skb_put(skbn, len), skb->data, len);
				p = skb_push(skbn, 2);

				*p++ = AX25_P_SEGMENT;

				*p = fragno--;
				if (first) {
					*p |= AX25_SEG_FIRST;
					first = 0;
				}
			} else {
				skb_reserve(skbn, frontlen + 1);
				memcpy(skb_put(skbn, len), skb->data, len);
				p = skb_push(skbn, 1);
				*p = AX25_P_TEXT;
			}

			skb_pull(skb, len);
			skb_queue_tail(&ax25->write_queue, skbn); /* Throw it on the queue */
		}

		kfree_skb(skb);
	} else {
		skb_queue_tail(&ax25->write_queue, skb);	  /* Throw it on the queue */
	}

	ax25_kick(ax25);
}