diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /net/llc/llc_conn.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'net/llc/llc_conn.c')
-rw-r--r-- | net/llc/llc_conn.c | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c new file mode 100644 index 000000000000..eba812a9c69c --- /dev/null +++ b/net/llc/llc_conn.c | |||
@@ -0,0 +1,915 @@ | |||
1 | /* | ||
2 | * llc_conn.c - Driver routines for connection component. | ||
3 | * | ||
4 | * Copyright (c) 1997 by Procom Technology, Inc. | ||
5 | * 2001-2003 by Arnaldo Carvalho de Melo <acme@conectiva.com.br> | ||
6 | * | ||
7 | * This program can be redistributed or modified under the terms of the | ||
8 | * GNU General Public License as published by the Free Software Foundation. | ||
9 | * This program is distributed without any warranty or implied warranty | ||
10 | * of merchantability or fitness for a particular purpose. | ||
11 | * | ||
12 | * See the GNU General Public License for more details. | ||
13 | */ | ||
14 | |||
15 | #include <linux/init.h> | ||
16 | #include <net/llc_sap.h> | ||
17 | #include <net/llc_conn.h> | ||
18 | #include <net/sock.h> | ||
19 | #include <linux/tcp.h> | ||
20 | #include <net/llc_c_ev.h> | ||
21 | #include <net/llc_c_ac.h> | ||
22 | #include <net/llc_c_st.h> | ||
23 | #include <net/llc_pdu.h> | ||
24 | |||
25 | #if 0 | ||
26 | #define dprintk(args...) printk(KERN_DEBUG args) | ||
27 | #else | ||
28 | #define dprintk(args...) | ||
29 | #endif | ||
30 | |||
31 | static int llc_find_offset(int state, int ev_type); | ||
32 | static void llc_conn_send_pdus(struct sock *sk); | ||
33 | static int llc_conn_service(struct sock *sk, struct sk_buff *skb); | ||
34 | static int llc_exec_conn_trans_actions(struct sock *sk, | ||
35 | struct llc_conn_state_trans *trans, | ||
36 | struct sk_buff *ev); | ||
37 | static struct llc_conn_state_trans *llc_qualify_conn_ev(struct sock *sk, | ||
38 | struct sk_buff *skb); | ||
39 | |||
40 | /* Offset table on connection states transition diagram */ | ||
41 | static int llc_offset_table[NBR_CONN_STATES][NBR_CONN_EV]; | ||
42 | |||
43 | /** | ||
44 | * llc_conn_state_process - sends event to connection state machine | ||
45 | * @sk: connection | ||
46 | * @skb: occurred event | ||
47 | * | ||
48 | * Sends an event to connection state machine. After processing event | ||
49 | * (executing it's actions and changing state), upper layer will be | ||
50 | * indicated or confirmed, if needed. Returns 0 for success, 1 for | ||
51 | * failure. The socket lock has to be held before calling this function. | ||
52 | */ | ||
53 | int llc_conn_state_process(struct sock *sk, struct sk_buff *skb) | ||
54 | { | ||
55 | int rc; | ||
56 | struct llc_sock *llc = llc_sk(sk); | ||
57 | struct llc_conn_state_ev *ev = llc_conn_ev(skb); | ||
58 | |||
59 | /* | ||
60 | * We have to hold the skb, because llc_conn_service will kfree it in | ||
61 | * the sending path and we need to look at the skb->cb, where we encode | ||
62 | * llc_conn_state_ev. | ||
63 | */ | ||
64 | skb_get(skb); | ||
65 | ev->ind_prim = ev->cfm_prim = 0; | ||
66 | rc = llc_conn_service(sk, skb); /* sending event to state machine */ | ||
67 | if (rc) { | ||
68 | printk(KERN_ERR "%s: llc_conn_service failed\n", __FUNCTION__); | ||
69 | goto out_kfree_skb; | ||
70 | } | ||
71 | |||
72 | if (!ev->ind_prim && !ev->cfm_prim) { | ||
73 | /* indicate or confirm not required */ | ||
74 | if (!skb->list) | ||
75 | goto out_kfree_skb; | ||
76 | goto out_skb_put; | ||
77 | } | ||
78 | |||
79 | if (ev->ind_prim && ev->cfm_prim) /* Paranoia */ | ||
80 | skb_get(skb); | ||
81 | |||
82 | switch (ev->ind_prim) { | ||
83 | case LLC_DATA_PRIM: | ||
84 | llc_save_primitive(skb, LLC_DATA_PRIM); | ||
85 | if (sock_queue_rcv_skb(sk, skb)) { | ||
86 | /* | ||
87 | * shouldn't happen | ||
88 | */ | ||
89 | printk(KERN_ERR "%s: sock_queue_rcv_skb failed!\n", | ||
90 | __FUNCTION__); | ||
91 | kfree_skb(skb); | ||
92 | } | ||
93 | break; | ||
94 | case LLC_CONN_PRIM: { | ||
95 | struct sock *parent = skb->sk; | ||
96 | |||
97 | skb->sk = sk; | ||
98 | skb_queue_tail(&parent->sk_receive_queue, skb); | ||
99 | sk->sk_state_change(parent); | ||
100 | } | ||
101 | break; | ||
102 | case LLC_DISC_PRIM: | ||
103 | sock_hold(sk); | ||
104 | if (sk->sk_type == SOCK_STREAM && | ||
105 | sk->sk_state == TCP_ESTABLISHED) { | ||
106 | sk->sk_shutdown = SHUTDOWN_MASK; | ||
107 | sk->sk_socket->state = SS_UNCONNECTED; | ||
108 | sk->sk_state = TCP_CLOSE; | ||
109 | if (!sock_flag(sk, SOCK_DEAD)) { | ||
110 | sk->sk_state_change(sk); | ||
111 | sock_set_flag(sk, SOCK_DEAD); | ||
112 | } | ||
113 | } | ||
114 | kfree_skb(skb); | ||
115 | sock_put(sk); | ||
116 | break; | ||
117 | case LLC_RESET_PRIM: | ||
118 | /* | ||
119 | * FIXME: | ||
120 | * RESET is not being notified to upper layers for now | ||
121 | */ | ||
122 | printk(KERN_INFO "%s: received a reset ind!\n", __FUNCTION__); | ||
123 | kfree_skb(skb); | ||
124 | break; | ||
125 | default: | ||
126 | if (ev->ind_prim) { | ||
127 | printk(KERN_INFO "%s: received unknown %d prim!\n", | ||
128 | __FUNCTION__, ev->ind_prim); | ||
129 | kfree_skb(skb); | ||
130 | } | ||
131 | /* No indication */ | ||
132 | break; | ||
133 | } | ||
134 | |||
135 | switch (ev->cfm_prim) { | ||
136 | case LLC_DATA_PRIM: | ||
137 | if (!llc_data_accept_state(llc->state)) | ||
138 | sk->sk_write_space(sk); | ||
139 | else | ||
140 | rc = llc->failed_data_req = 1; | ||
141 | break; | ||
142 | case LLC_CONN_PRIM: | ||
143 | if (sk->sk_type == SOCK_STREAM && | ||
144 | sk->sk_state == TCP_SYN_SENT) { | ||
145 | if (ev->status) { | ||
146 | sk->sk_socket->state = SS_UNCONNECTED; | ||
147 | sk->sk_state = TCP_CLOSE; | ||
148 | } else { | ||
149 | sk->sk_socket->state = SS_CONNECTED; | ||
150 | sk->sk_state = TCP_ESTABLISHED; | ||
151 | } | ||
152 | sk->sk_state_change(sk); | ||
153 | } | ||
154 | break; | ||
155 | case LLC_DISC_PRIM: | ||
156 | sock_hold(sk); | ||
157 | if (sk->sk_type == SOCK_STREAM && sk->sk_state == TCP_CLOSING) { | ||
158 | sk->sk_socket->state = SS_UNCONNECTED; | ||
159 | sk->sk_state = TCP_CLOSE; | ||
160 | sk->sk_state_change(sk); | ||
161 | } | ||
162 | sock_put(sk); | ||
163 | break; | ||
164 | case LLC_RESET_PRIM: | ||
165 | /* | ||
166 | * FIXME: | ||
167 | * RESET is not being notified to upper layers for now | ||
168 | */ | ||
169 | printk(KERN_INFO "%s: received a reset conf!\n", __FUNCTION__); | ||
170 | break; | ||
171 | default: | ||
172 | if (ev->cfm_prim) { | ||
173 | printk(KERN_INFO "%s: received unknown %d prim!\n", | ||
174 | __FUNCTION__, ev->cfm_prim); | ||
175 | break; | ||
176 | } | ||
177 | goto out_skb_put; /* No confirmation */ | ||
178 | } | ||
179 | out_kfree_skb: | ||
180 | kfree_skb(skb); | ||
181 | out_skb_put: | ||
182 | kfree_skb(skb); | ||
183 | return rc; | ||
184 | } | ||
185 | |||
186 | void llc_conn_send_pdu(struct sock *sk, struct sk_buff *skb) | ||
187 | { | ||
188 | /* queue PDU to send to MAC layer */ | ||
189 | skb_queue_tail(&sk->sk_write_queue, skb); | ||
190 | llc_conn_send_pdus(sk); | ||
191 | } | ||
192 | |||
193 | /** | ||
194 | * llc_conn_rtn_pdu - sends received data pdu to upper layer | ||
195 | * @sk: Active connection | ||
196 | * @skb: Received data frame | ||
197 | * | ||
198 | * Sends received data pdu to upper layer (by using indicate function). | ||
199 | * Prepares service parameters (prim and prim_data). calling indication | ||
200 | * function will be done in llc_conn_state_process. | ||
201 | */ | ||
202 | void llc_conn_rtn_pdu(struct sock *sk, struct sk_buff *skb) | ||
203 | { | ||
204 | struct llc_conn_state_ev *ev = llc_conn_ev(skb); | ||
205 | |||
206 | ev->ind_prim = LLC_DATA_PRIM; | ||
207 | } | ||
208 | |||
209 | /** | ||
210 | * llc_conn_resend_i_pdu_as_cmd - resend all all unacknowledged I PDUs | ||
211 | * @sk: active connection | ||
212 | * @nr: NR | ||
213 | * @first_p_bit: p_bit value of first pdu | ||
214 | * | ||
215 | * Resend all unacknowledged I PDUs, starting with the NR; send first as | ||
216 | * command PDU with P bit equal first_p_bit; if more than one send | ||
217 | * subsequent as command PDUs with P bit equal zero (0). | ||
218 | */ | ||
219 | void llc_conn_resend_i_pdu_as_cmd(struct sock *sk, u8 nr, u8 first_p_bit) | ||
220 | { | ||
221 | struct sk_buff *skb; | ||
222 | struct llc_pdu_sn *pdu; | ||
223 | u16 nbr_unack_pdus; | ||
224 | struct llc_sock *llc; | ||
225 | u8 howmany_resend = 0; | ||
226 | |||
227 | llc_conn_remove_acked_pdus(sk, nr, &nbr_unack_pdus); | ||
228 | if (!nbr_unack_pdus) | ||
229 | goto out; | ||
230 | /* | ||
231 | * Process unack PDUs only if unack queue is not empty; remove | ||
232 | * appropriate PDUs, fix them up, and put them on mac_pdu_q. | ||
233 | */ | ||
234 | llc = llc_sk(sk); | ||
235 | |||
236 | while ((skb = skb_dequeue(&llc->pdu_unack_q)) != NULL) { | ||
237 | pdu = llc_pdu_sn_hdr(skb); | ||
238 | llc_pdu_set_cmd_rsp(skb, LLC_PDU_CMD); | ||
239 | llc_pdu_set_pf_bit(skb, first_p_bit); | ||
240 | skb_queue_tail(&sk->sk_write_queue, skb); | ||
241 | first_p_bit = 0; | ||
242 | llc->vS = LLC_I_GET_NS(pdu); | ||
243 | howmany_resend++; | ||
244 | } | ||
245 | if (howmany_resend > 0) | ||
246 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; | ||
247 | /* any PDUs to re-send are queued up; start sending to MAC */ | ||
248 | llc_conn_send_pdus(sk); | ||
249 | out:; | ||
250 | } | ||
251 | |||
252 | /** | ||
253 | * llc_conn_resend_i_pdu_as_rsp - Resend all unacknowledged I PDUs | ||
254 | * @sk: active connection. | ||
255 | * @nr: NR | ||
256 | * @first_f_bit: f_bit value of first pdu. | ||
257 | * | ||
258 | * Resend all unacknowledged I PDUs, starting with the NR; send first as | ||
259 | * response PDU with F bit equal first_f_bit; if more than one send | ||
260 | * subsequent as response PDUs with F bit equal zero (0). | ||
261 | */ | ||
262 | void llc_conn_resend_i_pdu_as_rsp(struct sock *sk, u8 nr, u8 first_f_bit) | ||
263 | { | ||
264 | struct sk_buff *skb; | ||
265 | u16 nbr_unack_pdus; | ||
266 | struct llc_sock *llc = llc_sk(sk); | ||
267 | u8 howmany_resend = 0; | ||
268 | |||
269 | llc_conn_remove_acked_pdus(sk, nr, &nbr_unack_pdus); | ||
270 | if (!nbr_unack_pdus) | ||
271 | goto out; | ||
272 | /* | ||
273 | * Process unack PDUs only if unack queue is not empty; remove | ||
274 | * appropriate PDUs, fix them up, and put them on mac_pdu_q | ||
275 | */ | ||
276 | while ((skb = skb_dequeue(&llc->pdu_unack_q)) != NULL) { | ||
277 | struct llc_pdu_sn *pdu = llc_pdu_sn_hdr(skb); | ||
278 | |||
279 | llc_pdu_set_cmd_rsp(skb, LLC_PDU_RSP); | ||
280 | llc_pdu_set_pf_bit(skb, first_f_bit); | ||
281 | skb_queue_tail(&sk->sk_write_queue, skb); | ||
282 | first_f_bit = 0; | ||
283 | llc->vS = LLC_I_GET_NS(pdu); | ||
284 | howmany_resend++; | ||
285 | } | ||
286 | if (howmany_resend > 0) | ||
287 | llc->vS = (llc->vS + 1) % LLC_2_SEQ_NBR_MODULO; | ||
288 | /* any PDUs to re-send are queued up; start sending to MAC */ | ||
289 | llc_conn_send_pdus(sk); | ||
290 | out:; | ||
291 | } | ||
292 | |||
293 | /** | ||
294 | * llc_conn_remove_acked_pdus - Removes acknowledged pdus from tx queue | ||
295 | * @sk: active connection | ||
296 | * nr: NR | ||
297 | * how_many_unacked: size of pdu_unack_q after removing acked pdus | ||
298 | * | ||
299 | * Removes acknowledged pdus from transmit queue (pdu_unack_q). Returns | ||
300 | * the number of pdus that removed from queue. | ||
301 | */ | ||
302 | int llc_conn_remove_acked_pdus(struct sock *sk, u8 nr, u16 *how_many_unacked) | ||
303 | { | ||
304 | int pdu_pos, i; | ||
305 | struct sk_buff *skb; | ||
306 | struct llc_pdu_sn *pdu; | ||
307 | int nbr_acked = 0; | ||
308 | struct llc_sock *llc = llc_sk(sk); | ||
309 | int q_len = skb_queue_len(&llc->pdu_unack_q); | ||
310 | |||
311 | if (!q_len) | ||
312 | goto out; | ||
313 | skb = skb_peek(&llc->pdu_unack_q); | ||
314 | pdu = llc_pdu_sn_hdr(skb); | ||
315 | |||
316 | /* finding position of last acked pdu in queue */ | ||
317 | pdu_pos = ((int)LLC_2_SEQ_NBR_MODULO + (int)nr - | ||
318 | (int)LLC_I_GET_NS(pdu)) % LLC_2_SEQ_NBR_MODULO; | ||
319 | |||
320 | for (i = 0; i < pdu_pos && i < q_len; i++) { | ||
321 | skb = skb_dequeue(&llc->pdu_unack_q); | ||
322 | if (skb) | ||
323 | kfree_skb(skb); | ||
324 | nbr_acked++; | ||
325 | } | ||
326 | out: | ||
327 | *how_many_unacked = skb_queue_len(&llc->pdu_unack_q); | ||
328 | return nbr_acked; | ||
329 | } | ||
330 | |||
331 | /** | ||
332 | * llc_conn_send_pdus - Sends queued PDUs | ||
333 | * @sk: active connection | ||
334 | * | ||
335 | * Sends queued pdus to MAC layer for transmission. | ||
336 | */ | ||
337 | static void llc_conn_send_pdus(struct sock *sk) | ||
338 | { | ||
339 | struct sk_buff *skb; | ||
340 | |||
341 | while ((skb = skb_dequeue(&sk->sk_write_queue)) != NULL) { | ||
342 | struct llc_pdu_sn *pdu = llc_pdu_sn_hdr(skb); | ||
343 | |||
344 | if (LLC_PDU_TYPE_IS_I(pdu) && | ||
345 | !(skb->dev->flags & IFF_LOOPBACK)) { | ||
346 | struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); | ||
347 | |||
348 | skb_queue_tail(&llc_sk(sk)->pdu_unack_q, skb); | ||
349 | if (!skb2) | ||
350 | break; | ||
351 | skb = skb2; | ||
352 | } | ||
353 | dev_queue_xmit(skb); | ||
354 | } | ||
355 | } | ||
356 | |||
357 | /** | ||
358 | * llc_conn_service - finds transition and changes state of connection | ||
359 | * @sk: connection | ||
360 | * @skb: happened event | ||
361 | * | ||
362 | * This function finds transition that matches with happened event, then | ||
363 | * executes related actions and finally changes state of connection. | ||
364 | * Returns 0 for success, 1 for failure. | ||
365 | */ | ||
366 | static int llc_conn_service(struct sock *sk, struct sk_buff *skb) | ||
367 | { | ||
368 | int rc = 1; | ||
369 | struct llc_sock *llc = llc_sk(sk); | ||
370 | struct llc_conn_state_trans *trans; | ||
371 | |||
372 | if (llc->state > NBR_CONN_STATES) | ||
373 | goto out; | ||
374 | rc = 0; | ||
375 | trans = llc_qualify_conn_ev(sk, skb); | ||
376 | if (trans) { | ||
377 | rc = llc_exec_conn_trans_actions(sk, trans, skb); | ||
378 | if (!rc && trans->next_state != NO_STATE_CHANGE) { | ||
379 | llc->state = trans->next_state; | ||
380 | if (!llc_data_accept_state(llc->state)) | ||
381 | sk->sk_state_change(sk); | ||
382 | } | ||
383 | } | ||
384 | out: | ||
385 | return rc; | ||
386 | } | ||
387 | |||
388 | /** | ||
389 | * llc_qualify_conn_ev - finds transition for event | ||
390 | * @sk: connection | ||
391 | * @skb: happened event | ||
392 | * | ||
393 | * This function finds transition that matches with happened event. | ||
394 | * Returns pointer to found transition on success, %NULL otherwise. | ||
395 | */ | ||
396 | static struct llc_conn_state_trans *llc_qualify_conn_ev(struct sock *sk, | ||
397 | struct sk_buff *skb) | ||
398 | { | ||
399 | struct llc_conn_state_trans **next_trans; | ||
400 | llc_conn_ev_qfyr_t *next_qualifier; | ||
401 | struct llc_conn_state_ev *ev = llc_conn_ev(skb); | ||
402 | struct llc_sock *llc = llc_sk(sk); | ||
403 | struct llc_conn_state *curr_state = | ||
404 | &llc_conn_state_table[llc->state - 1]; | ||
405 | |||
406 | /* search thru events for this state until | ||
407 | * list exhausted or until no more | ||
408 | */ | ||
409 | for (next_trans = curr_state->transitions + | ||
410 | llc_find_offset(llc->state - 1, ev->type); | ||
411 | (*next_trans)->ev; next_trans++) { | ||
412 | if (!((*next_trans)->ev)(sk, skb)) { | ||
413 | /* got POSSIBLE event match; the event may require | ||
414 | * qualification based on the values of a number of | ||
415 | * state flags; if all qualifications are met (i.e., | ||
416 | * if all qualifying functions return success, or 0, | ||
417 | * then this is THE event we're looking for | ||
418 | */ | ||
419 | for (next_qualifier = (*next_trans)->ev_qualifiers; | ||
420 | next_qualifier && *next_qualifier && | ||
421 | !(*next_qualifier)(sk, skb); next_qualifier++) | ||
422 | /* nothing */; | ||
423 | if (!next_qualifier || !*next_qualifier) | ||
424 | /* all qualifiers executed successfully; this is | ||
425 | * our transition; return it so we can perform | ||
426 | * the associated actions & change the state | ||
427 | */ | ||
428 | return *next_trans; | ||
429 | } | ||
430 | } | ||
431 | return NULL; | ||
432 | } | ||
433 | |||
434 | /** | ||
435 | * llc_exec_conn_trans_actions - executes related actions | ||
436 | * @sk: connection | ||
437 | * @trans: transition that it's actions must be performed | ||
438 | * @skb: event | ||
439 | * | ||
440 | * Executes actions that is related to happened event. Returns 0 for | ||
441 | * success, 1 to indicate failure of at least one action. | ||
442 | */ | ||
443 | static int llc_exec_conn_trans_actions(struct sock *sk, | ||
444 | struct llc_conn_state_trans *trans, | ||
445 | struct sk_buff *skb) | ||
446 | { | ||
447 | int rc = 0; | ||
448 | llc_conn_action_t *next_action; | ||
449 | |||
450 | for (next_action = trans->ev_actions; | ||
451 | next_action && *next_action; next_action++) { | ||
452 | int rc2 = (*next_action)(sk, skb); | ||
453 | |||
454 | if (rc2 == 2) { | ||
455 | rc = rc2; | ||
456 | break; | ||
457 | } else if (rc2) | ||
458 | rc = 1; | ||
459 | } | ||
460 | return rc; | ||
461 | } | ||
462 | |||
463 | /** | ||
464 | * llc_lookup_established - Finds connection for the remote/local sap/mac | ||
465 | * @sap: SAP | ||
466 | * @daddr: address of remote LLC (MAC + SAP) | ||
467 | * @laddr: address of local LLC (MAC + SAP) | ||
468 | * | ||
469 | * Search connection list of the SAP and finds connection using the remote | ||
470 | * mac, remote sap, local mac, and local sap. Returns pointer for | ||
471 | * connection found, %NULL otherwise. | ||
472 | */ | ||
473 | struct sock *llc_lookup_established(struct llc_sap *sap, struct llc_addr *daddr, | ||
474 | struct llc_addr *laddr) | ||
475 | { | ||
476 | struct sock *rc; | ||
477 | struct hlist_node *node; | ||
478 | |||
479 | read_lock_bh(&sap->sk_list.lock); | ||
480 | sk_for_each(rc, node, &sap->sk_list.list) { | ||
481 | struct llc_sock *llc = llc_sk(rc); | ||
482 | |||
483 | if (llc->laddr.lsap == laddr->lsap && | ||
484 | llc->daddr.lsap == daddr->lsap && | ||
485 | llc_mac_match(llc->laddr.mac, laddr->mac) && | ||
486 | llc_mac_match(llc->daddr.mac, daddr->mac)) { | ||
487 | sock_hold(rc); | ||
488 | goto found; | ||
489 | } | ||
490 | } | ||
491 | rc = NULL; | ||
492 | found: | ||
493 | read_unlock_bh(&sap->sk_list.lock); | ||
494 | return rc; | ||
495 | } | ||
496 | |||
497 | /** | ||
498 | * llc_lookup_listener - Finds listener for local MAC + SAP | ||
499 | * @sap: SAP | ||
500 | * @laddr: address of local LLC (MAC + SAP) | ||
501 | * | ||
502 | * Search connection list of the SAP and finds connection listening on | ||
503 | * local mac, and local sap. Returns pointer for parent socket found, | ||
504 | * %NULL otherwise. | ||
505 | */ | ||
506 | static struct sock *llc_lookup_listener(struct llc_sap *sap, | ||
507 | struct llc_addr *laddr) | ||
508 | { | ||
509 | struct sock *rc; | ||
510 | struct hlist_node *node; | ||
511 | |||
512 | read_lock_bh(&sap->sk_list.lock); | ||
513 | sk_for_each(rc, node, &sap->sk_list.list) { | ||
514 | struct llc_sock *llc = llc_sk(rc); | ||
515 | |||
516 | if (rc->sk_type == SOCK_STREAM && rc->sk_state == TCP_LISTEN && | ||
517 | llc->laddr.lsap == laddr->lsap && | ||
518 | (llc_mac_match(llc->laddr.mac, laddr->mac) || | ||
519 | llc_mac_null(llc->laddr.mac))) { | ||
520 | sock_hold(rc); | ||
521 | goto found; | ||
522 | } | ||
523 | } | ||
524 | rc = NULL; | ||
525 | found: | ||
526 | read_unlock_bh(&sap->sk_list.lock); | ||
527 | return rc; | ||
528 | } | ||
529 | |||
530 | /** | ||
531 | * llc_data_accept_state - designates if in this state data can be sent. | ||
532 | * @state: state of connection. | ||
533 | * | ||
534 | * Returns 0 if data can be sent, 1 otherwise. | ||
535 | */ | ||
536 | u8 llc_data_accept_state(u8 state) | ||
537 | { | ||
538 | return state != LLC_CONN_STATE_NORMAL && state != LLC_CONN_STATE_BUSY && | ||
539 | state != LLC_CONN_STATE_REJ; | ||
540 | } | ||
541 | |||
542 | /** | ||
543 | * find_next_offset - finds offset for next category of transitions | ||
544 | * @state: state table. | ||
545 | * @offset: start offset. | ||
546 | * | ||
547 | * Finds offset of next category of transitions in transition table. | ||
548 | * Returns the start index of next category. | ||
549 | */ | ||
550 | static u16 find_next_offset(struct llc_conn_state *state, u16 offset) | ||
551 | { | ||
552 | u16 cnt = 0; | ||
553 | struct llc_conn_state_trans **next_trans; | ||
554 | |||
555 | for (next_trans = state->transitions + offset; | ||
556 | (*next_trans)->ev; next_trans++) | ||
557 | ++cnt; | ||
558 | return cnt; | ||
559 | } | ||
560 | |||
561 | /** | ||
562 | * llc_build_offset_table - builds offset table of connection | ||
563 | * | ||
564 | * Fills offset table of connection state transition table | ||
565 | * (llc_offset_table). | ||
566 | */ | ||
567 | void __init llc_build_offset_table(void) | ||
568 | { | ||
569 | struct llc_conn_state *curr_state; | ||
570 | int state, ev_type, next_offset; | ||
571 | |||
572 | for (state = 0; state < NBR_CONN_STATES; state++) { | ||
573 | curr_state = &llc_conn_state_table[state]; | ||
574 | next_offset = 0; | ||
575 | for (ev_type = 0; ev_type < NBR_CONN_EV; ev_type++) { | ||
576 | llc_offset_table[state][ev_type] = next_offset; | ||
577 | next_offset += find_next_offset(curr_state, | ||
578 | next_offset) + 1; | ||
579 | } | ||
580 | } | ||
581 | } | ||
582 | |||
583 | /** | ||
584 | * llc_find_offset - finds start offset of category of transitions | ||
585 | * @state: state of connection | ||
586 | * @ev_type: type of happened event | ||
587 | * | ||
588 | * Finds start offset of desired category of transitions. Returns the | ||
589 | * desired start offset. | ||
590 | */ | ||
591 | static int llc_find_offset(int state, int ev_type) | ||
592 | { | ||
593 | int rc = 0; | ||
594 | /* at this stage, llc_offset_table[..][2] is not important. it is for | ||
595 | * init_pf_cycle and I don't know what is it. | ||
596 | */ | ||
597 | switch (ev_type) { | ||
598 | case LLC_CONN_EV_TYPE_PRIM: | ||
599 | rc = llc_offset_table[state][0]; break; | ||
600 | case LLC_CONN_EV_TYPE_PDU: | ||
601 | rc = llc_offset_table[state][4]; break; | ||
602 | case LLC_CONN_EV_TYPE_SIMPLE: | ||
603 | rc = llc_offset_table[state][1]; break; | ||
604 | case LLC_CONN_EV_TYPE_P_TMR: | ||
605 | case LLC_CONN_EV_TYPE_ACK_TMR: | ||
606 | case LLC_CONN_EV_TYPE_REJ_TMR: | ||
607 | case LLC_CONN_EV_TYPE_BUSY_TMR: | ||
608 | rc = llc_offset_table[state][3]; break; | ||
609 | } | ||
610 | return rc; | ||
611 | } | ||
612 | |||
613 | /** | ||
614 | * llc_sap_add_socket - adds a socket to a SAP | ||
615 | * @sap: SAP | ||
616 | * @sk: socket | ||
617 | * | ||
618 | * This function adds a socket to sk_list of a SAP. | ||
619 | */ | ||
620 | void llc_sap_add_socket(struct llc_sap *sap, struct sock *sk) | ||
621 | { | ||
622 | write_lock_bh(&sap->sk_list.lock); | ||
623 | llc_sk(sk)->sap = sap; | ||
624 | sk_add_node(sk, &sap->sk_list.list); | ||
625 | write_unlock_bh(&sap->sk_list.lock); | ||
626 | } | ||
627 | |||
628 | /** | ||
629 | * llc_sap_remove_socket - removes a socket from SAP | ||
630 | * @sap: SAP | ||
631 | * @sk: socket | ||
632 | * | ||
633 | * This function removes a connection from sk_list.list of a SAP if | ||
634 | * the connection was in this list. | ||
635 | */ | ||
636 | void llc_sap_remove_socket(struct llc_sap *sap, struct sock *sk) | ||
637 | { | ||
638 | write_lock_bh(&sap->sk_list.lock); | ||
639 | sk_del_node_init(sk); | ||
640 | write_unlock_bh(&sap->sk_list.lock); | ||
641 | } | ||
642 | |||
643 | /** | ||
644 | * llc_conn_rcv - sends received pdus to the connection state machine | ||
645 | * @sk: current connection structure. | ||
646 | * @skb: received frame. | ||
647 | * | ||
648 | * Sends received pdus to the connection state machine. | ||
649 | */ | ||
650 | static int llc_conn_rcv(struct sock* sk, struct sk_buff *skb) | ||
651 | { | ||
652 | struct llc_conn_state_ev *ev = llc_conn_ev(skb); | ||
653 | struct llc_sock *llc = llc_sk(sk); | ||
654 | |||
655 | if (!llc->dev) | ||
656 | llc->dev = skb->dev; | ||
657 | ev->type = LLC_CONN_EV_TYPE_PDU; | ||
658 | ev->reason = 0; | ||
659 | return llc_conn_state_process(sk, skb); | ||
660 | } | ||
661 | |||
662 | void llc_conn_handler(struct llc_sap *sap, struct sk_buff *skb) | ||
663 | { | ||
664 | struct llc_addr saddr, daddr; | ||
665 | struct sock *sk; | ||
666 | |||
667 | llc_pdu_decode_sa(skb, saddr.mac); | ||
668 | llc_pdu_decode_ssap(skb, &saddr.lsap); | ||
669 | llc_pdu_decode_da(skb, daddr.mac); | ||
670 | llc_pdu_decode_dsap(skb, &daddr.lsap); | ||
671 | |||
672 | sk = llc_lookup_established(sap, &saddr, &daddr); | ||
673 | if (!sk) { | ||
674 | /* | ||
675 | * Didn't find an active connection; verify if there | ||
676 | * is a listening socket for this llc addr | ||
677 | */ | ||
678 | struct llc_sock *llc; | ||
679 | struct sock *parent = llc_lookup_listener(sap, &daddr); | ||
680 | |||
681 | if (!parent) { | ||
682 | dprintk("llc_lookup_listener failed!\n"); | ||
683 | goto drop; | ||
684 | } | ||
685 | |||
686 | sk = llc_sk_alloc(parent->sk_family, GFP_ATOMIC, parent->sk_prot); | ||
687 | if (!sk) { | ||
688 | sock_put(parent); | ||
689 | goto drop; | ||
690 | } | ||
691 | llc = llc_sk(sk); | ||
692 | memcpy(&llc->laddr, &daddr, sizeof(llc->laddr)); | ||
693 | memcpy(&llc->daddr, &saddr, sizeof(llc->daddr)); | ||
694 | llc_sap_add_socket(sap, sk); | ||
695 | sock_hold(sk); | ||
696 | sock_put(parent); | ||
697 | skb->sk = parent; | ||
698 | } else | ||
699 | skb->sk = sk; | ||
700 | bh_lock_sock(sk); | ||
701 | if (!sock_owned_by_user(sk)) | ||
702 | llc_conn_rcv(sk, skb); | ||
703 | else { | ||
704 | dprintk("%s: adding to backlog...\n", __FUNCTION__); | ||
705 | llc_set_backlog_type(skb, LLC_PACKET); | ||
706 | sk_add_backlog(sk, skb); | ||
707 | } | ||
708 | bh_unlock_sock(sk); | ||
709 | sock_put(sk); | ||
710 | return; | ||
711 | drop: | ||
712 | kfree_skb(skb); | ||
713 | } | ||
714 | |||
715 | #undef LLC_REFCNT_DEBUG | ||
716 | #ifdef LLC_REFCNT_DEBUG | ||
717 | static atomic_t llc_sock_nr; | ||
718 | #endif | ||
719 | |||
720 | /** | ||
721 | * llc_release_sockets - releases all sockets in a sap | ||
722 | * @sap: sap to release its sockets | ||
723 | * | ||
724 | * Releases all connections of a sap. Returns 0 if all actions complete | ||
725 | * successfully, nonzero otherwise | ||
726 | */ | ||
727 | int llc_release_sockets(struct llc_sap *sap) | ||
728 | { | ||
729 | int rc = 0; | ||
730 | struct sock *sk; | ||
731 | struct hlist_node *node; | ||
732 | |||
733 | write_lock_bh(&sap->sk_list.lock); | ||
734 | |||
735 | sk_for_each(sk, node, &sap->sk_list.list) { | ||
736 | llc_sk(sk)->state = LLC_CONN_STATE_TEMP; | ||
737 | |||
738 | if (llc_send_disc(sk)) | ||
739 | rc = 1; | ||
740 | } | ||
741 | |||
742 | write_unlock_bh(&sap->sk_list.lock); | ||
743 | return rc; | ||
744 | } | ||
745 | |||
746 | /** | ||
747 | * llc_backlog_rcv - Processes rx frames and expired timers. | ||
748 | * @sk: LLC sock (p8022 connection) | ||
749 | * @skb: queued rx frame or event | ||
750 | * | ||
751 | * This function processes frames that has received and timers that has | ||
752 | * expired during sending an I pdu (refer to data_req_handler). frames | ||
753 | * queue by llc_rcv function (llc_mac.c) and timers queue by timer | ||
754 | * callback functions(llc_c_ac.c). | ||
755 | */ | ||
756 | static int llc_backlog_rcv(struct sock *sk, struct sk_buff *skb) | ||
757 | { | ||
758 | int rc = 0; | ||
759 | struct llc_sock *llc = llc_sk(sk); | ||
760 | |||
761 | if (llc_backlog_type(skb) == LLC_PACKET) { | ||
762 | if (llc->state > 1) /* not closed */ | ||
763 | rc = llc_conn_rcv(sk, skb); | ||
764 | else | ||
765 | goto out_kfree_skb; | ||
766 | } else if (llc_backlog_type(skb) == LLC_EVENT) { | ||
767 | /* timer expiration event */ | ||
768 | if (llc->state > 1) /* not closed */ | ||
769 | rc = llc_conn_state_process(sk, skb); | ||
770 | else | ||
771 | goto out_kfree_skb; | ||
772 | } else { | ||
773 | printk(KERN_ERR "%s: invalid skb in backlog\n", __FUNCTION__); | ||
774 | goto out_kfree_skb; | ||
775 | } | ||
776 | out: | ||
777 | return rc; | ||
778 | out_kfree_skb: | ||
779 | kfree_skb(skb); | ||
780 | goto out; | ||
781 | } | ||
782 | |||
783 | /** | ||
784 | * llc_sk_init - Initializes a socket with default llc values. | ||
785 | * @sk: socket to initialize. | ||
786 | * | ||
787 | * Initializes a socket with default llc values. | ||
788 | */ | ||
789 | static void llc_sk_init(struct sock* sk) | ||
790 | { | ||
791 | struct llc_sock *llc = llc_sk(sk); | ||
792 | |||
793 | llc->state = LLC_CONN_STATE_ADM; | ||
794 | llc->inc_cntr = llc->dec_cntr = 2; | ||
795 | llc->dec_step = llc->connect_step = 1; | ||
796 | |||
797 | init_timer(&llc->ack_timer.timer); | ||
798 | llc->ack_timer.expire = LLC_ACK_TIME; | ||
799 | llc->ack_timer.timer.data = (unsigned long)sk; | ||
800 | llc->ack_timer.timer.function = llc_conn_ack_tmr_cb; | ||
801 | |||
802 | init_timer(&llc->pf_cycle_timer.timer); | ||
803 | llc->pf_cycle_timer.expire = LLC_P_TIME; | ||
804 | llc->pf_cycle_timer.timer.data = (unsigned long)sk; | ||
805 | llc->pf_cycle_timer.timer.function = llc_conn_pf_cycle_tmr_cb; | ||
806 | |||
807 | init_timer(&llc->rej_sent_timer.timer); | ||
808 | llc->rej_sent_timer.expire = LLC_REJ_TIME; | ||
809 | llc->rej_sent_timer.timer.data = (unsigned long)sk; | ||
810 | llc->rej_sent_timer.timer.function = llc_conn_rej_tmr_cb; | ||
811 | |||
812 | init_timer(&llc->busy_state_timer.timer); | ||
813 | llc->busy_state_timer.expire = LLC_BUSY_TIME; | ||
814 | llc->busy_state_timer.timer.data = (unsigned long)sk; | ||
815 | llc->busy_state_timer.timer.function = llc_conn_busy_tmr_cb; | ||
816 | |||
817 | llc->n2 = 2; /* max retransmit */ | ||
818 | llc->k = 2; /* tx win size, will adjust dynam */ | ||
819 | llc->rw = 128; /* rx win size (opt and equal to | ||
820 | * tx_win of remote LLC) */ | ||
821 | skb_queue_head_init(&llc->pdu_unack_q); | ||
822 | sk->sk_backlog_rcv = llc_backlog_rcv; | ||
823 | } | ||
824 | |||
825 | /** | ||
826 | * llc_sk_alloc - Allocates LLC sock | ||
827 | * @family: upper layer protocol family | ||
828 | * @priority: for allocation (%GFP_KERNEL, %GFP_ATOMIC, etc) | ||
829 | * | ||
830 | * Allocates a LLC sock and initializes it. Returns the new LLC sock | ||
831 | * or %NULL if there's no memory available for one | ||
832 | */ | ||
833 | struct sock *llc_sk_alloc(int family, int priority, struct proto *prot) | ||
834 | { | ||
835 | struct sock *sk = sk_alloc(family, priority, prot, 1); | ||
836 | |||
837 | if (!sk) | ||
838 | goto out; | ||
839 | llc_sk_init(sk); | ||
840 | sock_init_data(NULL, sk); | ||
841 | #ifdef LLC_REFCNT_DEBUG | ||
842 | atomic_inc(&llc_sock_nr); | ||
843 | printk(KERN_DEBUG "LLC socket %p created in %s, now we have %d alive\n", sk, | ||
844 | __FUNCTION__, atomic_read(&llc_sock_nr)); | ||
845 | #endif | ||
846 | out: | ||
847 | return sk; | ||
848 | } | ||
849 | |||
850 | /** | ||
851 | * llc_sk_free - Frees a LLC socket | ||
852 | * @sk - socket to free | ||
853 | * | ||
854 | * Frees a LLC socket | ||
855 | */ | ||
856 | void llc_sk_free(struct sock *sk) | ||
857 | { | ||
858 | struct llc_sock *llc = llc_sk(sk); | ||
859 | |||
860 | llc->state = LLC_CONN_OUT_OF_SVC; | ||
861 | /* Stop all (possibly) running timers */ | ||
862 | llc_conn_ac_stop_all_timers(sk, NULL); | ||
863 | #ifdef DEBUG_LLC_CONN_ALLOC | ||
864 | printk(KERN_INFO "%s: unackq=%d, txq=%d\n", __FUNCTION__, | ||
865 | skb_queue_len(&llc->pdu_unack_q), | ||
866 | skb_queue_len(&sk->sk_write_queue)); | ||
867 | #endif | ||
868 | skb_queue_purge(&sk->sk_receive_queue); | ||
869 | skb_queue_purge(&sk->sk_write_queue); | ||
870 | skb_queue_purge(&llc->pdu_unack_q); | ||
871 | #ifdef LLC_REFCNT_DEBUG | ||
872 | if (atomic_read(&sk->sk_refcnt) != 1) { | ||
873 | printk(KERN_DEBUG "Destruction of LLC sock %p delayed in %s, cnt=%d\n", | ||
874 | sk, __FUNCTION__, atomic_read(&sk->sk_refcnt)); | ||
875 | printk(KERN_DEBUG "%d LLC sockets are still alive\n", | ||
876 | atomic_read(&llc_sock_nr)); | ||
877 | } else { | ||
878 | atomic_dec(&llc_sock_nr); | ||
879 | printk(KERN_DEBUG "LLC socket %p released in %s, %d are still alive\n", sk, | ||
880 | __FUNCTION__, atomic_read(&llc_sock_nr)); | ||
881 | } | ||
882 | #endif | ||
883 | sock_put(sk); | ||
884 | } | ||
885 | |||
886 | /** | ||
887 | * llc_sk_reset - resets a connection | ||
888 | * @sk: LLC socket to reset | ||
889 | * | ||
890 | * Resets a connection to the out of service state. Stops its timers | ||
891 | * and frees any frames in the queues of the connection. | ||
892 | */ | ||
893 | void llc_sk_reset(struct sock *sk) | ||
894 | { | ||
895 | struct llc_sock *llc = llc_sk(sk); | ||
896 | |||
897 | llc_conn_ac_stop_all_timers(sk, NULL); | ||
898 | skb_queue_purge(&sk->sk_write_queue); | ||
899 | skb_queue_purge(&llc->pdu_unack_q); | ||
900 | llc->remote_busy_flag = 0; | ||
901 | llc->cause_flag = 0; | ||
902 | llc->retry_count = 0; | ||
903 | llc_conn_set_p_flag(sk, 0); | ||
904 | llc->f_flag = 0; | ||
905 | llc->s_flag = 0; | ||
906 | llc->ack_pf = 0; | ||
907 | llc->first_pdu_Ns = 0; | ||
908 | llc->ack_must_be_send = 0; | ||
909 | llc->dec_step = 1; | ||
910 | llc->inc_cntr = 2; | ||
911 | llc->dec_cntr = 2; | ||
912 | llc->X = 0; | ||
913 | llc->failed_data_req = 0 ; | ||
914 | llc->last_nr = 0; | ||
915 | } | ||