www.pudn.com > uCOSII上实现的tcpip.rar > ztcp_output.c


#include "include/zeth.h"
#include "include/zarp.h"
#include "include/ztcp.h"
#include "include/ztask.h"
#include "include/zstats.h"


u8_t tcp_ip_out(znetif_t *pnetif, ipaddr_t *pdest_ip, ipaddr_t *psrc_ip,zbuffer_t * pbuffer)
{
	ip_header_t	*pipheader;
	eth_header_t	*pethheader;
	ethaddr_t	remote_eth;
	u8_t	i;

	if ( pnetif != NULL &&	pbuffer!= NULL)
	{
		pethheader = (eth_header_t *)pbuffer->pdata;
		pipheader = (ip_header_t *) ( (u8_t *)pbuffer->pdata + ETH_HEAD_LEN);

		IPH_VHLTOS_SET(pipheader, 4, IP_HEAD_LEN / 4, 0);
		IPH_LEN_SET(pipheader, pbuffer->tot_len - ETH_HEAD_LEN);
		IPH_OFFSET_SET(pipheader, IP_DF);
		IPH_ID_SET(pipheader, ++ip_id);
		IPH_TTL_SET(pipheader, TCP_TTL);
		IPH_PROTO_SET(pipheader, IP_PROTO_TCP);
		pipheader->dest_ipaddr = *pdest_ip;
		pipheader->src_ipaddr = *psrc_ip;
		IPH_CHKSUM_SET(pipheader, 0);
		IPH_CHKSUM_SET(pipheader, inet_chksum(pipheader, IP_HEAD_LEN));

		if ( arp_lookup( &remote_eth, pdest_ip) != 0 )
		{
			if ( arp_query(&remote_eth, pdest_ip ) != 0 )
			{
				return -1;
			}
		}

		/*last eth header*/
		for ( i = 0; i < 6 ; i ++)
		{
			pethheader->dest_hwaddr.addr[i] = remote_eth.addr[i];
			pethheader->src_hwaddr.addr[i] = pnetif->hwaddr.addr[i];
		}
		pethheader->eth_type = ETHTYPE_IP;
		pnetif->netif_tx(pnetif, pbuffer);
	}
	return 0;
}


u8_t tcp_reset(znetif_t *pnetif, ip_header_t *pipheader, tcp_header_t *ptcpheader)
{
	zbuffer_t	*sbuf;
	ip_header_t	*piph;
	eth_header_t	*pethh;
	tcp_header_t	*ptcph;
	s16_t			hoff;
	u16_t			offset;


	sbuf = zbuffer_new(IP_HEAD_LEN + ETH_HEAD_LEN + TCP_HEAD_LEN );
	if ( sbuf == NULL)
		return -1;

	/*setup information pointer*/
	pethh = (eth_header_t *)(sbuf->pdata);
	piph = (ip_header_t *)( (u8_t *)sbuf->pdata + ETH_HEAD_LEN);
	ptcph = (tcp_header_t *)((u8_t *)sbuf->pdata + IP_HEAD_LEN + ETH_HEAD_LEN);

	/*first setup TCP header*/
	ptcph->src = ptcpheader->dest;
	ptcph->dest = ptcpheader->src;

	if ( TCPH_FLAGS(ptcpheader) & TCP_ACK )
	{
		ptcph->seqno = ptcpheader->ackno;
		TCPH_FLAGS_SET(ptcph, TCP_RST);
	}
	else
	{
		ptcph->seqno = 0;
		TCPH_FLAGS_SET(ptcph, TCP_RST | TCP_ACK);
	}

	offset = IPH_LEN(pipheader) - IPH_HL(pipheader) * 4 - (TCPH_OFFSET(ptcpheader) >> 2) ;

	if ( TCPH_FLAGS(ptcpheader) & TCP_SYN)
	{
		offset ++;
	}
	if ( TCPH_FLAGS(ptcpheader) & TCP_FIN)
	{
		offset ++;
	}

	ptcph->ackno = ptcpheader->seqno + offset;
	TCPH_OFFSET_SET(ptcph, TCP_HEAD_LEN << 2);
	ptcph->wnd = ptcph->urgp = 0;

	hoff = (s16_t)(0 - (IP_HEAD_LEN + ETH_HEAD_LEN));
	if ( zbuffer_head_adjust(sbuf, hoff) != 0)
		return -1;
	ptcph->chksum = 0;
	ptcph->chksum = inet_chksum_pseudo(sbuf , &pipheader->src_ipaddr, &pipheader->dest_ipaddr, IP_PROTO_TCP, TCP_HEAD_LEN);
	hoff *= -1;
	if ( zbuffer_head_adjust(sbuf,hoff) != 0)
		return -1;

	/*send it to network at once*/
	tcp_ip_out(pnetif, &pipheader->src_ipaddr, &pipheader->dest_ipaddr, sbuf);

	zbuffer_delete(sbuf);
	return 0;
}

static void tcp_output_seg(tcp_pcb_t *ptcp, tcp_seg_t *seg)
{

	s16_t	h_off;

	/* The TCP header has already been constructed, but the ackno and
	   wnd fields remain. */
	seg->ptcpheader->ackno = (ptcp->rcv_nxt);

	/* silly window avoidance */
	if(ptcp->rcv_wnd < ptcp->mss) 
	{
		seg->ptcpheader->wnd = 0;
	} 
	else 
	{
		seg->ptcpheader->wnd = ptcp->rcv_wnd;
	}

	/* If we don't have a local IP address, we get one by
	   calling ip_route(). */

	ptcp->rtime = 0;

	if(ptcp->rttest == 0) 
	{
		ptcp->rttest = tcp_ticks;
		ptcp->rtseq = seg->ptcpheader->seqno;

		/*DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %lu\n", pcb->rtseq));*/
	}

	/*DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output_segment: %lu:%lu\n",
	  htonl(seg->tcphdr->seqno), htonl(seg->tcphdr->seqno) +
	  seg->len));*/

	h_off = (s16_t)(0 - (IP_HEAD_LEN + ETH_HEAD_LEN));
	
	zbuffer_head_adjust( seg->_ori_pbuffer, h_off);
	seg->ptcpheader->chksum = 0x00;
	seg->ptcpheader->chksum = inet_chksum_pseudo( seg->_ori_pbuffer,
			&(ptcp->_psocket->_lipaddr),&(ptcp->_psocket->_ripaddr),
			IP_PROTO_TCP,
			seg->_ori_pbuffer->tot_len);
	h_off = 0 - h_off;
	zbuffer_head_adjust( seg->_ori_pbuffer, h_off);

	

	tcp_ip_out(ptcp->_psocket->_pnetif,
			&ptcp->_psocket->_ripaddr, &ptcp->_psocket->_lipaddr,
			seg->_ori_pbuffer);
	

}

void tcp_rexmit_seg(tcp_pcb_t *ptcp, tcp_seg_t *seg)
{
	u32_t wnd;
	s16_t	h_off;

/*	DEBUGF(TCP_REXMIT_DEBUG, ("tcp_rexmit_seg: skickar %ld:%ld\n",
				ntohl(seg->tcphdr->seqno),
				ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));*/

	wnd = MIN(ptcp->snd_wnd, ptcp->cwnd);

	if((seg->ptcpheader->seqno) - ptcp->lastack + seg->len <= wnd) 
	{

		/* Count the number of retranmissions. */
		++ptcp->nrtx;
		
		seg->ptcpheader->ackno = (ptcp->rcv_nxt);
		seg->ptcpheader->wnd = (ptcp->rcv_wnd);

		/* Recalculate checksum. */
		/* Recalculate checksum. */
		seg->ptcpheader->chksum = 0;
		h_off = (s16_t)(0 - (IP_HEAD_LEN + ETH_HEAD_LEN));
		zbuffer_head_adjust( seg->_ori_pbuffer, h_off);
		seg->ptcpheader->chksum = inet_chksum_pseudo( seg->_ori_pbuffer,
				&(ptcp->_psocket->_lipaddr),&(ptcp->_psocket->_ripaddr),
				IP_PROTO_TCP,
				seg->_ori_pbuffer->tot_len);
		h_off = 0 - h_off;
		zbuffer_head_adjust( seg->_ori_pbuffer, h_off);

		ptcp->rtime = 0;

		/* Don't take any rtt measurements after retransmitting. */    
		ptcp->rttest = 0;

		tcp_ip_out(ptcp->_psocket->_pnetif,
				&ptcp->_psocket->_ripaddr, &ptcp->_psocket->_lipaddr,
				seg->_ori_pbuffer);	
	} 
	else 
	{
		/*DEBUGF(TCP_REXMIT_DEBUG, ("tcp_rexmit_seg: no room in window %lu to send %lu (ack %lu)\n",
					wnd, ntohl(seg->tcphdr->seqno), pcb->lastack));*/
	}
}



s8_t tcp_insert_queue( tcp_pcb_t *ptcp, void *pdata, u16_t data_len,
		u8_t flags, u8_t *optdata, u8_t opt_len)
{
	tcp_seg_t *seg, *useg, *queue;
	u32_t left, seqno;
	u16_t seglen, tmp_len;
	void *ptr;
	u8_t queuelen, i;
	s16_t	offset;


	left = data_len;
	ptr = pdata;

	if(data_len > ptcp->snd_buf) 
	{
		/*DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_enqueue: too much data %d\n", len));*/
		return ERR_MEM;
	}

	seqno = ptcp->snd_lbb;

	queue = NULL;
	/*DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: %d\n", pcb->snd_queuelen));*/
	queuelen = ptcp->snd_queuelen;
	if(queuelen >= TCP_SND_QUEUELEN) 
	{
		/*DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_enqueue: too long queue %d (max %d)\n", queuelen, TCP_SND_QUEUELEN));*/
		goto memerr;
	}   

	if(ptcp->snd_queuelen != 0) 
	{
		/*	ASSERT("tcp_enqueue: valid queue length", pcb->unacked != NULL ||
			pcb->unsent != NULL);      */
	}

	seg = NULL;
	seglen = 0;

	while(queue == NULL || left > 0)
	{

		seglen = left > ptcp->mss? ptcp->mss: left;

		/* allocate memory for tcp_seg, and fill in fields */
		seg = tcp_seg_new();
		if(seg == NULL) 
		{
			/*DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_enqueue: could not allocate memory for tcp_seg\n"));*/
			goto memerr;
		}

		if(queue == NULL) {
			queue = seg;
		} else {
			for(useg = queue; useg->next != NULL; useg = useg->next);
			useg->next = seg;
		}

		/* If copy is set, memory should be allocated
		   and data copied into pbuf, otherwise data comes from
		   ROM or other static memory, and need not be copied. If
		   optdata is != NULL, we have options instead of data. */
		if(optdata != NULL) 
		{
			seg->_ori_pbuffer = zbuffer_new(TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN + opt_len);
			if (seg->_ori_pbuffer == NULL)
			{
				goto memerr;
			}
			++queuelen;
			seg->pdata = (void *)((u8_t *)seg->_ori_pbuffer->pdata + 
					TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN + opt_len);
			seg->pbuffer = seg->_ori_pbuffer;
			seg->ptcpheader = (void *)((u8_t *)seg->_ori_pbuffer->pdata + 
					IP_HEAD_LEN + ETH_HEAD_LEN);
		}
		else  /*there is no optdata*/
		{

			seg->_ori_pbuffer = zbuffer_new(TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN + seglen);
			if (seg->_ori_pbuffer == NULL)
			{
				goto memerr;
			}

			offset = 0 - (TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN );
			if ( zbuffer_head_adjust( seg->_ori_pbuffer, offset) != 0)
			{
				goto memerr;
			}
			tmp_len = seglen;
			zbuffer_write( seg->_ori_pbuffer, ptr , &tmp_len);	/*write data to segment from call*/
			if ( tmp_len != seglen)
			{
				goto memerr;
			}
			offset *= -1;
			if ( zbuffer_head_adjust(seg->_ori_pbuffer, offset) != 0)
			{
				goto memerr;
			}

			++queuelen;
			seg->pdata = (void *)((u8_t *)seg->_ori_pbuffer->pdata + 
					TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN);
			seg->pbuffer = seg->_ori_pbuffer;
			seg->ptcpheader = (void *)((u8_t *)seg->_ori_pbuffer->pdata + 
					IP_HEAD_LEN + ETH_HEAD_LEN);
		}

		if(queuelen > TCP_SND_QUEUELEN) 
		{
			/*	DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_enqueue: queue too long %d (%d)\n", queuelen, TCP_SND_QUEUELEN)); */	
			goto memerr;
		}

		seg->len = seglen;

		seg->ptcpheader->src = ptcp->_psocket->_lport;
		seg->ptcpheader->dest = ptcp->_psocket->_rport;
		
		seg->ptcpheader->seqno = (seqno);
		seg->ptcpheader->urgp = 0;
		TCPH_FLAGS_SET(seg->ptcpheader, flags);
		/* don't fill in tcphdr->ackno and tcphdr->wnd until later */

		if(optdata == NULL) 
		{
			TCPH_OFFSET_SET(seg->ptcpheader, 5 << 4);
		} else 
		{
			TCPH_OFFSET_SET(seg->ptcpheader, (5 + opt_len / 4) << 4);
			/* Copy options into data portion of segment.
			   Options can thus only be sent in non data carrying
			   segments such as SYN|ACK. */
			for ( i = 0;  i < opt_len; i++)
				*((u8_t *)seg->ptcpheader + TCP_HEAD_LEN + i) = optdata[i];
		}
		/*
		   DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_enqueue: queueing %lu:%lu (0x%x)\n",
		   ntohl(seg->tcphdr->seqno),
		   ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg),
		   flags));*/

		left -= seglen;
		seqno += seglen;
		ptr = (void *)((char *)ptr + seglen);
	}


	/* Go to the last segment on the ->unsent queue. */    
	if(ptcp->unsent == NULL) 
	{
		useg = NULL;
	}
	else 
	{
		for(useg = ptcp->unsent; useg->next != NULL; useg = useg->next);
	}

	/* If there is room in the last pbuf on the unsent queue,
	   chain the first pbuf on the queue together with that. */
	if(useg != NULL &&
			TCP_TCPLEN(useg) != 0 &&
			!(TCPH_FLAGS(useg->ptcpheader) & (TCP_SYN | TCP_FIN)) &&
			!(flags & (TCP_SYN | TCP_FIN)) &&
			useg->len + queue->len <= ptcp->mss) 
	{
		/* Remove TCP header from first segment. */
		offset = 0 - (TCP_HEAD_LEN + IP_HEAD_LEN + ETH_HEAD_LEN );
		queue->_ori_pbuffer = zbuffer_adjust( queue->_ori_pbuffer, offset, 0);
		zbuffer_add( useg->_ori_pbuffer, queue->_ori_pbuffer);
		useg->len += queue->len;			/*TCP's data update*/
		useg->next = queue->next;
		if(seg == queue) 
		{
			seg = NULL;
		}
		queue->_ori_pbuffer = NULL;			/*we will delete queue so we must set it to NULL*/
		queue->next = NULL;
		tcp_seg_delete(queue);			/*remove this segment*/
		queuelen --;					
	} 
	else 
	{      
		if(useg == NULL) 
		{
			ptcp->unsent = queue;
		} 
		else 
		{
			useg->next = queue;
		}
	}
	if((flags & TCP_SYN) || (flags & TCP_FIN)) 
	{
		++data_len;
	}
	ptcp->snd_lbb += data_len;
	ptcp->snd_buf -= data_len;
	ptcp->snd_queuelen = queuelen;
	/*DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: %d (after enqueued)\n", pcb->snd_queuelen));*/

	if(ptcp->snd_queuelen != 0) 
	{
		/*	ASSERT("tcp_enqueue: valid queue length", pcb->unacked != NULL ||
			pcb->unsent != NULL);
			*/
	}


	/* Set the PSH flag in the last segment that we enqueued, but only
	   if the segment has data (indicated by seglen > 0). */
	if(seg != NULL && seglen > 0 && seg->ptcpheader != NULL) 
	{
		TCPH_FLAGS_SET(seg->ptcpheader, TCPH_FLAGS(seg->ptcpheader) | TCP_PSH);
	}

	return ERR_OK;
memerr:

	if(queue != NULL) 
	{
		tcp_seg_delete(queue);
	}

	if(ptcp->snd_queuelen != 0) 
	{
		/*		
				ASSERT("tcp_enqueue: valid queue length", pcb->unacked != NULL ||
				pcb->unsent != NULL);
				*/
	}

	/*	DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue: %d (with mem err)\n", pcb->snd_queuelen));*/
	return ERR_MEM;
}

void tcp_abort(tcp_pcb_t *ptcp, zbuffer_t *pbuffer)
{

	ip_header_t	*pipheader;
	tcp_header_t *ptcpheader;

	/* Figure out on which TCP PCB list we are, and remove us. If we
	   are in an active state, call the receive function associated with
	   the PCB with a NULL argument, and send an RST to the remote end. */
	if(ptcp->state == TIME_WAIT) 
	{
		tcp_pcb_remove(&ptcp_tw_chain, ptcp);
		tcp_pcb_delete(ptcp);
	} 
	else if(ptcp->state == LISTEN) 
	{
		tcp_pcb_remove(&ptcp_listen_chain, ptcp);
		tcp_pcb_delete(ptcp);
	} 
	else 
	{
		if ( pbuffer != NULL)
		{
			pipheader = (ip_header_t *)( (u8_t *)pbuffer->pdata + ETH_HEAD_LEN);
			ptcpheader = (tcp_header_t *)((u8_t *)pbuffer->pdata + IP_HEAD_LEN + ETH_HEAD_LEN);
			tcp_reset(ptcp->_psocket->_pnetif, pipheader, ptcpheader);
		}
		tcp_pcb_remove(&ptcp_active_chain, ptcp);
		tcp_pcb_delete(ptcp);
	}
}


/* find out what we can send and send it */
s8_t tcp_output(tcp_pcb_t *ptcp)
{
	zbuffer_t *pbuffer;
	tcp_header_t *ptcpheader;
	tcp_seg_t *seg, *useg;
	u32_t wnd;

	wnd = MIN(ptcp->snd_wnd, ptcp->cwnd);


	seg = ptcp->unsent;

	if(ptcp->flags & TF_ACK_NOW) 
	{
		/* If no segments are enqueued but we should send an ACK, we
		   construct the ACK and send it. */
		ptcp->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
		pbuffer = zbuffer_new(IP_HEAD_LEN + ETH_HEAD_LEN + TCP_HEAD_LEN );
		ptcpheader = (tcp_header_t *)( (u8_t *)pbuffer->pdata + ETH_HEAD_LEN + IP_HEAD_LEN);

		/*setup ptcpheader's information*/
		ptcpheader->src = ptcp->_psocket->_lport;
		ptcpheader->dest = ptcp->_psocket->_rport;
		ptcpheader->seqno = ptcp->snd_nxt;
		ptcpheader->ackno = ptcp->rcv_nxt;
		TCPH_FLAGS_SET(ptcpheader, TCP_ACK);
		ptcpheader->wnd = ptcp->rcv_wnd;
		ptcpheader->urgp = 0;
		TCPH_OFFSET_SET(ptcpheader, 5 << 4);
		ptcpheader->chksum = 0;
		zbuffer_head_adjust(pbuffer, 0 - IP_HEAD_LEN - ETH_HEAD_LEN);
		ptcpheader->chksum = inet_chksum_pseudo(pbuffer, &(ptcp->_psocket->_lipaddr),
				&(ptcp->_psocket->_ripaddr), IP_PROTO_TCP, TCP_HEAD_LEN);
		zbuffer_head_adjust(pbuffer, IP_HEAD_LEN + ETH_HEAD_LEN);
		
		tcp_ip_out(ptcp->_psocket->_pnetif,
				&ptcp->_psocket->_ripaddr, &ptcp->_psocket->_lipaddr, pbuffer);
		
		zbuffer_delete(pbuffer);
		
		return ERR_OK;
	} 

	while(seg != NULL &&
			(seg->ptcpheader->seqno) - ptcp->lastack + seg->len <= wnd) 
	{
		ptcp->rtime = 0;
		
		ptcp->unsent = seg->next;

		seg->next = NULL;

		if(ptcp->state != SYN_SENT) 
		{
			TCPH_FLAGS_SET(seg->ptcpheader, TCPH_FLAGS(seg->ptcpheader) | TCP_ACK);
			ptcp->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
		}

		tcp_output_seg(ptcp, seg);

		ptcp->snd_nxt = (seg->ptcpheader->seqno) + TCP_TCPLEN(seg);
		
		if(TCP_SEQ_LT(ptcp->snd_max, ptcp->snd_nxt)) 
		{
			ptcp->snd_max = ptcp->snd_nxt;
		}

		/* put segment on unacknowledged list if length > 0 */

		if(TCP_TCPLEN(seg) > 0) 
		{


			seg->next = NULL;
			if(ptcp->unacked == NULL) 
			{
				ptcp->unacked = seg;
			} 
			else 
			{
				for(useg = ptcp->unacked; useg->next != NULL; useg = useg->next);
				useg->next = seg;
			}
		} 
		else
		{
			tcp_seg_delete(seg);	
		}
		
		seg = ptcp->unsent;


	}  
	return ERR_OK;
}