591 lines
14 KiB
C
591 lines
14 KiB
C
/* Link Access Procedures Balanced (LAPB) - with changes for rational
|
|
* behavior over packet radio
|
|
* Added segmentation and deferred frame delivery to correct LAPB errors
|
|
* from queued transmit frames already gone to the TNC. k33 - K5JB
|
|
* Added IP only VC call sign k34 - K5JB
|
|
*/
|
|
#include "options.h"
|
|
#include "config.h"
|
|
#ifdef AX25
|
|
#include "global.h"
|
|
#include "mbuf.h"
|
|
#include "timer.h"
|
|
#include "ax25.h"
|
|
#include "lapb.h"
|
|
#include "iface.h"
|
|
#ifdef MSDOS
|
|
#include <mem.h>
|
|
#endif
|
|
|
|
void free_q(),frmr();
|
|
void procdata(),clr_ex(),est_link(),enq_resp(),inv_rex(),lapb_output();
|
|
int sendframe(),start_timer(),stop_timer(); /* latter 2 should have been void */
|
|
int sendctl(),ackours();
|
|
|
|
#ifdef VCIP_SSID
|
|
extern struct ax25_addr ip_call;
|
|
int addreq();
|
|
#endif
|
|
|
|
#ifdef SID2
|
|
extern int bbscall_set;
|
|
#endif
|
|
|
|
#if defined(SEGMENT) && defined(SEG_CMD)
|
|
int rx_segment = 1; /* also used in ax25cmd.c */
|
|
#endif
|
|
|
|
/* Process incoming frames */
|
|
int
|
|
lapb_input(axp,cmdrsp,bp)
|
|
struct ax25_cb *axp; /* Link control structure */
|
|
char cmdrsp; /* Command/response flag */
|
|
struct mbuf *bp; /* Rest of frame, starting with ctl */
|
|
{
|
|
int16 ftype();
|
|
void lapbstate(),del_ax25();
|
|
char control;
|
|
char class; /* General class (I/S/U) of frame */
|
|
int16 type; /* Specific type (I/RR/RNR/etc) of frame */
|
|
char poll = 0;
|
|
char final = 0;
|
|
int nr; /* ACK number of incoming frame */
|
|
int ns; /* Seq number of incoming frame */
|
|
char tmp;
|
|
int recovery;
|
|
|
|
if(bp == NULLBUF || axp == NULLAX25){
|
|
free_p(bp);
|
|
return -1;
|
|
}
|
|
|
|
/* Extract the various parts of the control field for easy use */
|
|
control = pullchar(&bp);
|
|
type = ftype(control);
|
|
class = type & 0x3;
|
|
/* Check for polls and finals */
|
|
if(control & PF){
|
|
switch(cmdrsp){
|
|
case COMMAND:
|
|
poll = YES;
|
|
break;
|
|
case RESPONSE:
|
|
final = YES;
|
|
break;
|
|
}
|
|
}
|
|
/* Extract sequence numbers, if present */
|
|
switch(class){
|
|
case I:
|
|
case I+2:
|
|
ns = (control >> 1) & MMASK;
|
|
case S: /* Note fall-thru */
|
|
nr = (control >> 5) & MMASK;
|
|
break;
|
|
}
|
|
/* This section follows the SDL diagrams by K3NA fairly closely */
|
|
recovery = 0;
|
|
switch(axp->state){
|
|
case DISCONNECTED: /* need to revisit this... */
|
|
switch(type){
|
|
case SABM: /* Initialize or reset link */
|
|
sendctl(axp,RESPONSE,UA); /* Always accept */
|
|
clr_ex(axp);
|
|
axp->unack = axp->vr = axp->vs = 0;
|
|
lapbstate(axp,CONNECTED);/* Resets state counters */
|
|
start_timer(&axp->t3);
|
|
start_timer(&axp->t4);
|
|
break;
|
|
/* note that NOS has a case DISC here but it has same effect as default */
|
|
case DM: /* Ignore to avoid infinite loops */
|
|
break;
|
|
default: /* All others get DM */
|
|
/* This is not according to ax.25 v1, but looks OK to me
|
|
if(poll)
|
|
*/
|
|
sendctl(axp,RESPONSE,DM);
|
|
break;
|
|
}
|
|
break;
|
|
/* note that NOS has a test for state == DISCONNECTED here but we avoid
|
|
* spaghetti code by doing it later */
|
|
case SETUP:
|
|
switch(type){
|
|
case SABM: /* Simultaneous open */
|
|
sendctl(axp,RESPONSE,UA);
|
|
break;
|
|
case DISC:
|
|
sendctl(axp,RESPONSE,DM);
|
|
break;
|
|
case UA: /* Connection accepted */
|
|
/* Note: xmit queue not cleared */
|
|
stop_timer(&axp->t1);
|
|
start_timer(&axp->t3);
|
|
axp->unack = axp->vr = axp->vs = 0;
|
|
lapbstate(axp,CONNECTED);
|
|
start_timer(&axp->t4);
|
|
break;
|
|
case DM: /* Connection refused */
|
|
free_q(&axp->txq);
|
|
stop_timer(&axp->t1);
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
default: /* All other frames ignored */
|
|
break;
|
|
}
|
|
break;
|
|
case DISCPENDING:
|
|
switch(type){
|
|
case SABM:
|
|
sendctl(axp,RESPONSE,DM);
|
|
break;
|
|
case DISC:
|
|
sendctl(axp,RESPONSE,UA);
|
|
break;
|
|
case UA:
|
|
case DM:
|
|
stop_timer(&axp->t1);
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
default: /* Respond with DM only to command polls */
|
|
if(poll)
|
|
sendctl(axp,RESPONSE,DM);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case RECOVERY: /* revised following saved 800 bytes - K5JB */
|
|
recovery = 1; /* fall through */
|
|
case CONNECTED:
|
|
switch(type){
|
|
case SABM:
|
|
sendctl(axp,RESPONSE,UA);
|
|
clr_ex(axp);
|
|
if(!recovery)
|
|
free_q(&axp->txq); /* remarked out in GRI NOS */
|
|
stop_timer(&axp->t1);
|
|
start_timer(&axp->t3);
|
|
axp->unack = axp->vr = axp->vs = 0;
|
|
lapbstate(axp,CONNECTED); /* remarked out in NOS *//* Purge queues */
|
|
if(recovery && !run_timer(&axp->t4))
|
|
start_timer(&axp->t4);
|
|
break;
|
|
case DISC:
|
|
free_q(&axp->txq);
|
|
sendctl(axp,RESPONSE,UA);
|
|
stop_timer(&axp->t1);
|
|
stop_timer(&axp->t3);
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
case DM:
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
case UA:
|
|
case FRMR:
|
|
est_link(axp);
|
|
lapbstate(axp,SETUP); /* Re-establish link */
|
|
break;
|
|
case REJ:
|
|
case RNR:
|
|
case RR:
|
|
axp->remotebusy = (type == RNR) ? YES : NO;
|
|
if(recovery){
|
|
if(axp->proto == V1 || final){
|
|
stop_timer(&axp->t1);
|
|
ackours(axp,nr);
|
|
if(axp->unack != 0)
|
|
inv_rex(axp);
|
|
else{
|
|
start_timer(&axp->t3);
|
|
if(type != RNR) /* k37 */
|
|
lapbstate(axp,CONNECTED);
|
|
if(!run_timer(&axp->t4))
|
|
start_timer(&axp->t4);
|
|
}
|
|
}else{
|
|
if(poll)
|
|
enq_resp(axp);
|
|
ackours(axp,nr);
|
|
if(type == REJ && axp->unack != 0)
|
|
inv_rex(axp); /* This is certain to trigger output */
|
|
if(!run_timer(&axp->t1))
|
|
start_timer(&axp->t1);
|
|
}
|
|
}else{ /* not recovery */
|
|
if(poll)
|
|
enq_resp(axp);
|
|
ackours(axp,nr);
|
|
if(type == REJ){
|
|
stop_timer(&axp->t1);
|
|
start_timer(&axp->t3);
|
|
/* This may or may not actually invoke transmission,
|
|
* depending on whether this REJ was caused by
|
|
* our losing his prior ACK.
|
|
*/
|
|
inv_rex(axp);
|
|
}
|
|
}
|
|
break;
|
|
case I:
|
|
ackours(axp,nr);
|
|
if(recovery){
|
|
if(!run_timer(&axp->t1))
|
|
/* Make sure timer is running, since an I frame
|
|
* cannot satisfy a poll
|
|
*/
|
|
start_timer(&axp->t1);
|
|
}else
|
|
start_timer(&axp->t4);
|
|
if(len_mbuf(axp->rxq) >= axp->window){
|
|
/* Too bad he didn't listen to us; he'll
|
|
* have to resend the frame later. This
|
|
* drastic action is necessary to avoid
|
|
* deadlock.
|
|
*/
|
|
if(recovery || poll)
|
|
sendctl(axp,RESPONSE,RNR);
|
|
free_p(bp);
|
|
bp = NULLBUF;
|
|
break;
|
|
}
|
|
/* Reject or ignore I-frames with receive sequence number errors */
|
|
if(ns != axp->vr){
|
|
if(axp->proto == V1 || !axp->rejsent){
|
|
axp->rejsent = YES;
|
|
sendctl(axp,RESPONSE,REJ);
|
|
}
|
|
else
|
|
axp->response = 0;
|
|
break;
|
|
}
|
|
axp->rejsent = NO;
|
|
axp->vr = (axp->vr+1) & MMASK;
|
|
tmp = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
|
|
/* in this area, NOS sends unnecessary RR(p) frames */
|
|
axp->response = tmp; /* will take care of after lapb_output */
|
|
procdata(axp,bp);
|
|
bp = NULLBUF;
|
|
break;
|
|
default: /* All others ignored */
|
|
break;
|
|
}
|
|
break;
|
|
case FRAMEREJECT:
|
|
switch(type){
|
|
case SABM:
|
|
sendctl(axp,RESPONSE,UA);
|
|
clr_ex(axp);
|
|
axp->unack = axp->vr = axp->vs = 0;
|
|
stop_timer(&axp->t1);
|
|
start_timer(&axp->t3);
|
|
lapbstate(axp,CONNECTED);
|
|
break;
|
|
case DISC:
|
|
free_q(&axp->txq);
|
|
sendctl(axp,RESPONSE,UA);
|
|
stop_timer(&axp->t1);
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
case DM:
|
|
stop_timer(&axp->t1);
|
|
lapbstate(axp,DISCONNECTED);
|
|
break;
|
|
default:
|
|
frmr(axp,0,0);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
free_p(bp); /* In case anything's left */
|
|
/* Empty the trash */
|
|
if(axp->state == DISCONNECTED){
|
|
del_ax25(axp);
|
|
return 0;
|
|
}
|
|
/* See if we can send some data, perhaps piggybacking an ack.
|
|
* If successful, lapb_output will clear axp->response.
|
|
*/
|
|
lapb_output(axp);
|
|
return 0;
|
|
}
|
|
/* Handle incoming acknowledgements for frames we've sent.
|
|
* Free frames being acknowledged.
|
|
* Return -1 to cause a frame reject if number is bad, 0 otherwise
|
|
*/
|
|
static int
|
|
ackours(axp,n)
|
|
struct ax25_cb *axp;
|
|
char n;
|
|
{
|
|
struct mbuf *bp;
|
|
int acked = 0; /* Count of frames acked by this ACK */
|
|
int oldest; /* Seq number of oldest unacked I-frame */
|
|
|
|
/* Free up acknowledged frames by purging frames from the I-frame
|
|
* transmit queue. Start at the remote end's last reported V(r)
|
|
* and keep going until we reach the new sequence number.
|
|
* If we try to free a null pointer, then we have a frame reject condition.
|
|
*/
|
|
oldest = (axp->vs - axp->unack) & MMASK;
|
|
while(axp->unack != 0 && oldest != n){
|
|
if((bp = dequeue(&axp->txq)) == NULLBUF){
|
|
/* Acking unsent frame */
|
|
return -1;
|
|
}
|
|
free_p(bp);
|
|
axp->unack--;
|
|
acked++;
|
|
#ifdef notdef /* don't think we need this */
|
|
if(!axp->remotebusy) /* k37 */
|
|
#endif
|
|
axp->retries = 0;
|
|
oldest = (oldest + 1) & MMASK;
|
|
}
|
|
if(axp->unack == 0){
|
|
/* All frames acked, stop timeout */
|
|
stop_timer(&axp->t1);
|
|
start_timer(&axp->t3);
|
|
} else if(acked != 0) {
|
|
/* Partial ACK; restart timer */
|
|
start_timer(&axp->t1);
|
|
}
|
|
/* If user has set a transmit upcall, indicate how many frames
|
|
* may be queued
|
|
*/
|
|
if(acked != 0 && axp->t_upcall != NULLVFP)
|
|
(*axp->t_upcall)(axp,axp->paclen * (axp->maxframe - axp->unack));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Establish data link */
|
|
void
|
|
est_link(axp)
|
|
struct ax25_cb *axp;
|
|
{
|
|
clr_ex(axp);
|
|
axp->retries = 0;
|
|
sendctl(axp,COMMAND,SABM);
|
|
stop_timer(&axp->t3);
|
|
start_timer(&axp->t1);
|
|
}
|
|
/* Clear exception conditions */
|
|
void
|
|
clr_ex(axp)
|
|
struct ax25_cb *axp;
|
|
{
|
|
axp->remotebusy = NO;
|
|
axp->rejsent = NO;
|
|
axp->response = 0;
|
|
stop_timer(&axp->t3);
|
|
}
|
|
/* Enquiry response */
|
|
void
|
|
enq_resp(axp)
|
|
struct ax25_cb *axp;
|
|
{
|
|
char ctl;
|
|
|
|
ctl = len_mbuf(axp->rxq) >= axp->window ? RNR : RR;
|
|
sendctl(axp,RESPONSE,ctl);
|
|
axp->response = 0;
|
|
stop_timer(&axp->t3);
|
|
}
|
|
/* Invoke retransmission */
|
|
void
|
|
inv_rex(axp)
|
|
struct ax25_cb *axp;
|
|
{
|
|
axp->vs -= axp->unack;
|
|
axp->vs &= MMASK;
|
|
axp->unack = 0;
|
|
}
|
|
/* Generate Frame Reject (FRMR) response
|
|
* If reason != 0, this is the initial error frame
|
|
* If reason == 0, resend the last error frame
|
|
*/
|
|
void
|
|
frmr(axp,control,reason)
|
|
register struct ax25_cb *axp;
|
|
char control;
|
|
char reason;
|
|
{
|
|
struct mbuf *frmrinfo;
|
|
register char *cp;
|
|
void lapbstate();
|
|
|
|
if(reason != 0){
|
|
cp = axp->frmrinfo;
|
|
*cp++ = control;
|
|
*cp++ = axp->vr << 5 || axp->vs << 1;
|
|
*cp = reason;
|
|
}
|
|
if((frmrinfo = alloc_mbuf(3)) == NULLBUF)
|
|
return; /* No memory */
|
|
frmrinfo->cnt = 3;
|
|
memcpy(frmrinfo->data,axp->frmrinfo,3);
|
|
sendframe(axp,RESPONSE,FRMR,frmrinfo);
|
|
}
|
|
|
|
/* Send S or U frame to currently connected station */
|
|
int
|
|
sendctl(axp,cmdrsp,cmd)
|
|
struct ax25_cb *axp;
|
|
char cmdrsp,cmd;
|
|
{
|
|
int16 ftype();
|
|
|
|
if((ftype(cmd) & 0x3) == S) /* Insert V(R) if S frame */
|
|
cmd |= (axp->vr << 5);
|
|
return sendframe(axp,cmdrsp,cmd,NULLBUF);
|
|
}
|
|
|
|
/* defer output with timer, give time for ack to abort retry */
|
|
|
|
void
|
|
lapb_output(axp)
|
|
register struct ax25_cb *axp;
|
|
{
|
|
/* function installed in lapbtime.c */
|
|
axp->t2.arg = (char *)axp;
|
|
start_timer(&axp->t2);
|
|
}
|
|
|
|
/* Set new link state.
|
|
* If the new state is disconnected, also free the link control block.
|
|
*/
|
|
void
|
|
lapbstate(axp,s)
|
|
struct ax25_cb *axp;
|
|
int s;
|
|
{
|
|
int oldstate;
|
|
|
|
oldstate = axp->state;
|
|
axp->state = s;
|
|
if(s == DISCONNECTED){
|
|
stop_timer(&axp->t1);
|
|
stop_timer(&axp->t2);
|
|
stop_timer(&axp->t3);
|
|
stop_timer(&axp->t4);
|
|
free_q(&axp->txq);
|
|
}
|
|
/* Don't bother the client unless the state is really changing */
|
|
if(oldstate != s && axp->s_upcall != NULLVFP)
|
|
(*axp->s_upcall)(axp,oldstate,s);
|
|
}
|
|
/* Process a valid incoming I frame */
|
|
static void
|
|
procdata(axp,bp)
|
|
struct ax25_cb *axp;
|
|
struct mbuf *bp;
|
|
{
|
|
unsigned char pid;
|
|
#ifdef SEGMENT
|
|
int seq;
|
|
#endif
|
|
int ip_route();
|
|
#ifdef NETROM
|
|
void nr_route();
|
|
#endif
|
|
/* Extract level 3 PID, and THEN see if there is any info - k35 */
|
|
if(pullup(&bp,&pid,1) != 1 || bp == NULLBUF)
|
|
return; /* No PID, or was a zero length ROSE Booger */
|
|
|
|
#ifdef SEGMENT /* NOS's "segmentation" scheme -- uses its own PID */
|
|
#ifdef SEG_CMD
|
|
if(pid == PID_SEGMENT && rx_segment) /* if turned off we will trash it */
|
|
#else
|
|
if(pid == PID_SEGMENT)
|
|
#endif
|
|
{
|
|
seq = pullchar(&bp);
|
|
if(axp->segremain == 0){ /* Probably first segment of a set */
|
|
/* Start reassembly */
|
|
if(!(seq & SEG_FIRST)){
|
|
free_p(bp); /* How in the hell did we get this from ax25? */
|
|
return;
|
|
}
|
|
/* Put first segment on list */
|
|
axp->segremain = seq & SEG_REM;
|
|
axp->rxasm = bp;
|
|
return; /* will handle it later */
|
|
}else{
|
|
/* Reassembly in progress; continue */
|
|
if((seq & SEG_REM) != axp->segremain - 1){
|
|
/* Error! How in hell did it get out of order? */
|
|
free_p(axp->rxasm);
|
|
axp->rxasm = NULLBUF;
|
|
axp->segremain = 0;
|
|
free_p(bp);
|
|
return;
|
|
}
|
|
|
|
/* Correct, in-order segment */
|
|
append(&axp->rxasm,bp);
|
|
if((axp->segremain = (seq & SEG_REM)) != 0)
|
|
return; /* will handle it later */
|
|
|
|
/* Else, done; process it */
|
|
bp = axp->rxasm;
|
|
axp->rxasm = NULLBUF;
|
|
pid = pullchar(&bp); /* tumble to switch below */
|
|
} /* else */
|
|
} /* if SEGMENT */
|
|
#endif /* SEGMENT */
|
|
|
|
/* Finally (maybe), we do something with this frame */
|
|
|
|
switch(pid){
|
|
case PID_IP: /* DoD Internet Protocol */
|
|
ip_route(bp,0);
|
|
break;
|
|
case PID_NO_L3:
|
|
#ifdef VCIP_SSID
|
|
/* don't think it is now necessary to test for bp->cnt - k35 */
|
|
#ifdef notdef
|
|
if(addreq(&axp->addr.source,&ip_call) || bp->cnt == 0){
|
|
free_p(bp); /* don't send zero length packets upstairs */
|
|
break;
|
|
}
|
|
#else
|
|
/* zero length I frames already trashed - k35 */
|
|
if(addreq(&axp->addr.source,&ip_call)){
|
|
free_p(bp);
|
|
break;
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef SID2
|
|
/* new style of eliminating spurious I frames k36 */
|
|
if(bbscall_set && addreq(&axp->addr.source,&mycall) && !axp->user){
|
|
free_p(bp);
|
|
break;
|
|
}
|
|
#ifdef REFERENCE
|
|
/* See if a session already exists */
|
|
for(s = sessions; s < &sessions[nsessions]; s++){
|
|
if(s->type == AX25TNC
|
|
&& addreq(&s->cb.ax25_cb->addr.dest,&dest)){
|
|
printf("Session %u to %s already exists\n",s - sessions,argv[2]);
|
|
return 1;
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
append(&axp->rxq,bp); /* Enqueue for application - usually mailbox */
|
|
if(axp->r_upcall != NULLVFP)
|
|
(*axp->r_upcall)(axp,len_mbuf(axp->rxq));
|
|
break;
|
|
#ifdef NETROM
|
|
case PID_NETROM:
|
|
nr_route(bp,axp);
|
|
break;
|
|
#endif
|
|
default: /* Note: ARP is invalid here */
|
|
free_p(bp);
|
|
break;
|
|
}
|
|
}
|
|
#endif /* AX25 */
|