* Copyright (c) 1982 Regents of the University of California.
* All rights reserved. The Berkeley software License Agreement
* specifies the terms and conditions for redistribution.
* @(#)spp_usrreq.c 6.10 (Berkeley) %G%
#include "../net/route.h"
#include "../netinet/tcp_fsm.h"
#include "../netinet/tcp_timer.h"
* SP protocol implementation.
spp_iss
= 1; /* WRONG !! should fish it out of TODR */
register struct nspcb
*nsp
;
register struct sppcb
*cb
;
register struct spidp
*si
= mtod(m
, struct spidp
*);
register struct socket
*so
;
panic("No nspcb in spp_input\n");
if (m
->m_len
< sizeof(*si
)) {
if ((m
= m_pullup(m
, sizeof(*si
))) == 0) {
si
= mtod(m
, struct spidp
*);
si
->si_seq
= ntohs(si
->si_seq
);
si
->si_ack
= ntohs(si
->si_ack
);
si
->si_alo
= ntohs(si
->si_alo
);
if (so
->so_options
& SO_DEBUG
|| traceallspps
) {
if (so
->so_options
& SO_ACCEPTCONN
) {
* Mark socket as temporary until we're
* committed to keeping it. The code at
* ``drop'' and ``dropwithreset'' check the
* flag dropsocket to see if the temporary
* socket created here should be discarded.
* We mark the socket as discardable until
* we're committed to it below in TCPS_LISTEN.
nsp
= (struct nspcb
*)so
->so_pcb
;
nsp
->nsp_laddr
= si
->si_dna
;
cb
->s_state
= TCPS_LISTEN
;
* Packet received on connection.
* reset idle time and keep-alive timer;
cb
->s_timer
[TCPT_KEEP
] = TCPTV_KEEP
;
register struct sockaddr_ns
*sns
;
* If somebody here was carying on a conversation
* and went away, and his pen pal thinks he can
* still talk, we get the misdirected packet.
if (spp_hardnosed
&& (si
->si_did
!= 0 || si
->si_seq
!= 0)) {
am
= m_get(M_DONTWAIT
, MT_SONAME
);
am
->m_len
= sizeof (struct sockaddr_ns
);
sns
= mtod(am
, struct sockaddr_ns
*);
sns
->sns_addr
= si
->si_sna
;
nsp
->nsp_laddr
= si
->si_dna
;
if (ns_pcbconnect(nsp
, am
)) {
cb
->s_state
= TCPS_SYN_RECEIVED
;
cb
->s_timer
[TCPT_KEEP
] = TCPTV_KEEP
;
dropsocket
= 0; /* committed to socket */
* This state means that we have gotten a response
* to our attempt to establish a connection.
* We fill in the data from the other side,
* telling us which port to respond to, instead of the well-
* known one we might have sent to in the first place.
* We also require that this is a response to our
if (si
->si_did
!=cb
->s_sid
) {
cb
->s_dport
= nsp
->nsp_fport
= si
->si_sport
;
cb
->s_timer
[TCPT_REXMT
] = 0;
cb
->s_state
= TCPS_ESTABLISHED
;
* This state means that we have heard a response
* to our acceptance of their connection
* It is probably logically unnecessary in this
if (si
->si_did
!=cb
->s_sid
) {
nsp
->nsp_fport
= si
->si_sport
;
cb
->s_timer
[TCPT_REXMT
] = 0;
cb
->s_timer
[TCPT_KEEP
] = TCPTV_KEEP
;
cb
->s_state
= TCPS_ESTABLISHED
;
if (so
->so_options
& SO_DEBUG
|| traceallspps
)
spp_trace(SA_INPUT
, (u_char
)ostate
, cb
, &spp_savesi
, 0);
m
->m_len
-= sizeof (struct idp
);
m
->m_off
+= sizeof (struct idp
);
(void) spp_output(cb
,(struct mbuf
*)0);
si
->si_seq
= ntohs(si
->si_seq
);
si
->si_ack
= ntohs(si
->si_ack
);
si
->si_alo
= ntohs(si
->si_alo
);
ns_error(dtom(si
), NS_ERR_NOSOCK
, 0);
if (cb
->s_nspcb
->nsp_socket
->so_options
& SO_DEBUG
|| traceallspps
)
spp_trace(SA_DROP
, (u_char
)ostate
, cb
, &spp_savesi
, 0);
if (cb
== 0 || cb
->s_nspcb
->nsp_socket
->so_options
& SO_DEBUG
|| traceallspps
)
spp_trace(SA_DROP
, (u_char
)ostate
, cb
, &spp_savesi
, 0);
* This is structurally similar to the tcp reassembly routine
* but its function is somewhat different: It merely queues
* packets up, and suppresses duplicates.
register struct sppcb
*cb
;
register struct spidp
*si
;
register struct spidp_q
*q
;
struct socket
*so
= cb
->s_nspcb
->nsp_socket
;
struct sockbuf
*sb
= & (so
->so_rcv
);
char packetp
= cb
->s_flags
& SF_HI
;
* Update our news from them.
cb
->s_flags
|= SF_DELACK
;
if (SSEQ_GT(si
->si_ack
,cb
->s_rack
)) {
cb
->s_timer
[TCPT_REXMT
] = 0;
* If transmit timer is running and timed sequence
* number was acked, update smoothed round trip time.
if (cb
->s_rtt
&& SSEQ_GT(si
->si_ack
, cb
->s_rtseq
)) {
(1 - tcp_alpha
) * cb
->s_rtt
;
if (SSEQ_GT(si
->si_alo
,cb
->s_ralo
)) {
cb
->s_timer
[TCPT_PERSIST
] = 0;
* If this is a system packet, we don't need to
* queue it up, and won't update acknowledge #
* If this packet number has a sequence number less
* than that of the first packet not yet seen coming
* from them, this must be a duplicate, so drop.
if (SSEQ_LT(si
->si_seq
,cb
->s_ack
)) {
if (si
->si_seq
== cb
->s_ack
-1)
* If this packet number is higher than that which
* we have allocated refuse it, unless urgent
if (SSEQ_GT(si
->si_seq
,cb
->s_alo
) && (!(si
->si_cc
& SP_OB
))) {
* If this packet is urgent, inform process
cb
->s_iobc
= ((char *)si
)[1 + sizeof(*si
)];
* Loop through all packets queued up to insert in
for (q
= cb
->s_q
.si_next
; q
!=&cb
->s_q
; q
= q
->si_next
) {
if (si
->si_seq
== SI(q
)->si_seq
) return (1); /*duplicate */
if (SSEQ_LT(si
->si_seq
,SI(q
)->si_seq
)) break;
#define SPINC sizeof(struct sphdr)
* Loop through all packets queued up to update acknowledge
* number, and present all acknowledged data to user;
* If in packet interface mode, show packet headers.
for (q
= cb
->s_q
.si_next
; q
!=&cb
->s_q
; q
= q
->si_next
) {
if (SI(q
)->si_seq
== cb
->s_ack
) {
if (SI(q
)->si_cc
& SP_OB
) {
so
->so_oobmark
= sb
->sb_cc
;
so
->so_state
|= SS_RCVATMARK
;
cb
->s_rhdr
= *mtod(m
, struct sphdr
*);
if (wakeup
) sorwakeup(so
);
extern u_char nsctlerrmap
[];
extern struct nspcb
*idp_drop();
if (cmd
< 0 || cmd
> PRC_NCMDS
)
type
= NS_ERR_UNREACH_HOST
;
na
= &((struct sockaddr_ns
*)arg
)->sns_addr
;
na
= (struct ns_addr
*)arg
;
errp
= (struct ns_errp
*)arg
;
na
= &errp
->ns_err_idp
.idp_dna
;
type
= ntohs((u_short
)type
);
case NS_ERR_UNREACH_HOST
:
ns_pcbnotify(na
, (int)nsctlerrmap
[cmd
], spp_abort
, (long) 0);
nsp
= ns_pcblookup(na
, errp
->ns_err_idp
.idp_sna
.x_port
,
(void) spp_drop((struct sppcb
*)nsp
->nsp_pcb
,
(void) idp_drop(nsp
, (int)nsctlerrmap
[cmd
]);
register struct nspcb
*nsp
;
register struct sppcb
*cb
= (struct sppcb
*)(nsp
->nsp_pcb
);
register struct spidp
*si
;
struct mbuf
*firstbad
, *m0
;
* The notification that we have sent
* too much is bad news -- we will
* have to go through queued up so far
* splitting ones which are too big and
* reassigning sequence numbers and checksums.
* we should then retransmit all packets from
* one above the offending packet to the last one
* we had sent (or our allocation)
* then the offending one so that the any queued
* data at our destination will be discarded.
ep
= (struct ns_errp
*)nsp
->nsp_notify_param
;
sb
= &nsp
->nsp_socket
->so_snd
;
cb
->s_mtu
= ep
->ns_err_param
;
badseq
= SI(&ep
->ns_err_idp
)->si_seq
;
for (m
= sb
->sb_mb
; m
; m
= m
->m_act
) {
si
= mtod(m
, struct spidp
*);
if (si
->si_seq
== badseq
)
for (m0
= m
, len
= 0; m
; m
= m
->m_next
)
register struct sppcb
*cb
;
struct socket
*so
= cb
->s_nspcb
->nsp_socket
;
register struct spidp
*si
= (struct spidp
*) 0;
register struct sockbuf
*sb
= &(so
->so_snd
);
for (m
= m0
; m
; m
= m
->m_next
) {
if (cb
->s_flags
& SF_PI
) {
m
= m_copy(m0
, off
, mtu
);
error
= spp_output(cb
, m
);
* Force length even, by adding a "garbage byte" if
if (m
->m_len
+ m
->m_off
< MMAXOFF
)
struct mbuf
*m1
= m_get(M_DONTWAIT
, MT_DATA
);
m
= m_get(M_DONTWAIT
, MT_HEADER
);
* Fill in mbuf with extended SP header
* and addresses and length put into network format.
m
->m_off
= MMAXOFF
- sizeof (struct spidp
);
m
->m_len
= sizeof (struct spidp
);
si
= mtod(m
, struct spidp
*);
if ((cb
->s_flags
& SF_PI
) && (cb
->s_flags
& SF_HO
)) {
register struct sphdr
*sh
;
if (m0
->m_len
< sizeof (*sh
)) {
if((m0
= m_pullup(m0
, sizeof(*sh
))) == NULL
) {
sh
= mtod(m0
, struct sphdr
*);
si
->si_cc
|= sh
->sp_cc
& SP_EM
;
m0
->m_len
-= sizeof (*sh
);
m0
->m_off
+= sizeof (*sh
);
if (cb
->s_oobflags
& SF_SOOB
) {
* make sure OB packets convey exactly 1 byte.
* If the packet is 1 byte or larger, we
* have already guaranted there to be at least
* one garbage byte for the checksum, and
* extra bytes shouldn't hurt!
si
->si_len
= htons((u_short
)len
);
* queue stuff up for output
register struct sockbuf
*sb2
= &so
->so_rcv
;
int credit
= ((sb2
->sb_mbmax
- sb2
->sb_mbcnt
) / cb
->s_mtu
);
int alo
= cb
->s_ack
+ credit
;
if (cb
->s_oobflags
& SF_SOOB
) {
* must transmit this out of band packet
cb
->s_oobflags
&= ~ SF_SOOB
;
* Decide what to transmit:
* If we have a new packet, send that
* (So long as it is in our allocation)
* If it is time to retransmit a packet,
* Otherwise, see if it time to bang on them
* to ask for our current allocation.
if (SSEQ_LT(cb
->s_snt
, cb
->s_ralo
))
else if (cb
->s_force
== (1+TCPT_REXMT
)) {
} else if (SSEQ_LT(cb
->s_ralo
, cb
->s_seq
)) {
if (cb
->s_timer
[TCPT_PERSIST
] == 0) {
/* tcp has cb->s_rxtshift = 0; here */
si
= mtod(m
, struct spidp
*);
if (SSEQ_LT(si
->si_seq
, cb
->s_rack
)) {
if ((sb
->sb_flags
& SB_WAIT
)
if (SSEQ_LT(si
->si_seq
, lookfor
))
if (si
&& (si
->si_seq
!= lookfor
))
* must make a copy of this packet for
* idp_output to monkey with
m
= m_copy(dtom(si
), 0, (int)M_COPYALL
);
si
= mtod(m
, struct spidp
*);
} else if (cb
->s_force
|| cb
->s_flags
& SF_AK
) {
* Must send an acknowledgement or a probe
m
= m_get(M_DONTWAIT
, MT_HEADER
);
* Fill in mbuf with extended SP header
* and addresses and length put into network format.
m
->m_off
= MMAXOFF
- sizeof (struct spidp
);
si
= mtod(m
, struct spidp
*);
si
->si_seq
= cb
->s_snt
+ 1;
si
->si_len
= htons(sizeof (*si
));
* Stuff checksum and output datagram.
* If we are almost out of allocation
* or one of the timers has gone off
if (SSEQ_GEQ(cb
->s_seq
, cb
->s_ralo
))
* If this is a new packet (and not a system packet),
* and we are not currently timing anything,
* time this one and ask for an ack.
if (SSEQ_LT(cb
->s_snt
, si
->si_seq
) && (!(si
->si_cc
& SP_SP
))) {
cb
->s_rtseq
= si
->si_seq
;
* If the retransmit timer has not been set
* and this is a real packet
* then start the retransmit timer
if (cb
->s_timer
[TCPT_REXMT
] == 0) {
TCPT_RANGESET(cb
->s_timer
[TCPT_REXMT
],
tcp_beta
* cb
->s_srtt
, TCPTV_MIN
,
si
->si_seq
= htons(si
->si_seq
);
si
->si_alo
= htons(cb
->s_alo
);
si
->si_ack
= htons(cb
->s_ack
);
si
->si_sum
= ns_cksum(dtom(si
), len
);
if (so
->so_options
& SO_DEBUG
|| traceallspps
)
spp_trace(SA_OUTPUT
, cb
->s_state
, cb
, si
, 0);
if (so
->so_options
& SO_DONTROUTE
)
error
= ns_output(m
, (struct route
*)0, NS_ROUTETOIF
);
error
= ns_output(m
, &cb
->s_nspcb
->nsp_route
, 0);
if (traceallspps
&& sppconsdebug
) {
printf("spp_out: %x\n", error
);
if (so
->so_options
& SO_DEBUG
|| traceallspps
)
spp_trace(SA_OUTPUT
, cb
->s_state
, cb
, si
, 0);
spp_ctloutput(req
, so
, level
, name
, value
)
struct nspcb
*nsp
= sotonspcb(so
);
register struct sppcb
*cb
;
if (level
!= NSPROTO_SPP
) {
/* This will have to be changed when we do more general
return (idp_ctloutput(req
, so
, level
, name
, value
));
m
= m_get(M_DONTWAIT
, MT_DATA
);
case SO_HEADERS_ON_INPUT
:
case SO_HEADERS_ON_OUTPUT
:
m
->m_len
= sizeof(short);
m
->m_off
= MMAXOFF
- sizeof(short);
*mtod(m
, short *) = cb
->s_flags
& mask
;
m
->m_len
= sizeof(struct sphdr
);
m
->m_off
= MMAXOFF
- sizeof(struct sphdr
);
*mtod(m
, struct sphdr
*) = cb
->s_rhdr
;
m
->m_len
= sizeof(struct spidp
);
m
->m_off
= MMAXOFF
- sizeof(struct sphdr
);
*mtod(m
, struct sphdr
*) = cb
->s_shdr
.si_s
;
case SO_HEADERS_ON_INPUT
:
case SO_HEADERS_ON_OUTPUT
:
ok
= mtod(*value
, int *);
register struct sphdr
*sp
= mtod(*value
, struct sphdr
*);
cb
->s_cc
= sp
->sp_cc
& SP_EM
;
spp_usrreq(so
, req
, m
, nam
, rights
)
struct mbuf
*m
, *nam
, *rights
;
struct nspcb
*nsp
= sotonspcb(so
);
register struct sppcb
*cb
;
return (ns_control(so
, (int)m
, (caddr_t
)nam
,
(struct ifnet
*)rights
));
if (rights
&& rights
->m_len
) {
ostate
= cb
? cb
->s_state
: 0;
error
= ns_pcballoc(so
, &nspcb
);
error
= soreserve(so
, 2048, 2048);
struct mbuf
*mm
= m_getclr(M_DONTWAIT
,MT_PCB
);
cb
= mtod(mm
, struct sppcb
*);
cb
->s_state
= TCPS_LISTEN
;
cb
->s_q
.si_next
= cb
->s_q
.si_prev
= &cb
->s_q
;
nsp
->nsp_pcb
= (caddr_t
) cb
;
if (cb
->s_state
> TCPS_LISTEN
)
error
= ns_pcbbind(nsp
, nam
);
error
= ns_pcbbind(nsp
, (struct mbuf
*)0);
cb
->s_state
= TCPS_LISTEN
;
* Initiate connection to peer.
* Enter SYN_SENT state, and mark socket as connecting.
* Start keep-alive timer, setup prototype header,
* Send initial system packet requesting connection.
if (nsp
->nsp_lport
== 0) {
error
= ns_pcbbind(nsp
, (struct mbuf
*)0);
error
= ns_pcbconnect(nsp
, nam
);
cb
->s_state
= TCPS_SYN_SENT
;
cb
->s_timer
[TCPT_KEEP
] = TCPTV_KEEP
;
cb
->s_force
= 1 + TCPTV_KEEP
;
* Other party is required to respond to
* the port I send from, but he is not
* required to answer from where I am sending to,
* original port I am sending to is still saved in
error
= spp_output(cb
, (struct mbuf
*) 0);
* We may decide later to implement connection closing
* handshaking at the spp level optionally.
* here is the hook to do it:
* Accept a connection. Essentially all the work is
* done at higher levels; just return the address
* of the peer, storing through addr.
struct sockaddr_ns
*sns
= mtod(nam
, struct sockaddr_ns
*);
nam
->m_len
= sizeof (struct sockaddr_ns
);
sns
->sns_addr
= nsp
->nsp_faddr
;
error
= spp_output(cb
, (struct mbuf
*) 0);
* After a receive, possibly send acknowledgment
(void) spp_output(cb
, (struct mbuf
*) 0);
error
= spp_output(cb
, m
);
(void) spp_drop(cb
, ECONNABORTED
);
if ( ! (cb
->s_oobflags
& SF_IOOB
) ) {
*mtod(m
, caddr_t
) = cb
->s_iobc
;
cb
->s_oobflags
&= ~ SF_IOOB
;
if (sbspace(&so
->so_snd
) < -512) {
cb
->s_oobflags
|= SF_SOOB
;
error
= spp_output(cb
, m
);
cb
->s_oobflags
&= ~SF_SOOB
;
ns_setsockaddr(nsp
, nam
);
ns_setpeeraddr(nsp
, nam
);
cb
= spp_timers(cb
, (int)nam
);
if (cb
&& (so
->so_options
& SO_DEBUG
|| traceallspps
))
spp_trace(SA_USER
, (u_char
)ostate
, cb
, (struct spidp
*)0, req
);
spp_usrreq_sp(so
, req
, m
, nam
, rights
)
struct mbuf
*m
, *nam
, *rights
;
int error
= spp_usrreq(so
, req
, m
, nam
, rights
);
if (req
== PRU_ATTACH
&& error
== 0) {
struct nspcb
*nsp
= sotonspcb(so
);
((struct sppcb
*)nsp
->nsp_pcb
)->s_flags
|=
* Create template to be used to send spp packets on a connection.
* Called after host entry created, fills
* in a skeletal spp header (choosing connection id),
* minimizing the amount of work necessary when the connection is used.
register struct nspcb
*nsp
= cb
->s_nspcb
;
register struct spidp
*n
= &(cb
->s_shdr
);
n
->si_sna
= nsp
->nsp_laddr
;
n
->si_dna
= nsp
->nsp_faddr
;
n
->si_sid
= htons(spp_iss
);
spp_iss
+= SPP_ISSINCR
/2;
* Close a SPIP control block:
* discard spp control block itself
* discard ns protocol control block
register struct sppcb
*cb
;
register struct spidp_q
*s
;
struct nspcb
*nsp
= cb
->s_nspcb
;
struct socket
*so
= nsp
->nsp_socket
;
while (s
!= &(cb
->s_q
)) {
return ((struct sppcb
*)0);
* Someday we may do level 3 handshaking
* to close a connection or send a xerox style error.
register struct sppcb
*cb
;
register struct sppcb
*cb
;
* Drop connection, reporting
register struct sppcb
*cb
;
struct socket
*so
= cb
->s_nspcb
->nsp_socket
;
* someday, in the xerox world
* we will generate error protocol packets
* announcing that the socket has gone away.
/*if (TCPS_HAVERCVDSYN(tp->t_state)) {
tp->t_state = TCPS_CLOSED;
(void) spp_close((struct sppcb
*)nsp
->nsp_pcb
);
register struct sppcb
*cb
;
/*if (cb->s_timer[TCPT_REXMT])
panic("spp_output REXMT");*/
* Start/restart persistance timer.
TCPT_RANGESET(cb
->s_timer
[TCPT_PERSIST
],
((int)(tcp_beta
* cb
->s_srtt
)) << cb
->s_rxtshift
,
TCPTV_PERSMIN
, TCPTV_MAX
);
if (cb
->s_rxtshift
>= TCP_MAXRXTSHIFT
)
* Fast timeout routine for processing delayed acks
register struct nspcb
*nsp
;
register struct sppcb
*cb
;
for (; nsp
!= &nspcb
; nsp
= nsp
->nsp_next
)
if ((cb
= (struct sppcb
*)nsp
->nsp_pcb
) &&
(cb
->s_flags
& SF_DELACK
)) {
cb
->s_flags
&= ~SF_DELACK
;
(void) spp_output(cb
, (struct mbuf
*) 0);
* spp protocol timeout routine called every 500 ms.
* Updates the timers in all active pcb's and
* causes finite state machine actions if timers expire.
register struct nspcb
*ip
, *ipnxt
;
register struct sppcb
*cb
;
* Search through tcb's and update active timers.
for (i
= 0; i
< TCPT_NTIMERS
; i
++) {
if (cb
->s_timer
[i
] && --cb
->s_timer
[i
] == 0) {
(void) spp_usrreq(cb
->s_nspcb
->nsp_socket
,
PRU_SLOWTIMO
, (struct mbuf
*)0,
(struct mbuf
*)i
, (struct mbuf
*)0);
if (ipnxt
->nsp_prev
!= ip
)
spp_iss
+= SPP_ISSINCR
/PR_SLOWHZ
; /* increment iss */
float spp_backoff
[TCP_MAXRXTSHIFT
] =
{ 1.0, 1.2, 1.4, 1.7, 2.0, 3.0, 5.0, 8.0, 16.0, 32.0 };
int sppexprexmtbackoff
= 0;
register struct sppcb
*cb
;
* 2 MSL timeout in shutdown went off. Delete connection
* Retransmission timer went off. Message has not
* been acked within retransmit interval. Back off
* to a longer retransmit interval and retransmit all
* unacknowledged messages in the window.
if (cb
->s_rxtshift
> TCP_MAXRXTSHIFT
) {
cb
= spp_drop(cb
, ETIMEDOUT
);
(void) spp_output(cb
, (struct mbuf
*) 0);
TCPT_RANGESET(cb
->s_timer
[TCPT_REXMT
],
(int)cb
->s_srtt
, TCPTV_MIN
, TCPTV_MAX
);
if (sppexprexmtbackoff
) {
TCPT_RANGESET(cb
->s_timer
[TCPT_REXMT
],
cb
->s_timer
[TCPT_REXMT
] << cb
->s_rxtshift
,
TCPT_RANGESET(cb
->s_timer
[TCPT_REXMT
],
cb
->s_timer
[TCPT_REXMT
] *
spp_backoff
[cb
->s_rxtshift
- 1],
* Persistance timer into zero window.
* Force a probe to be sent.
(void) spp_output(cb
, (struct mbuf
*) 0);
* Keep-alive timer went off; send something
* or drop connection if idle for too long.
if (cb
->s_state
< TCPS_ESTABLISHED
)
if (cb
->s_nspcb
->nsp_socket
->so_options
& SO_KEEPALIVE
) {
if (cb
->s_idle
>= TCPTV_MAXIDLE
)
(void) spp_output(cb
, (struct mbuf
*) 0);
cb
->s_timer
[TCPT_KEEP
] = TCPTV_KEEP
;
cb
= spp_drop(cb
, ETIMEDOUT
);