diff packages/redboot/current/src/net/tcp.c @ 115:6ed91473a1cd ecos-sw-2000-08-21

Merge from eCos master repository on 2000-08-21-22:40:54-BST
author jlarmour
date Fri, 25 Aug 2000 17:32:38 +0000
parents
children 6b5f480c6929
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/packages/redboot/current/src/net/tcp.c
@@ -0,0 +1,743 @@
+//==========================================================================
+//
+//      net/tcp.c
+//
+//      Stand-alone TCP networking support for RedBoot
+//
+//==========================================================================
+//####COPYRIGHTBEGIN####
+//                                                                          
+// -------------------------------------------                              
+// The contents of this file are subject to the Red Hat eCos Public License 
+// Version 1.1 (the "License"); you may not use this file except in         
+// compliance with the License.  You may obtain a copy of the License at    
+// http://www.redhat.com/                                                   
+//                                                                          
+// Software distributed under the License is distributed on an "AS IS"      
+// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the 
+// License for the specific language governing rights and limitations under 
+// the License.                                                             
+//                                                                          
+// The Original Code is eCos - Embedded Configurable Operating System,      
+// released September 30, 1998.                                             
+//                                                                          
+// The Initial Developer of the Original Code is Red Hat.                   
+// Portions created by Red Hat are                                          
+// Copyright (C) 1998, 1999, 2000 Red Hat, Inc.                             
+// All Rights Reserved.                                                     
+// -------------------------------------------                              
+//                                                                          
+//####COPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):    gthomas
+// Contributors: gthomas
+// Date:         2000-07-14
+// Purpose:      
+// Description:  
+//              
+// This code is part of RedBoot (tm).
+//
+//####DESCRIPTIONEND####
+//
+//==========================================================================
+
+#include <net/net.h>
+
+#define MAX_TCP_SEGMENT (ETH_MAX_PKTLEN - (sizeof(eth_header_t) + sizeof(ip_header_t)))
+#define MAX_TCP_DATA    (MAX_TCP_SEGMENT - sizeof(tcp_header_t))
+
+
+/* sequence number comparison macros */
+#define SEQ_LT(a,b) ((int)((a)-(b)) < 0)
+#define SEQ_LE(a,b) ((int)((a)-(b)) <= 0)
+#define SEQ_GT(a,b) ((int)((a)-(b)) > 0)
+#define SEQ_GE(a,b) ((int)((a)-(b)) >= 0)
+
+
+static void do_retrans(void *p);
+static void do_close(void *p);
+
+#ifdef BSP_LOG
+static char *
+flags_to_str(octet f)
+{
+    static char str[7], *p;
+
+    p = str;
+
+    if (f & TCP_FLAG_FIN)
+	*p++ = 'F';
+    if (f & TCP_FLAG_SYN)
+	*p++ = 'S';
+    if (f & TCP_FLAG_RST)
+	*p++ = 'R';
+    if (f & TCP_FLAG_PSH)
+	*p++ = 'P';
+    if (f & TCP_FLAG_ACK)
+	*p++ = 'A';
+    if (f & TCP_FLAG_URG)
+	*p++ = 'U';
+    *p = '\0';
+    return str;
+}
+#endif
+
+/*
+ * A major assumption is that only a very small number of sockets will
+ * active, so a simple linear search of those sockets is acceptible.
+ */
+static tcp_socket_t *tcp_list;
+
+/*
+ * Format and send an outgoing segment.
+ */
+static void
+tcp_send(tcp_socket_t *s, int flags, int resend)
+{
+    tcp_header_t *tcp;
+    ip_header_t  *ip;
+    pktbuf_t     *pkt = &s->pkt;
+    unsigned short cksum;
+    dword         tcp_magic;
+    int           tcp_magic_size = sizeof(tcp_magic);
+
+    ip = pkt->ip_hdr;
+    tcp = pkt->tcp_hdr;
+
+    if (flags & TCP_FLAG_SYN) {
+	/* If SYN, assume no data and send MSS option in tcp header */
+	pkt->pkt_bytes = sizeof(tcp_header_t) + 4;
+	tcp->hdr_len = 6;
+        tcp_magic = htonl(0x02040000 | MAX_TCP_DATA);
+	memcpy((unsigned char *)(tcp+1), &tcp_magic, tcp_magic_size);
+	s->data_bytes = 0;
+    } else {
+	pkt->pkt_bytes = s->data_bytes + sizeof(tcp_header_t);
+	tcp->hdr_len = 5;
+    }
+
+    /* tcp header */
+    tcp->reserved = 0;
+    tcp->seqnum = htonl(s->seq);
+    tcp->acknum = htonl(s->ack);
+    tcp->checksum = 0;
+
+    if (!resend) {
+	tcp->src_port = htons(s->our_port);
+	tcp->dest_port = htons(s->his_port);
+	tcp->flags = flags;
+	/* always set PUSH flag if sending data */
+	if (s->data_bytes)
+	    tcp->flags |= TCP_FLAG_PSH;
+	tcp->window = htons(MAX_TCP_DATA);
+	tcp->urgent = 0;
+
+	/* fill in some pseudo-header fields */
+	memcpy(ip->source, __local_ip_addr, sizeof(ip_addr_t));
+	memcpy(ip->destination, s->his_addr.ip_addr, sizeof(ip_addr_t));
+	ip->protocol = IP_PROTO_TCP;
+    }
+
+    /* another pseudo-header field */
+    ip->length = htons(pkt->pkt_bytes);
+
+    /* compute tcp checksum */
+    cksum = __sum((word *)tcp, pkt->pkt_bytes, __pseudo_sum(ip));
+    tcp->checksum = htons(cksum);
+
+    __ip_send(pkt, IP_PROTO_TCP, &s->his_addr);
+
+    BSPLOG(bsp_log("tcp_send: state[%d] flags[%s] ack[%x] data[%d].\n",
+		   s->state, flags_to_str(tcp->flags), s->ack, s->data_bytes));
+
+    if (s->state == _TIME_WAIT)
+	__timer_set(&s->timer, 120000, do_close, s);
+    else if ((tcp->flags & (TCP_FLAG_FIN | TCP_FLAG_SYN)) || s->data_bytes)
+	__timer_set(&s->timer, 1000, do_retrans, s);
+}
+
+
+/*
+ * Send a reset for a bogus incoming segment.
+ */
+static void
+send_reset(pktbuf_t *pkt, ip_route_t *r)
+{
+    ip_header_t   *ip = pkt->ip_hdr;
+    tcp_header_t  *tcp = pkt->tcp_hdr;
+    dword         seq, ack;
+    word          src, dest;
+    word          cksum;
+
+    seq = ntohl(tcp->acknum);
+    ack = ntohl(tcp->seqnum);
+    src = ntohs(tcp->dest_port);
+    dest = ntohs(tcp->src_port);
+
+    tcp = (tcp_header_t *)(ip + 1);
+    pkt->pkt_bytes = sizeof(tcp_header_t);
+    
+    /* tcp header */
+    tcp->hdr_len = 5;
+    tcp->reserved = 0;
+    tcp->seqnum = htonl(seq);
+    tcp->acknum = htonl(ack);
+    tcp->window = htons(1024);
+    tcp->urgent = 0;
+    tcp->checksum = 0;
+    tcp->src_port = htons(src);
+    tcp->dest_port = htons(dest);
+    tcp->flags = TCP_FLAG_RST | TCP_FLAG_ACK;
+
+    /* fill in some pseudo-header fields */
+    memcpy(ip->source, __local_ip_addr, sizeof(ip_addr_t));
+    memcpy(ip->destination, r->ip_addr, sizeof(ip_addr_t));
+    ip->protocol = IP_PROTO_TCP;
+    ip->length = htons(pkt->pkt_bytes);
+
+    /* compute tcp checksum */
+    cksum = __sum((word *)tcp, pkt->pkt_bytes, __pseudo_sum(ip));
+    tcp->checksum = htons(cksum);
+
+    __ip_send(pkt, IP_PROTO_TCP, r);
+}
+
+
+
+/*
+ * Remove given socket from socket list.
+ */
+static void
+unlink_socket(tcp_socket_t *s)
+{
+    tcp_socket_t *prev, *tp;
+
+    for (prev = NULL, tp = tcp_list; tp; prev = tp, tp = tp->next)
+	if (tp == s) {
+	    BSPLOG(bsp_log("unlink tcp socket.\n"));
+	    if (prev)
+		prev->next = s->next;
+	    else
+		tcp_list = s->next;
+	}
+}
+
+/*
+ * Retransmit last packet.
+ */
+static void
+do_retrans(void *p)
+{
+    BSPLOG(bsp_log("tcp do_retrans.\n"));
+    tcp_send((tcp_socket_t *)p, 0, 1);
+}
+
+
+static void
+do_close(void *p)
+{
+    BSPLOG(bsp_log("tcp do_close.\n"));
+    /* close connection */
+    ((tcp_socket_t *)p)->state = _CLOSED;
+    unlink_socket(p);
+}
+
+
+static void
+free_rxlist(tcp_socket_t *s)
+{
+    pktbuf_t *p;
+
+    BSPLOG(bsp_log("tcp free_rxlist.\n"));
+
+    while ((p = s->rxlist) != NULL) {
+	s->rxlist = p->next;
+	__pktbuf_free(p);
+    }
+}
+
+
+/*
+ * Handle a conection reset.
+ */
+static void
+do_reset(tcp_socket_t *s)
+{
+    /* close connection */
+    s->state = _CLOSED;
+    __timer_cancel(&s->timer);
+    free_rxlist(s);
+    unlink_socket(s);
+}
+
+
+/*
+ * Extract data from incoming tcp segment.
+ * Returns true if packet is queued on rxlist, false otherwise.
+ */
+static int
+handle_data(tcp_socket_t *s, pktbuf_t *pkt)
+{
+    tcp_header_t  *tcp = pkt->tcp_hdr;
+    unsigned int  diff, seq;
+    int           data_len;
+    char          *data_ptr;
+    pktbuf_t      *p;
+
+    data_len = pkt->pkt_bytes - (tcp->hdr_len << 2);
+    data_ptr = ((char *)tcp)  + (tcp->hdr_len << 2);
+
+    seq = ntohl(tcp->seqnum);
+
+    BSPLOG(bsp_log("tcp data: seq[%x] len[%d].\n", seq, data_len));
+
+    if (SEQ_LE(seq, s->ack)) {
+	/*
+	 * Figure difference between which byte we're expecting and which byte
+	 * is sent first. Adjust data length and data pointer accordingly.
+	 */
+	diff = s->ack - seq;
+	data_len -= diff;
+	data_ptr += diff;
+
+	if (data_len > 0) {
+	    /* queue the new data */
+	    s->ack += data_len;
+	    pkt->next = NULL;
+	    if ((p = s->rxlist) != NULL) {
+		while (p->next)
+		    p = p->next;
+		p->next = pkt;
+		BSPLOG(bsp_log("tcp data: Add pkt[%x] len[%d].\n",
+			       pkt, data_len));
+	    } else {
+		s->rxlist = pkt;
+		s->rxcnt = data_len;
+		s->rxptr = data_ptr;
+		BSPLOG(bsp_log("tcp data: pkt[%x] len[%d].\n",
+			       pkt, data_len));
+	    }
+	    return 1;
+	}
+    }
+    return 0;
+}
+
+
+static void
+handle_ack(tcp_socket_t *s, pktbuf_t *pkt)
+{
+    tcp_header_t *tcp = pkt->tcp_hdr;
+    dword        ack;
+    int          advance;
+    char         *dp;
+
+    /* process ack value in packet */
+    ack = ntohl(tcp->acknum);
+
+    BSPLOG(bsp_log("Rcvd tcp ACK %x\n", ack));
+
+    if (SEQ_GT(ack, s->seq)) {
+	__timer_cancel(&s->timer);
+	advance = ack - s->seq;
+	if (advance > s->data_bytes)
+	    advance = s->data_bytes;
+
+	BSPLOG(bsp_log("seq advance %d", advance));
+
+	if (advance > 0) {
+	    s->seq += advance;
+	    s->data_bytes -= advance;
+	    if (s->data_bytes) {
+		/* other end ack'd only part of the pkt */
+		BSPLOG(bsp_log(" %d bytes left", s->data_bytes));
+		dp = (char *)(s->pkt.tcp_hdr + 1);
+		memcpy(dp, dp + advance, s->data_bytes);
+	    }
+	}
+    }
+    BSPLOG(bsp_log("\n"));
+}
+
+
+/*
+ * Handle incoming TCP packets.
+ */
+void
+__tcp_handler(pktbuf_t *pkt, ip_route_t *r)
+{
+    tcp_header_t *tcp = pkt->tcp_hdr;
+    ip_header_t  *ip = pkt->ip_hdr;
+    tcp_socket_t *prev,*s;
+    dword        ack;
+    int          queued = 0;
+
+    /* set length for pseudo sum calculation */
+    ip->length = htons(pkt->pkt_bytes);
+
+    if (__sum((word *)tcp, pkt->pkt_bytes, __pseudo_sum(ip)) == 0) {
+	for (prev = NULL, s = tcp_list; s; prev = s, s = s->next) {
+	    if (s->our_port == ntohs(tcp->dest_port)) {
+		if (s->his_port == 0)
+		    break;
+		if (s->his_port == ntohs(tcp->src_port) &&
+		    !memcmp(r->ip_addr, s->his_addr.ip_addr, sizeof(ip_addr_t)))
+		    break;
+	    }
+	}
+
+	if (s) {
+	    /* found the socket this packet belongs to */
+	    
+	    /* refresh his ethernet address */
+	    memcpy(s->his_addr.enet_addr, r->enet_addr, sizeof(enet_addr_t));
+
+	    if (s->state != _SYN_RCVD && tcp->flags & TCP_FLAG_RST) {
+		BSPLOG(bsp_log("TCP_FLAG_RST rcvd\n"));
+		do_reset(s);
+		__pktbuf_free(pkt);
+		return;
+	    }
+
+	    switch (s->state) {
+
+	      case _SYN_SENT:
+		/* active open not supported */
+		break;
+
+	      case _LISTEN:
+		if (tcp->flags & TCP_FLAG_SYN) {
+		    s->state = _SYN_RCVD;
+		    s->ack = ntohl(tcp->seqnum) + 1;
+		    s->his_port = ntohs(tcp->src_port);
+		    memcpy(s->his_addr.ip_addr, r->ip_addr, sizeof(ip_addr_t));
+		    s->data_bytes = 0;
+		    s->ack_pending = 0;
+
+		    BSPLOG(bsp_log("SYN from %d.%d.%d.%d:%d (seq %x)\n",
+			       s->his_addr.ip_addr[0],s->his_addr.ip_addr[1],
+			       s->his_addr.ip_addr[2],s->his_addr.ip_addr[3],
+			       s->his_port, ntohl(tcp->seqnum)));
+
+		    tcp_send(s, TCP_FLAG_SYN | TCP_FLAG_ACK, 0);
+		}
+		else
+		    send_reset(pkt, r);
+		break;
+
+	      case _SYN_RCVD:
+		BSPLOG(bsp_log("_SYN_RCVD timer cancel.\n"));
+		__timer_cancel(&s->timer);
+
+		/* go back to _LISTEN state if reset */
+		if (tcp->flags & TCP_FLAG_RST) {
+		    s->state = _LISTEN;
+
+		    BSPLOG(bsp_log("_SYN_RCVD --> _LISTEN\n"));
+
+		} else if (tcp->flags & TCP_FLAG_SYN) {
+		    /* apparently our SYN/ACK was lost? */
+		    tcp_send(s, 0, 1);
+
+		    BSPLOG(bsp_log("retransmitting SYN/ACK\n"));
+
+		} else if ((tcp->flags & TCP_FLAG_ACK) &&
+			   ntohl(tcp->acknum) == (s->seq + 1)) {
+		    /* we've established the connection */
+		    s->state = _ESTABLISHED;
+		    s->seq++;
+
+		    BSPLOG(bsp_log("ACK received - connection established\n"));
+		}
+		break;
+
+	      case _ESTABLISHED:
+	      case _CLOSE_WAIT:
+		ack = s->ack;  /* save original ack */
+		if (tcp->flags & TCP_FLAG_ACK)
+		    handle_ack(s, pkt);
+
+		queued = handle_data(s, pkt);
+
+		if ((tcp->flags & TCP_FLAG_FIN) &&
+		    ntohl(tcp->seqnum) == s->ack) {
+
+		    BSPLOG(bsp_log("FIN received - going to _CLOSE_WAIT\n"));
+
+		    s->ack++;
+		    s->state = _CLOSE_WAIT;
+		}
+		/*
+		 * Send an ack if neccessary.
+		 */
+		if (s->ack != ack || s->ack_pending) {
+		    /*
+		     * we can't send an ack if our write packet is holding outgoing
+		     * data pending an incoming ack.
+		     */
+		    if (s->data_bytes == 0) {
+			BSPLOG(bsp_log("Sending ack: new[%x] old[%x]\n",
+				       s->ack, ack));
+			__timer_cancel(&s->timer);
+			tcp_send(s, TCP_FLAG_ACK, 0);
+			s->ack_pending = 0;
+		    } else
+			s->ack_pending = 1;
+		}
+		break;
+
+	      case _LAST_ACK:
+		if (tcp->flags & TCP_FLAG_ACK) {
+		    handle_ack(s, pkt);
+		    if (ntohl(tcp->acknum) == (s->seq + 1)) {
+			BSPLOG(bsp_log("_LAST_ACK --> _CLOSED\n"));
+			s->state = _CLOSED;
+			unlink_socket(s);
+		    }
+		}
+		break;
+
+	      case _FIN_WAIT_1:
+		if (tcp->flags & TCP_FLAG_ACK) {
+		    handle_ack(s, pkt);
+		    if (ntohl(tcp->acknum) == (s->seq + 1)) {
+			/* got ACK for FIN packet */
+			if (tcp->flags & TCP_FLAG_FIN) {
+			    BSPLOG(bsp_log("_FIN_WAIT_1 --> _TIME_WAIT\n"));
+			    s->ack++;
+			    s->state = _TIME_WAIT;
+			    tcp_send(s, TCP_FLAG_ACK, 0);
+			} else {
+			    s->state = _FIN_WAIT_2;
+			    BSPLOG(bsp_log("_FIN_WAIT_1 --> _FIN_WAIT_2\n"));
+			}
+		    }
+		}
+		if (tcp->flags & TCP_FLAG_FIN) {
+		    BSPLOG(bsp_log("_FIN_WAIT_1 --> _CLOSING\n"));
+		    __timer_cancel(&s->timer);
+		    s->ack++;
+		    s->state = _CLOSING;
+		    tcp_send(s, TCP_FLAG_FIN | TCP_FLAG_ACK, 0);
+		}
+		break;
+
+	      case _FIN_WAIT_2:
+		queued = handle_data(s, pkt);
+		if (tcp->flags & TCP_FLAG_FIN) {
+		    BSPLOG(bsp_log("_FIN_WAIT_2 --> _TIME_WAIT\n"));
+		    s->ack++;
+		    s->state = _TIME_WAIT;
+		    tcp_send(s, TCP_FLAG_ACK, 0);
+		}
+		break;
+
+	      case _CLOSING:
+		if (tcp->flags & TCP_FLAG_ACK) {
+		    handle_ack(s, pkt);
+		    if (ntohl(tcp->acknum) == (s->seq + 1)) {
+			/* got ACK for FIN packet */
+			BSPLOG(bsp_log("_CLOSING --> _TIME_WAIT\n"));
+			__timer_cancel(&s->timer);
+			s->state = _TIME_WAIT;
+		    }
+		}
+		break;
+
+	      case _TIME_WAIT:
+		BSPLOG(bsp_log("_TIME_WAIT resend.\n"));
+		tcp_send(s, 0, 1); /* just resend ack */
+		break;
+	    }
+	} else {
+	    BSPLOG(bsp_log("Unexpected segment from: %d.%d.%d.%d:%d\n",
+			   r->ip_addr[0], r->ip_addr[1], r->ip_addr[3],
+			   r->ip_addr[4], ntohs(tcp->src_port)));
+	    send_reset(pkt, r);
+	}
+    }
+    if (!queued)
+	__pktbuf_free(pkt);
+}
+
+
+void
+__tcp_poll(void)
+{
+    __enet_poll();
+    __timer_poll();
+}
+
+
+int
+__tcp_listen(tcp_socket_t *s, word port)
+{
+    BSPLOG(bsp_log("tcp_listen: s[%p] port[%x]\n", s, port));
+
+    memset(s, 0, sizeof(tcp_socket_t));
+    s->state    = _LISTEN;
+    s->our_port = port;
+    s->pkt.buf = (word *)s->pktbuf;
+    s->pkt.bufsize = ETH_MAX_PKTLEN;
+#if 0
+    s->pkt.eth_hdr = (eth_header_t *)s->pkt.buf;
+    s->pkt.ip_hdr  = (ip_header_t *)(s->pkt.eth_hdr + 1);
+#else
+    s->pkt.ip_hdr  = (ip_header_t *)s->pkt.buf;
+#endif
+    s->pkt.tcp_hdr = (tcp_header_t *)(s->pkt.ip_hdr + 1);
+
+    s->next = tcp_list;
+
+#if 1
+    /* limit to one open socket at a time */
+    if (s->next) {
+	BSPLOG(bsp_log("tcp_listen: recursion error\n"));
+	BSPLOG(while(1));
+    }
+#endif
+    
+    tcp_list = s;
+
+    return 0;
+}
+
+
+/*
+ * Block while waiting for all data to be transmitted.
+ */
+void
+__tcp_drain(tcp_socket_t *s)
+{
+    BSPLOG(bsp_log("__tcp_drain.\n"));
+    while (s->state != _CLOSED && s->data_bytes)
+	__tcp_poll();
+    BSPLOG(bsp_log("__tcp_drain done.\n"));
+}
+
+
+
+/*
+ * Close the tcp connection.
+ */
+void
+__tcp_close(tcp_socket_t *s)
+{
+    __tcp_drain(s);
+    if (s->state == _ESTABLISHED || s->state == _SYN_RCVD) {
+	BSPLOG(bsp_log("__tcp_close: going to _FIN_WAIT_1\n"));
+	s->state = _FIN_WAIT_1;
+	tcp_send(s, TCP_FLAG_ACK | TCP_FLAG_FIN, 0);
+    } else if (s->state == _CLOSE_WAIT) {
+
+	BSPLOG(bsp_log("__tcp_close: going to _LAST_ACK\n"));
+
+	s->state = _LAST_ACK;
+	tcp_send(s, TCP_FLAG_ACK | TCP_FLAG_FIN, 0);
+    }
+    free_rxlist(s);
+}
+
+
+/*
+ * Wait for connection to be fully closed.
+ */
+void
+__tcp_close_wait(tcp_socket_t *s)
+{
+    BSPLOG(bsp_log("__tcp_close_wait.\n"));
+    while (s->state != _CLOSED)
+	__tcp_poll();
+    BSPLOG(bsp_log("__tcp_close_wait done.\n"));
+}
+
+
+/*
+ * Read up to 'len' bytes without blocking.
+ */
+int
+__tcp_read(tcp_socket_t *s, char *buf, int len)
+{
+    int          nread;
+    pktbuf_t     *pkt;
+    tcp_header_t *tcp;
+
+    if (len <= 0 || s->rxcnt == 0)
+	return 0;
+
+    if (s->state != _ESTABLISHED && s->rxcnt == 0)
+	return -1;
+
+    nread = 0;
+    while (len) {
+	if (len < s->rxcnt) {
+	    memcpy(buf, s->rxptr, len);
+	    BSPLOG(bsp_log("tcp_read: read %d bytes.\n", len));
+	    s->rxptr += len;
+	    s->rxcnt -= len;
+	    nread    += len;
+
+	    BSPLOG(bsp_log("tcp_read: %d bytes left in rxlist head.\n",
+		       s->rxcnt));
+
+	    break;
+	} else {
+	    memcpy(buf, s->rxptr, s->rxcnt);
+	    BSPLOG(bsp_log("tcp_read: read %d bytes. pkt[%x] freed.\n",
+			   s->rxcnt, s->rxlist));
+	    nread += s->rxcnt;
+	    buf   += s->rxcnt;
+	    len   -= s->rxcnt;
+
+	    /* setup for next packet in list */
+	    pkt = s->rxlist;
+	    s->rxlist = pkt->next;
+	    __pktbuf_free(pkt);
+
+	    if ((pkt = s->rxlist) != NULL) {
+		tcp = pkt->tcp_hdr;
+		s->rxcnt = pkt->pkt_bytes - (tcp->hdr_len << 2);
+		s->rxptr = ((char *)tcp)  + (tcp->hdr_len << 2);
+
+		BSPLOG(bsp_log("tcp_read: next pkt[%x] has %d bytes.\n",
+			   s->rxlist, s->rxcnt));
+	    } else {
+
+		BSPLOG(bsp_log("tcp_read: no more data.\n"));
+
+		s->rxcnt = 0;
+		break;
+	    }
+	}
+    }
+    return nread;
+}
+
+
+int
+__tcp_write(tcp_socket_t *s, char *buf, int len)
+{
+    tcp_header_t *tcp = s->pkt.tcp_hdr;
+
+    if (len <= 0)
+	return 0;
+
+    if (s->state != _ESTABLISHED && s->state != _CLOSE_WAIT)
+	return -1;
+
+    if (s->data_bytes)
+	return 0;
+
+    if (len > MAX_TCP_DATA)
+	len = MAX_TCP_DATA;
+
+    memcpy(tcp + 1, buf, len);
+    s->data_bytes = len;
+
+    tcp_send(s, TCP_FLAG_ACK, 0);
+
+    return len;
+}
+