(H8移植編その2第10回)TCPの再送処理の実装準備

2010/11/25

あなたは 人目のお客様です.

う〜,風邪を引いてしまった.昨日は会社を早退してしまったのだが, 週末は組込みこぞうがあるので大事をとって,今日は会社休んでいる.

で,寝ているだけでも暇なので,午前中は部屋をあったかくしてちょっと作業を してしまおう.KOZOSのTCP/IP実装についての改良がちょっとたまっていたので, それを一気にアップしてしまおうと思う.

まずTCPの再送についてなのだけど,現状で実装されていない. しかし安定した通信を行うためには,やはり再送処理くらいは実装しておきたい (TCPのスライディングウィンドウとか輻輳制御は,通信の安定性でなく性能のための ものなので実装はしないが,再送処理は確実に通信を行うための制御なので, 実装するというポリシー).

ただパケットの再送を行うためには送信パケットをバッファリングして ACKが来たら廃棄して次のパケットを送信するという手順に書き換える必要がある (現状は送信しっぱなしの実装になっている).

しかし現状の実装だと,TCPのステータスごとに処理が tcp_recv_open(),tcp_recv_close(),tcp_recv_data()の3つに分かれていて, 再送処理をしようとすると,通常のデータを含むパケット以外にも,SYNやFINにまで 再送処理をそれぞれ入れる必要ができてしまい,なんだかなあな感じだ.

ということで,TCPのステータスに応じてパケットの処理を(それぞれの処理関数で) 行うのでなく,そもそもステータスごとに処理を分けるんでなくて, 処理関数を共通化してしまって,送信処理も共通化してしまうことで, 再送処理も共通化してしまいたい.

で,TCPの処理をごっそりと書き換えてみた.

修正点は以下.

まずtcp.cから,tcp_recv_open(),tcp_recv_close(),tcp_recv_data()の 3つの処理関数を削除する.

diff -ruN h8_09/os/tcp.c h8_10/os/tcp.c
--- h8_09/os/tcp.c	Mon Nov 15 22:05:30 2010
+++ h8_10/os/tcp.c	Thu Nov 25 10:22:26 2010
@@ -288,6 +288,7 @@
   return 0;
 }
 
+#if 0
 static int tcp_recv_open(struct netbuf *pkt,
 			 struct connection *con,
 			 struct tcp_header *tcphdr)
@@ -342,7 +343,9 @@
 
   return 0;
 }
+#endif
 
+#if 0
 static int tcp_recv_close(struct netbuf *pkt,
 			  struct connection *con,
 			  struct tcp_header *tcphdr)
@@ -416,6 +419,7 @@
 
   return 0;
 }
+#endif
 
 static int tcp_recv_flush(struct connection *con)
 {
@@ -453,6 +457,7 @@
   return 0;
 }
 
+#if 0
 static int tcp_recv_data(struct netbuf *pkt,
 			 struct connection *con,
 			 struct tcp_header *tcphdr)
@@ -482,12 +487,15 @@
 
   return 0;
 }
+#endif
 
 static int tcp_recv(struct netbuf *pkt)
 {
次にtcp_recv()の中で,削除した3関数と同等の処理を行うように書き直す.
 static int tcp_recv(struct netbuf *pkt)
 {
   struct netbuf *buf;
   struct connection *con;
   struct tcp_header *tcphdr;
+  int new_status;
+  int closed = 0, ret = 0;
 
   tcphdr = (struct tcp_header *)pkt->top;
 
   con = tcp_search_connection_from_addr(0,
 					pkt->option.common.ipaddr.addr,
 					tcphdr->dst_port,
 					tcphdr->src_port);
   if (!con)
     con = tcp_search_connection_from_addr(0, 0, tcphdr->dst_port, 0);
 
   if (!con) return 0;
 
+  new_status = con->status;
+
   if (tcphdr->flags & TCP_HEADER_FLAG_RST) {
-    buf = kz_kmalloc(sizeof(*buf));
-    buf->cmd = TCP_CMD_CLOSE;
-    buf->option.tcp.close.number = con->number;
-    kz_send(con->id, 0, (char *)buf);
+    closed++;
+  }
 
-    con = tcp_delete_connection(con->number);
-    tcp_free_connection(con);
+  if (tcphdr->flags & TCP_HEADER_FLAG_ACK) {
+    /* ここで送信キューからパケット削除する */
+
+    /* データ送信に対してACKが返ってきたので,次のデータを送信する */
+    if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
+      con->seq_number = tcphdr->ack_number;
+      tcp_send_flush(con);
+    }
+
+    /* SYNSENT,SYNRECVならばESTABに */
+    if ((con->status == TCP_CONNECTION_STATUS_SYNSENT) ||
+	(con->status == TCP_CONNECTION_STATUS_SYNRECV)) {
+      con->seq_number = tcphdr->ack_number;
+
+      /* セッション確立を上位タスクに通知 */
+      buf = kz_kmalloc(sizeof(*buf));
+      buf->cmd = TCP_CMD_ESTAB;
+      buf->option.tcp.establish.number = con->number;
+      kz_send(con->id, 0, (char *)buf);
+
+      new_status = TCP_CONNECTION_STATUS_ESTAB;
+    }
+
+    if (con->status == TCP_CONNECTION_STATUS_FINWAIT1) {
+      con->seq_number = tcphdr->ack_number;
+      new_status = TCP_CONNECTION_STATUS_FINWAIT2;
+    }
+    if (con->status == TCP_CONNECTION_STATUS_LASTACK) {
+      con->seq_number = tcphdr->ack_number;
+      con->status = TCP_CONNECTION_STATUS_CLOSED;
+      closed++;
+    }
+  }
+
+  if (tcphdr->flags & TCP_HEADER_FLAG_SYN) {
+    /* LISTENならばSYN+ACKを返す */
+    if (con->status == TCP_CONNECTION_STATUS_LISTEN) {
+      con->snd_number = con->seq_number = 1;
+      con->dst_ipaddr = pkt->option.common.ipaddr.addr;
+      con->dst_port   = tcphdr->src_port;
+      con->ack_number = tcphdr->seq_number + 1;
+
+      tcp_makesendpkt(con, TCP_HEADER_FLAG_SYNACK, 1460, 1460, 1, 0, NULL);
+      new_status = TCP_CONNECTION_STATUS_SYNRECV;
+    }
+
+    /* SYNSENTならばACKを返す(たぶんSYN+ACKが来ている) */
+    if (con->status == TCP_CONNECTION_STATUS_SYNSENT) {
+      con->ack_number = tcphdr->seq_number + 1;
+      tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
+    }
+  }
+
+  if (tcphdr->flags & TCP_HEADER_FLAG_FIN) {
+    /* FINWAIT2なら,ACKを返してCLOSEDに遷移 */
+    if (con->status == TCP_CONNECTION_STATUS_FINWAIT2) {
+      con->ack_number = tcphdr->seq_number + 1;
+      tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
+      new_status = TCP_CONNECTION_STATUS_CLOSED;
+      closed++;
+    }
+
+    /* ESTABなら,ACK, FIN+ACK を返してLASTACKに遷移 */
+    if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
+      con->ack_number = tcphdr->seq_number + 1;
+      tcp_makesendpkt(con, TCP_HEADER_FLAG_ACK, 1460, 0, 0, 0, NULL);
+      /* new_status = TCP_CONNECTION_STATUS_CLOSEWAIT; */
+      tcp_makesendpkt(con,
+#if 0
+		      TCP_HEADER_FLAG_FIN,
+#else
+		      TCP_HEADER_FLAG_FINACK,
+#endif		  
+		      1460, 0, 0, 0, NULL);
+      new_status = TCP_CONNECTION_STATUS_LASTACK;
+    }
+  }
 
-    return 0;
+  if (tcphdr->flags & TCP_HEADER_FLAG_PSH) {
+    /* データを受信 */
+    if (con->status == TCP_CONNECTION_STATUS_ESTAB) {
+      pkt->next = NULL;
+      *(con->recv_queue_end) = pkt;
+      con->recv_queue_end = &(pkt->next);
+      tcp_recv_flush(con); /* データを上位タスクに通知してACKを返す */
+      ret = 1;
+    }
   }
 
+#if 0
   switch (con->status) {
   case TCP_CONNECTION_STATUS_LISTEN:
   case TCP_CONNECTION_STATUS_SYNSENT:
   case TCP_CONNECTION_STATUS_SYNRECV:
     return tcp_recv_open(pkt, con, tcphdr);
 
   case TCP_CONNECTION_STATUS_ESTAB:
     if (tcphdr->flags & TCP_HEADER_FLAG_FIN)
       if (tcp_recv_close(pkt, con, tcphdr))
 	return 1; /* conが削除されたので処理継続できないので返る.要修正 */
     return tcp_recv_data(pkt, con, tcphdr);
 
   case TCP_CONNECTION_STATUS_FINWAIT1:
   case TCP_CONNECTION_STATUS_FINWAIT2:
   case TCP_CONNECTION_STATUS_CLOSEWAIT:
   case TCP_CONNECTION_STATUS_LASTACK:
     return tcp_recv_close(pkt, con, tcphdr);
 
   case TCP_CONNECTION_STATUS_CLOSED:
   default:
     break;
   }
+#endif
 
-  return 0;
+  con->status = new_status;
+  if (closed) {
+    /* セッション終了を上位タスクに通知 */
+    buf = kz_kmalloc(sizeof(*buf));
+    buf->cmd = TCP_CMD_CLOSE;
+    buf->option.tcp.close.number = con->number;
+    kz_send(con->id, 0, (char *)buf);
+
+    con = tcp_delete_connection(con->number);
+    tcp_free_connection(con);
+  }
+
+  return ret;
 }
修正はこんなかんじ.いちおうシミュレータで動作確認した. webサーバがちゃんと動いているようだ.
メールは kozos(アットマーク)kozos.jp まで