Linuxネットワークプログラミングのtcpdumpパケット分析TCP三次握手過程

7049 ワード

TCPプロトコルを用いてネットワーク通信を行う場合、通信の両端にまず接続リンクを確立する必要があるが、これはUDP通信を用いて「接続リンク」を必要としないことを意味するものではない.ここでいう接続リンクとは、通信プロトコルの範疇の東を指し、物理媒体や電磁波信号ではないので、TCPは接続向けのネットワーク通信プロトコルである.主に、双方が通信する際に、受信したパケットのシリアル番号、次回受信するパケットのシリアル番号、相手のスライドウィンドウ情報など、接続に関する情報を保持していることを意味します.
OK、雑談は少なくて、私達はテーマに入って、以下1つの簡単なTCPサービス端とクライアントコードを結びつけて、tcpdumpコマンドを借りてTCPが接続を創立する時の3回の握手の過程(Three-way handshake process)を分析します.
サービス・エンド・コードは次のとおりです.
/**
 * server.c
 *
 * TCP server program, it is a simple example only.
 *
 * Writen By: Zhou Jianchun
 * Date: 2011.08.12
 *
 * Compiled With: gcc -o client client.c
 * Tested On: Ubuntu 11.04 LTS
 *
 * gcc version: 4.5.2
 *
 */

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <time.h>
#include <strings.h>
#include <string.h>

#define SERVER_PORT 20000
#define LENGTH_OF_LISTEN_QUEUE 10
#define BUFFER_SIZE 255
#define WELCOME_MESSAGE "welcome to our server."

int main(int argc, char **argv)
{
	int server_fd, client_fd;
	struct sockaddr_in server_addr, client_addr;

	if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		printf("create socket error, exit!
"); exit(1); } bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = htons(INADDR_ANY); if(bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { printf("bind to port %d failed, exit!
", SERVER_PORT); exit(1); } if(listen(server_fd, LENGTH_OF_LISTEN_QUEUE) < 0) { printf("failed to listen, exit!
"); exit(1); } while(1) { char buf[BUFFER_SIZE]; long timestamp; socklen_t length = sizeof(client_addr); client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &length); if(client_fd <0) { printf("call accept error, break from while loop!
"); break; } strcpy(buf, WELCOME_MESSAGE); printf("connect from client: IP: %s, Port: %d
", (char *)inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); timestamp = time(NULL); strcat(buf, "timestamp on server:"); strcat(buf, ctime(×tamp)); send(client_fd, buf, BUFFER_SIZE, 0); close(client_fd); close(server_fd); return 0; } }

クライアントコード:
/**
 * client.c
 *
 * TCP client program, it is a simple example only.
 *
 * Writen By: Zhou Jianchun
 * Date: 2011.08.12
 *
 * Compiled With: gcc -o client client.c
 * Tested On: Ubuntu 11.04 LTS
 *
 * gcc version: 4.5.2
 *
 */

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

#define SERVER_PORT 20000
#define CLIENT_PORT ((20001 + rand()) % 65536)
#define BUFFER_SIZE 255
#define REQUEST_MESSAGE "welcome to connect the server.
" void usage(char *name) { printf("usage: %s IP
", name); } int main(int argc, char **argv) { int server_fd, client_fd, length = 0; struct sockaddr_in server_addr, client_addr; socklen_t socklen = sizeof(server_addr); char buf[BUFFER_SIZE]; if(argc < 2) { usage(argv[0]); exit(1); } if((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("create socket error, exit!
"); exit(1); } srand(time(NULL)); bzero(&client_addr, sizeof(client_addr)); client_addr.sin_family = AF_INET; //client_addr.sin_port = htons(CLIENT_PORT); client_addr.sin_port = htons(40000); client_addr.sin_addr.s_addr = htons(INADDR_ANY); bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; inet_aton(argv[1], &server_addr.sin_addr); server_addr.sin_port = htons(SERVER_PORT); /*if(bind(client_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) { printf("bind to port %d failed, exit!
", CLIENT_PORT); exit(1); }*/ if(connect(client_fd, (struct sockaddr*)&server_addr, socklen) < 0) { printf("can not connect to %s, exit!
", argv[1]); exit(1); } /*length = recv(client_fd, buf, BUFFER_SIZE, 0); if(length < 0) { printf("recieve data from %s error, exit!
", argv[1]); exit(1); } */ char *tmp = buf; while((length = read(client_fd, tmp, BUFFER_SIZE)) > 0) { tmp += length; } printf("frome server %s:
\t%s", argv[1], buf); close(client_fd); return 0; }

コードロジックは非常に簡単で、サービス側のプログラムが起動した後に20000ポートで傍受して、外部の接続を待って、クライアントが起動した後に接続して、サービス側は一連の文字列情報をクライアントに送信して、それから退出して、クライアントは情報を読み取った後にも退出します.
プログラムを実行する前に、別の端末で次のコマンドを入力します.
tcpdump 'port 20000' -i lo -S
両端のプログラムが終了すると、このコマンドは次の情報を出力します.
17:05:35.358403 IP neptune.local.49493 > neptune.local.20000: Flags [S], seq 1317094743, win 32792, options [mss 16396,sackOK,TS val 7083694 ecr 0,nop,wscale 6], length 0
17:05:35.358439 IP neptune.local.20000 > neptune.local.49493: Flags [S.], seq 1311370954, ack 1317094744, win 32768, options [mss 16396,sackOK,TS val 7083694 ecr 7083694,nop,wscale 6], length 0
17:05:35.358468 IP neptune.local.49493 > neptune.local.20000: Flags [.], ack 1311370955, win 513, options [nop,nop,TS val 7083694 ecr 7083694], length 0
17:05:35.358871 IP neptune.local.20000 > neptune.local.49493: Flags [P.], seq 1311370955:1311371210, ack 1317094744, win 512, options [nop,nop,TS val 7083694 ecr 7083694], length 255
17:05:35.358890 IP neptune.local.49493 > neptune.local.20000: Flags [.], ack 1311371210, win 530, options [nop,nop,TS val 7083694 ecr 7083694], length 0
17:05:35.358913 IP neptune.local.20000 > neptune.local.49493: Flags [F.], seq 1311371210, ack 1317094744, win 512, options [nop,nop,TS val 7083694 ecr 7083694], length 0
17:05:35.359419 IP neptune.local.49493 > neptune.local.20000: Flags [F.], seq 1317094744, ack 1311371211, win 530, options [nop,nop,TS val 7083694 ecr 7083694], length 0
17:05:35.359441 IP neptune.local.20000 > neptune.local.49493: Flags [.], ack 1317094745, win 512, options [nop,nop,TS val 7083694 ecr 7083694], length 0

次のように分析します.
1.クライアントは49493ポートを介してサービス側の20000ポートにSYN同期要求パケットを送信し、最初の握手を展開し、Flags[S]テーブルが求めるパケットのタイプはSYN、すなわち同期要求パケットであり、seqフィールドはパケットシーケンス番号を識別する.
2.サービス側はACK確認パケットを送信し、一方でSYN要求パケットを付加し、クライアントの同期要求を確認しながらクライアントに同期要求を送信する.ここでFlags[S.]のポイント番号は確認パケット(ACK)であり、SはSYN要求パケットであることを示す.TCPはデュプレクス通信プロトコルであり、接続確立後に双方が同時にデータを送受信できるため、双方ともSYNパケット要求同期を送信する.
3.クライアントがACKパケット確認サービス側のSYN同期要求を送信すると、このときFlagsには小数点が1つしかなく、このパケットが確認のために使用されていることを示す.
これで3回の握手プロセスが終了し,双方ともACKパケットを受け取った場合,いずれもESTABLISHED状態に入り,この時点でデータ送信が可能であることを示した.
4.サービス側はクライアントに1つのパケットを送信して、パケットの中の内容は1つの文字列で、この時のFlags標識の中にアルファベットPが見えて、PUSH DATAを意味して、データを送信する意味です.
これでTCPの3回の握手過程の分析は終わり、本人のレベルが限られているため、ブログの中の不適切さや間違いは避けられない.読者の批判と指摘を切に望んでいる.同时に読者を歓迎して共に関连する内容を探求して、もし喜んで交流するならばあなたの贵重な意见を残して、ありがとうございます.