/* * tcp_record.c * libpcap を使った TCP パケット解析サンプルコード * ・コネクションごとに別ファイルにダンプする。 * ・起動後に SYN を検出したものだけ記録する。 * ・Ctrl-C で終了。 */ #define PROG_NAME "tcp_record" #include "mystab_pcap.h" #include "apr_getopt.h" #include "apr_network_io.h" /* デバッグフラグ D0 全パケットの表示等 */ #define D0 0 /* デバッグフラグ D1 全状態遷移の表示 */ #define D1 0 /* デバッグフラグ D2 オープンクローズの表示 */ #define D2 1 /* デバッグフラグ DD その他 */ #define DD 0 /* コマンドラインオプション定義 */ static const apr_getopt_option_t opt_option[] = { /* long-option, short-option, has-arg flag, description */ { "port_num", 'p', TRUE, "[MUST] port_number" }, { "interface", 'i', TRUE, "[MUST] network device to use" }, { "log_dir", 'l', TRUE, "[MUST] log directory to record" }, { "hex_dump", 'H', FALSE, "[OPTION] dumps tcp-payload in hexadecimal."}, { "help", 'h', FALSE, "[OPTION] this help" }, /* -h or --help */ #if defined(WIN32) || defined(WINDOWS) || defined(MSVC) { "disp_ifs", 'D', FALSE, "[OPTION] displays avalable interfaces (Win Only)" }, #endif { NULL, 0, 0, NULL }, /* SENTINEL */ }; /* カスタマイズ可能マクロ変数定義:ここから */ /* 同時に扱えるコネクション数 * 詳細:コネクションレコードテーブルのサイズのデフォルト値 (65536未満) */ #define CONREC_TAB_SIZE 100 /* 応答待ちバッファ (ACK 待ちの TCP ペイロードを格納するバッファ)のサイズ * このサイズを超えたパケットが発生した場合は、記録を強制終了する */ #define TCP_PAYLOAD_BUFFER_SIZE (1500*5) /* コネクションレコードの保持のタイムアウト秒 * 最後にパケットの発生した時間から、この秒数を経過した記録は * 強制的に終了する */ #define TIME_OUT_SEC 600 /* カスタマイズ可能マクロ変数定義:ここまで */ /* コネクションレコードテーブル */ typedef struct _st_my_conrec { /* ステータス(0で未使用) */ apr_byte_t stat; /* コネクションID */ apr_uint32_t id; /* コネクション名 XXX.XXX.XXX.XXX:XXXXX-XXX.XXX.XXX.XXX:XXXXX */ char name[43]; /* クライアントIPアドレス */ char c_ipaddr[16]; /* クライアントポート番号 */ apr_uint16_t c_port; /* サーバIPアドレス */ char s_ipaddr[16]; /* サーバポート番号 */ apr_uint16_t s_port; /* 開始日時 */ apr_time_t start_time; /* パケットの発生した最終日時 */ time_t last_time; /* メモリプール */ apr_pool_t *pool; /* ログファイルのファイルポインタ */ apr_file_t *fp; /* クライアントシーケンス番号 */ apr_uint32_t c_seq_no; /* サーバシーケンス番号 */ apr_uint32_t s_seq_no; /* サーバ応答待ちのクライアントデータのサイズ */ apr_uint16_t c_data_size; /* クライアント応答待ちのサーバデータのサイズ */ apr_uint16_t s_data_size; /* サーバ応答待ちのクライアントデータ */ apr_byte_t c_data[TCP_PAYLOAD_BUFFER_SIZE]; /* クライアント応答待ちのサーバデータ */ apr_byte_t s_data[TCP_PAYLOAD_BUFFER_SIZE]; } my_conrec; /* コネクションテーブルのステータス */ #define CLOSED 1 #define SYN1 2 #define SYN2 3 #define ESTAB 4 #define C_FIN1 5 #define C_FIN2 6 #define S_FIN1 7 #define S_FIN2 8 #define UNKNOWN 99 static apr_uint16_t count_active_conrec_data = 0; static apr_uint16_t count_id = 0; my_conrec conrec_tab[CONREC_TAB_SIZE]; /* 監視対象ポート */ static apr_uint16_t target_port = 0; /* ローカルアドレス文字列 */ static const char *local_addr = NULL; /* ログ保存ディレクトリ */ static const char *log_dir = NULL; /* 観測者情報 */ static char *watcher_str = NULL; /* TCP ペイロードの16進ダンプフラグ */ static int tcp_payload_hex_dump_flag = 0; #define STAT_NORMAL 0 #define STAT_TIME_OUT 1 #define STAT_ABORTED 2 #define STAT_OVERFLOW 3 void my_conrec_close ( my_conrec * cr_ptr , int status ) { if (cr_ptr->fp) { apr_status_t rv = APR_SUCCESS; char kbuf[32]; apr_file_printf(cr_ptr->fp, "0\r\n"); rv = apr_rfc822_date(kbuf, apr_time_now()); if (rv == APR_SUCCESS) { apr_file_printf(cr_ptr->fp, "End-Date: %s\r\n", kbuf); } switch (status) { case STAT_NORMAL: /* FIN パケット検出による正常終了 */ apr_file_printf(cr_ptr->fp, "Status: %d NORMAL\r\n", status); break; case STAT_TIME_OUT: /* タイムアウトによる強制終了 */ apr_file_printf(cr_ptr->fp, "Status: %d TIME_OUT\r\n", status); break; case STAT_ABORTED: /* プロセス終了による強制終了 */ apr_file_printf(cr_ptr->fp, "Status: %d ABORTED\r\n", status); break; case STAT_OVERFLOW: /* 応答待ちバッファあふれによる強制終了 */ apr_file_printf(cr_ptr->fp, "Status: %d OVERFLOW\r\n", status); break; } apr_file_printf(cr_ptr->fp, "\r\n"); apr_file_close(cr_ptr->fp); cr_ptr->fp = NULL; } if (cr_ptr->pool) { apr_pool_destroy(cr_ptr->pool); cr_ptr->pool = NULL; } memset(cr_ptr, 0, sizeof(my_conrec)); count_active_conrec_data--; } /* * 返り値: * エラー時は NULL */ my_conrec *my_conrec_get ( const char *c_ipaddr , apr_uint16_t c_port , const char *s_ipaddr , apr_uint16_t s_port ) { my_conrec *cr_ptr = NULL; int i=0; for (i=0; istat) { continue; } if (time(NULL) - cr_ptr->last_time >= TIME_OUT_SEC) { if(DD)puts("#Time UP!."); my_conrec_close(cr_ptr, STAT_TIME_OUT); continue; } if (!strcmp(c_ipaddr, cr_ptr->c_ipaddr) && c_port==cr_ptr->c_port && !strcmp(s_ipaddr, cr_ptr->s_ipaddr) && s_port==cr_ptr->s_port) { if(0)puts("Found!"); return cr_ptr; } } return NULL; } my_conrec * my_conrec_new ( const char *c_ipaddr , apr_uint16_t c_port , const char *s_ipaddr , apr_uint16_t s_port ) { my_conrec *cr_ptr = NULL; int i=0; for (i=0; istat > 0) { if (time(NULL) - cr_ptr->last_time < TIME_OUT_SEC) { continue; } else { my_conrec_close(cr_ptr, STAT_TIME_OUT); } } cr_ptr->stat = CLOSED; strcpy (cr_ptr->c_ipaddr, c_ipaddr); cr_ptr->c_port = c_port; strcpy (cr_ptr->s_ipaddr, s_ipaddr); cr_ptr->s_port = s_port; cr_ptr->start_time = apr_time_now(); cr_ptr->last_time = time(NULL); snprintf(cr_ptr->name, 43, "%s:%d-%s:%d", c_ipaddr, c_port, s_ipaddr, s_port); cr_ptr->id = count_id++; count_active_conrec_data++; return cr_ptr; } return NULL; } void my_conrec_open ( my_conrec * cr_ptr ) { apr_status_t rv = APR_SUCCESS; rv = apr_pool_create(& cr_ptr->pool, NULL); if (rv != APR_SUCCESS) { return; } { apr_time_exp_t tm; char *out_filename = NULL; char tbuf[16]; /* YYYYMMDD-hhmmss */ int retsize = 0; apr_time_exp_lt(&tm, cr_ptr->start_time); apr_strftime(tbuf, &retsize, 16, "%Y%m%d-%H%M%S", &tm); out_filename = apr_psprintf(cr_ptr->pool, "%s/%s-%05u-%s-%u-%s-%u.txt", log_dir, tbuf, cr_ptr->id, cr_ptr->c_ipaddr, cr_ptr->c_port, cr_ptr->s_ipaddr, cr_ptr->s_port); if(DD)printf("#%s\n", out_filename); rv = apr_file_open(& cr_ptr->fp, out_filename, APR_FOPEN_WRITE|APR_FOPEN_CREATE|APR_FOPEN_TRUNCATE|APR_FOPEN_BINARY, APR_OS_DEFAULT, cr_ptr->pool); if (rv != APR_SUCCESS) { if(DD)printf("#ERROR: %s (%u)\n", out_filename, cr_ptr->fp); return; } apr_file_printf(cr_ptr->fp, "Watcher: %s\r\n", watcher_str); apr_file_printf(cr_ptr->fp, "Session-Id: %s-%05u\r\n", tbuf, cr_ptr->id); apr_file_printf(cr_ptr->fp, "Client: %s:%u\r\n", cr_ptr->c_ipaddr, cr_ptr->c_port); apr_file_printf(cr_ptr->fp, "Server: %s:%u\r\n", cr_ptr->s_ipaddr, cr_ptr->s_port); { char kbuf[32]; rv = apr_rfc822_date(kbuf, cr_ptr->start_time); if (rv == APR_SUCCESS) { apr_file_printf(cr_ptr->fp, "Date: %s\r\n", kbuf); } apr_file_printf(cr_ptr->fp, "\r\n"); } } } void my_conrec_record( my_conrec *cr_ptr , apr_byte_t direction_flag , my_tcp_pkt *d ) { int i=0; if (! cr_ptr->fp) { return; } apr_file_printf(cr_ptr->fp, "#%c,seq_no=%u,ack_no=%u,[", (direction_flag)?'C':'S', d->tcp_seq_no, d->tcp_ack_no ); if (d->tcp_urg_bit) { apr_file_printf(cr_ptr->fp, "urg:"); } if (0) if (d->tcp_ack_bit) { /* ACK ビットは必ず 1 になる */ apr_file_printf(cr_ptr->fp, "ack:"); } if (d->tcp_psh_bit) { apr_file_printf(cr_ptr->fp, "psh:"); } if (d->tcp_rst_bit) { apr_file_printf(cr_ptr->fp, "rst:"); } if (d->tcp_syn_bit) { apr_file_printf(cr_ptr->fp, "syn:"); } if (d->tcp_fin_bit) { apr_file_printf(cr_ptr->fp, "fin:"); } apr_file_printf(cr_ptr->fp, "],size=%u", d->tcp_payload_size); apr_file_printf(cr_ptr->fp, "\r\n"); if (tcp_payload_hex_dump_flag && d->tcp_payload_size) { for (i=0; itcp_payload_size; i++) { if (!i || i%32 == 0) { apr_file_printf(cr_ptr->fp, "# "); } apr_file_printf(cr_ptr->fp, "%02X", d->tcp_payload_pos[i]); if (i && i%32 == 31) { apr_file_printf(cr_ptr->fp, "\r\n"); } } if (i%32 != 0) { /* */ apr_file_printf(cr_ptr->fp, "\r\n"); } } } void my_conrec_set( my_conrec *cr_ptr , apr_byte_t direction_flag , my_tcp_pkt *d ) { if (direction_flag) { if (cr_ptr->c_data_size == 0 && d->tcp_seq_no > cr_ptr->c_seq_no) { cr_ptr->c_seq_no = d->tcp_seq_no; } if (d->tcp_seq_no + d->tcp_payload_size <= cr_ptr->c_seq_no + cr_ptr->c_data_size) { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_set (1) already set!\r\n"); } else if (cr_ptr->c_seq_no + cr_ptr->c_data_size == d->tcp_seq_no) { if (cr_ptr->c_data_size + d->tcp_payload_size < TCP_PAYLOAD_BUFFER_SIZE) { memcpy(cr_ptr->c_data + cr_ptr->c_data_size, d->tcp_payload_pos, d->tcp_payload_size); cr_ptr->c_data_size += d->tcp_payload_size; } else { apr_file_printf(cr_ptr->fp, "#ERROR: cr_ptr->c_data overflow!\r\n"); my_conrec_close(cr_ptr, STAT_OVERFLOW); } } else { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_set (2)\r\n"); } } else { if (cr_ptr->s_data_size == 0 && d->tcp_seq_no > cr_ptr->s_seq_no) { cr_ptr->s_seq_no = d->tcp_seq_no; } if (d->tcp_seq_no + d->tcp_payload_size <= cr_ptr->s_seq_no + cr_ptr->s_data_size) { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_set (3) already set!\r\n"); } else if (cr_ptr->s_seq_no + cr_ptr->s_data_size == d->tcp_seq_no) { if (cr_ptr->s_data_size + d->tcp_payload_size < TCP_PAYLOAD_BUFFER_SIZE) { memcpy(cr_ptr->s_data + cr_ptr->s_data_size, d->tcp_payload_pos, d->tcp_payload_size); cr_ptr->s_data_size += d->tcp_payload_size; } else { apr_file_printf(cr_ptr->fp, "#ERROR: cr_ptr->s_data overflow!\r\n"); my_conrec_close(cr_ptr, STAT_OVERFLOW); } } else { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_set (4)\r\n"); } } } void my_conrec_ack( my_conrec *cr_ptr , apr_byte_t direction_flag , my_tcp_pkt *d ) { if (direction_flag) { if (d->tcp_ack_no == cr_ptr->s_seq_no + cr_ptr->s_data_size) { if (cr_ptr->s_data_size > 0 && cr_ptr->fp) { apr_size_t len = cr_ptr->s_data_size; apr_file_printf(cr_ptr->fp, "S%u\r\n", cr_ptr->s_data_size); apr_file_write(cr_ptr->fp, cr_ptr->s_data, &len); apr_file_printf(cr_ptr->fp, "\r\n"); cr_ptr->s_seq_no = d->tcp_ack_no; cr_ptr->s_data_size = 0; } } else { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_ack (1): (s_seq_no=%u)+(s_data_size=%u)!=(ack_no=%u)\r\n", cr_ptr->s_seq_no, cr_ptr->s_data_size, d->tcp_ack_no); } } else { if (d->tcp_ack_no == cr_ptr->c_seq_no + cr_ptr->c_data_size) { if (cr_ptr->c_data_size > 0 && cr_ptr->fp) { apr_size_t len = cr_ptr->c_data_size; apr_file_printf(cr_ptr->fp, "C%u\r\n", cr_ptr->c_data_size); apr_file_write(cr_ptr->fp, cr_ptr->c_data, &len); apr_file_printf(cr_ptr->fp, "\r\n"); cr_ptr->c_seq_no = d->tcp_ack_no; cr_ptr->c_data_size = 0; } } else { apr_file_printf(cr_ptr->fp, "#WARNING: my_conrec_ack (2): (c_seq_no=%u)+(c_data_size=%u)!=(ack_no=%u)\r\n", cr_ptr->c_seq_no, cr_ptr->c_data_size, d->tcp_ack_no); } } } /* パケット(ディレクションとコードビット)による状態遷移 */ void my_tcp_trans (apr_byte_t direction_flag, my_tcp_pkt *d) { my_conrec *cr_ptr = NULL; char *client_ip = NULL; apr_uint16_t client_port = 0; char *server_ip = NULL; apr_uint16_t server_port = 0; if (direction_flag) { client_ip = d->ip_src_addr; client_port = d->tcp_src_port; server_ip = d->ip_dst_addr; server_port = d->tcp_dst_port; } else { client_ip = d->ip_dst_addr; client_port = d->tcp_dst_port; server_ip = d->ip_src_addr; server_port = d->tcp_src_port; } if (direction_flag && d->tcp_syn_bit && !d->tcp_ack_bit) { cr_ptr = my_conrec_new(client_ip, client_port, server_ip, server_port); if (!cr_ptr) { if(D2)puts("what's up ?! (1)"); if(D2)printf("active=%d\n", count_active_conrec_data); return; } cr_ptr->stat = SYN1; cr_ptr->c_seq_no = d->tcp_seq_no; return; } cr_ptr = my_conrec_get(client_ip, client_port, server_ip, server_port); if (!cr_ptr) { return; } if (cr_ptr->stat == SYN1) { if (!direction_flag && d->tcp_syn_bit) { cr_ptr->stat = SYN2; cr_ptr->s_seq_no = d->tcp_seq_no; } return; } if (cr_ptr->stat == SYN2) { if (direction_flag) { cr_ptr->stat = ESTAB; cr_ptr->c_seq_no = d->tcp_seq_no; cr_ptr->s_seq_no = d->tcp_ack_no; my_conrec_open(cr_ptr); if(D2)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); if(D2)printf("OPENED. active=%d\n", count_active_conrec_data); return; } } my_conrec_record(cr_ptr, direction_flag, d); my_conrec_ack(cr_ptr, direction_flag, d); if (d->tcp_payload_size != 0) { if (d->tcp_rst_bit) { /* 再送パケットの処理がここに入る… */ } my_conrec_set(cr_ptr, direction_flag, d); } if (cr_ptr->stat == ESTAB) { if (direction_flag && d->tcp_fin_bit) { cr_ptr->stat = C_FIN1; // cr_ptr->c_seq_no++; // cr_ptr->s_seq_no; if(0)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); if(0)printf("C_FIN1.\n"); return; } if (!direction_flag && d->tcp_fin_bit) { cr_ptr->stat = S_FIN1; cr_ptr->c_seq_no++; /* このあたり、やや手探り… */ cr_ptr->s_seq_no++; if(0)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); if(0)printf("S_FIN1.\n"); return; } } if (cr_ptr->stat == C_FIN1) { if (!direction_flag) { if (cr_ptr->s_data_size == 0) { if(D2)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); my_conrec_close(cr_ptr, STAT_NORMAL); if(D2)printf("C_FIN1-ACK-CLOSED. active=%d\n", count_active_conrec_data); } else { cr_ptr->stat = C_FIN2; } return; } } if (cr_ptr->stat == C_FIN2) { if (cr_ptr->s_data_size == 0) { if(D2)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); my_conrec_close(cr_ptr, STAT_NORMAL); if(D2)printf("C_FIN1-ACK-CLOSED. active=%d\n", count_active_conrec_data); } } if (cr_ptr->stat == S_FIN1) { if (direction_flag) { if(D2)printf("[%s],%c,", cr_ptr->name, direction_flag?'C':'S'); my_conrec_close(cr_ptr, STAT_NORMAL); if(D2)printf("S_FIN1-ACK-CLOSED. active=%d\n", count_active_conrec_data); return; } } } /* * 指定されたホストネームのIPアドレスの文字列を返す関数 * hostname に "" を指定した場合、動作するホストのIPアドレスが返る。 */ char * get_host_addr(const char * hostname, apr_pool_t *pool) { apr_status_t rv = APR_SUCCESS; apr_sockaddr_t *sa = NULL; char *ipaddr = NULL; rv = apr_sockaddr_info_get (&sa, hostname, APR_UNSPEC, 0, 0, pool); if (rv != APR_SUCCESS) { return NULL; } rv = apr_sockaddr_ip_get (&ipaddr, sa); if (rv != APR_SUCCESS) { return NULL; } return ipaddr; } void print_usage (apr_file_t *out) { apr_getopt_option_t *ptr = (apr_getopt_option_t *)opt_option; apr_file_printf(out, "Usage: %s ", PROG_NAME); while (ptr && ptr->name) { if (ptr->has_arg) { apr_file_printf(out, "[ -%c | --%s ] ", ptr->optch, ptr->name); } else { apr_file_printf(out, "[ -%c | --%s ] ", ptr->optch, ptr->name); } ptr++; } apr_file_printf(out, "\n"); ptr = (apr_getopt_option_t *)opt_option; while (ptr && ptr->name) { apr_file_printf(out, "\t-%c, --%s\t: %s\n", ptr->optch, ptr->name, ptr->description); ptr++; } } /* * コマンドライン引数の処理 */ int my_pcap_init ( int ac , char **av , my_pcap_params *params , apr_file_t *astderr , apr_pool_t *pool ) { apr_status_t rv = APR_SUCCESS; apr_getopt_t *opt = NULL; int opt_ch = 0; const char *opt_arg = NULL; const char * interface_str = NULL; if (ac < 2) { print_usage(astderr); return 0; } rv = apr_getopt_init(&opt, pool, ac, (const char * const *)av); if (rv != APR_SUCCESS) { apr_file_printf(astderr, "ERROR: apr_getopt_init\n"); return 0; } while ((rv = apr_getopt_long(opt, opt_option, &opt_ch, &opt_arg)) == APR_SUCCESS) { switch (opt_ch) { case 'p': target_port = atoi(opt_arg); break; case 'i': interface_str = opt_arg; break; case 'l': log_dir = opt_arg; break; case 'H': tcp_payload_hex_dump_flag = 1; break; #if defined(WIN32) || defined(WINDOWS) || defined(MSVC) case 'D': { char errbuf[80]; if (! disp_devices(astderr, errbuf, sizeof(errbuf))) { apr_file_printf(astderr, "ERROR: disp_devices: %s\n", errbuf); } } return 0; break; #endif case 'h': print_usage(astderr); return 0; } } if (rv != APR_EOF || target_port<=0 || !interface_str || !log_dir) { print_usage(astderr); return 0; } /* libpcap 用パラメータのセット */ params->device = interface_str; params->filter = apr_psprintf(pool, "tcp port %d", target_port); watcher_str = get_host_addr("", pool); memset(conrec_tab, 0, sizeof(conrec_tab)); return 1; } void my_pcap_main( apr_pool_t *pool , apr_file_t *out , my_tcp_pkt *d ) { apr_byte_t direction_flag = 0; if (!d || !pool || !out) { return; } if (d->tcp_dst_port == target_port) { direction_flag = 1; } my_tcp_trans(direction_flag, d); } /* * 終期化処理 * コネクションテーブルの強制クローズ */ void my_pcap_finally ( apr_file_t *out ) { int i=0; my_conrec *cr_ptr = NULL; for (i=0; istat) { my_conrec_close(cr_ptr, STAT_ABORTED); } } apr_file_printf(out, "done.\n"); }