Home >Backend Development >PHP Tutorial >PHP实现流量复制tcpcopy ( php + python)
看了tcpcopy的源码,php 和 python 都可以操作raw socket,因此,用php 和 python 实现了tcpcopy,代码比较简单
tcpcopy关键点,理认上来说,只要得到tcp请求报文中数据部分,在ip层转发到测试服务器,即可实现流量复制。
因此,只需要维护tcp会话即可维护一个假的tcp连接,骗过测试服务器即可。
代码实现,主要以TCP状态机为基础,同时考虑抓包的无序问题,结合tcpdump调试,用php实现tcpcopy,用python实现intercept。
以下代码,经测试,客户机发起5000个http请求(大约50个长连接),流量全部会复制到测试机上。
其实,tcpcopy与lvs、nat、运营商流量劫持工作原理类似,都是通过欺骗tcp协议栈达到目的; 同理,通过在应用层处理原始套接字,也可以实现lvs负载均衡功能(后续可能会尝试)。
Code:
1. tcpcopy.php
两个进程工作,一个进程负责抓包并放入queue中; 另一个进程负责消费抓到的数据包,因此对tcpcopy进程效率要求不高。
<?php date_default_timezone_set('PRC'); ini_set('memory_limit','512M'); error_reporting(E_ALL); ini_set( 'display_errors', 'On' ); ini_set( "log_errors", "On" ); ini_set( "error_log", "/tmp/php_error.log" ); $local_ip = "192.168.56.101"; $src_ip = "192.168.56.101"; $dest_ip = "192.168.56.102"; $src_port = 50000+rand(1,10000); $src_port = 50000; $local_udp_port = 20000; $g_remote_test_ip = "192.168.56.102"; $dest_port = 8080; $seq_num = 1000000000+rand(1,1000000000); $seq_num = 1; $ack_num = 0; $g_fake_port_indx = 20000; class RawSocket{ public $s; private $dest_ip; private $dest_port; const FIN = 1; const SYN = 2; const ACK = 16; /* private $seq_num; private $ack_num */ public function create_listen_udp( $local_ip, $local_port ){ // var_dump( func_get_args( ) ); // $local_ip = "0.0.0.0"; // $local_port = 53; $s = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); // socket_set_nonblock( $s ); $bind_ret = socket_bind($s, $local_ip, $local_port ); if( $s === false || $bind_ret === false ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return false; } /* var_dump( socket_getsockname( $s , $addr, $port ) ); var_dump( $addr ); var_dump( $port ); */ $this->s = $s; return $this->s; } // listen local ip public function create_listen_socket( $local_ip, $remote_ip=null ){ $one = 1; $raw_socket = socket_create( AF_INET, SOCK_RAW, getprotobyname("tcp") ); // $raw_socket = socket_create( AF_INET, SOCK_RAW, getprotobyname("icmp") ); /* no need bind,connect, 不能抓不属于自己的包 */ // $raw_socket = socket_create( AF_INET, SOCK_RAW, 1 ); socket_set_nonblock( $raw_socket ); // trick, 3 is stand for header control $set_ret = socket_set_option( $raw_socket, getprotobyname("ip"), 3, $one); $conn_ret = $bind_ret = true; if( isset( $local_ip) ){ // 设置 dest ip $bind_ret = socket_bind($raw_socket, $local_ip ); } if( isset( $remote_ip) ){ // 不限制 source_ip // $conn_ret = socket_connect( $raw_socket, $remote_ip, 0 ); } // $bind_ret = socket_connect( $raw_socket, $remote_ip, 0 ); // $bind_ret = socket_bind($raw_socket, $local_ip ); if( $raw_socket === false || $set_ret === false || $conn_ret === false || $bind_ret === false ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return false; } $this->s = $raw_socket; return $this->s; } // send raw socket to ip public function create_send_socket( $remote_ip ){ $this->dest_ip = $remote_ip; $dest_ip_fake = "127.0.0.1"; $dest_ip_fake = "192.168.56.101"; $dest_port_fake = 8080; $one = 1; $raw_socket = socket_create( AF_INET, SOCK_RAW, getprotobyname("tcp") ); // $raw_socket = socket_create( AF_INET, SOCK_RAW, getprotobyname("ip") ); socket_set_nonblock( $raw_socket ); // trick, 3 is stand for header control $set_ret = socket_set_option( $raw_socket, getprotobyname("ip"), 3, $one); // trick, connect is needed anyway. $dest_ip must by right. $conn_ret = socket_connect( $raw_socket, $remote_ip, 0 ); // $conn_ret = socket_connect( $raw_socket, $dest_ip_fake, $dest_port ); // trick, for read // socket_bind($raw_socket, $src_ip ); if( $raw_socket === false || $set_ret === false || $conn_ret === false ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return false; } $this->s = $raw_socket; return $this->s; } public function create_socket( $src_ip, $src_port, $dest_ip=null, $dest_port=null ){ $this->dest_ip = $dest_ip; $this->dest_port = $dest_port; $dest_ip_fake = "127.0.0.1"; $dest_ip_fake = "192.168.56.101"; $dest_port_fake = 8080; $one = 1; $raw_socket = socket_create( AF_INET, SOCK_RAW, getprotobyname("tcp") ); socket_set_nonblock( $raw_socket ); // trick, 3 is stand for header control $set_ret = socket_set_option( $raw_socket, getprotobyname("ip"), 3, $one); // trick, connect is needed anyway. $dest_ip must by right. // $conn_ret = socket_connect( $raw_socket, $dest_ip, $dest_port ); // $conn_ret = socket_connect( $raw_socket, $dest_ip_fake, $dest_port ); // trick, for read socket_bind($raw_socket, $src_ip ); if( $raw_socket === false || $set_ret === false || $conn_ret === false ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return false; } $this->s = $raw_socket; return $this->s; } public function socket_read_raw( ){ $read_ret = socket_read( $this->s, 65535 ); if( $read_ret === false ){ $error = socket_last_error(); if( 11 !== $error ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return null; } return false; } if( strlen($read_ret) > 20 ){ // echo "read_data...\n"; return self::IPUnpack( $read_ret ); } return null; } public function socket_read_udp( ){ $read_ret = socket_recvfrom( $this->s, $buf, 65535, 0, $from='', $port=0 ); //echo $read_ret . "\n"; if( $read_ret === false ){ $error = socket_last_error(); if( 11 !== $error ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return null; } return false; } if( $read_ret > 20 ){ // echo "udp_read_data...\n"; return self::IPUnpack( $buf ); } return null; } /* public function socket_recv_raw( ){ $buf = ''; $read_ret = socket_recvfrom( $this->s, $buf, 65535 ); if( $read_ret === false ){ $error = socket_last_error(); if( 11 !== $error ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return null; } return false; } if( strlen($buf) > 20 ){ echo "read_data...\n"; return $this->IPUnpack( $buf ); } return null; } */ public function socket_send( $src_ip, $src_port, $dest_ip, $dest_port, $seq_num, $ack_num, $tcp_flag, $tcp_data,$timestamp, $ts_echo ){ $ip_data = self::TCPPacket( ip2long($src_ip), ip2long($dest_ip), $src_port, $dest_port, $seq_num, $ack_num, $tcp_flag, $tcp_data, $timestamp, $ts_echo ); // echo "tcp_data_md5:" . md5( $ip_data ) . "\n"; $ip_data = self::IPPacket("tcp", ip2long($src_ip), ip2long($dest_ip), $ip_data ); $write_ret = socket_write( $this->s, $ip_data ); if( $write_ret === false || $write_ret !== strlen($ip_data)){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; return false; } // echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; // echo "write_ret:" . json_encode( $write_ret ) . "\n"; return $write_ret; } /* IP header */ /* versionAndHeaderlen: 1Byte service: 1Byte totalLen: 2Bytes PacketID: 2Bytes SliceInfo: 2Bytes TTL: 1Byte type: 1Byte checksum: 2Bytes ==> just ip header, 16bits fan ma sum srcIP: 4Bytes destIP: 4Bytes */ public static function IPUnpack( $packet ){ $arr = unpack("Cverlen/x/ntotal_len/x4/Cttl/Ctype/ncheck_sum/Nsrc_ip/Ndest_ip/x*", $packet); $arr['version'] = $arr['verlen'] >> 4; $arr['header_len'] = ($arr['verlen'] & 0x0f) check_sum( $header )); return $header . $data; } /* TCP header */ /* srcPort: 2Bytes destPort: 2Bytes seqNum: 4Bytes ackNum: 4Bytes headerLenAndFlag: 2Byte ==> 4Bits(Len)+6Bits(reserved)+(U,ACK,PSH,RST,SYN,FIN) windowSize: 2Bytes checkSum: 2Bytes ==> tcp header + tcp data urgentPoint: 2Bytes */ /* * opt: kind(8bit)+len(8bit)+content */ public static function TCPUnpack( $packet ){ $arr = unpack("nsrc_port/ndest_port/Nseq_num/Nack_num/nhdrlen_flag/nwindow_size/ncheck_sum/nurgent/xtcp_data", $packet."*" ); $arr['header_len'] = ($arr['hdrlen_flag'] >> 12 ) >1) & 0x01) ? true : false; $arr['RST'] = (($flag>>2) & 0x01) ? true : false; $arr['ACK'] = (($flag>>4) & 0x01) ? true : false; $arr['tcp_data'] = strlen($packet) == $arr['header_len'] ? '': substr( $packet, $arr['header_len'] ); return $arr; } public static function TCPPacket( $src_ip, $dest_ip, $src_port, $dest_port, $seq_num, $ack_num, $flag, $tcp_data, $timestamp=null, $ts_echo=null ){ $window_size = 6000; $chk_sum = 0; $header_len = 20 >> 2; $header_option = ""; if( $timestamp !== null ){ $header_option = pack("CCNNn", 8, 10, $timestamp, $ts_echo, 0); $header_len = (20+12) >> 2; } $i = 2; while( $i -- ){ $tcp_header = pack("nn"."N"."N"."nn"."nn", $src_port, $dest_port, $seq_num, $ack_num, ($header_len > 16 ){ $chk_sum = ($chk_sum >> 16) + ($chk_sum & 0xffff); } $chk_sum = 0xffff & ~$chk_sum; if( true || $need_pack ){ $chk_sum = pack("n*", $chk_sum ); return $chk_sum; } return $chk_sum; } } $TCP_SYN = 1create_send_socket($dest_ip); /* $raw_socket_1->socket_send( $src_ip, $src_port, $dest_ip, $dest_port, $seq_num, $ack_num, $tcp_flag, $tcp_data, $timestamp, $ts_echo ); */ $raw_socket_2 = new RawSocket( ); $s2 = $raw_socket_2->create_listen_socket( /*local_ip*/ $local_ip, /*$dest_ip*/ null ); /* while( true ){ $read_ret = $raw_socket_2->socket_read_raw( ); if( $read_ret !== false && null !== $read_ret ){ echo json_encode( $read_ret ) . "\n"; } } */ $raw_socket_3= new RawSocket( ); $s3 = $raw_socket_3->create_listen_udp( /*local_ip*/ $local_ip, /*$dest_ip*/ $local_udp_port ); /* while( true ){ $read_ret = $raw_socket_3->socket_read_udp( ); if( $read_ret !== false && null !== $read_ret ){ echo json_encode( $read_ret ) . "\n"; } } */ $socket_map = array( (string)($s1) => array('type'=>'send_raw', 'obj'=>$raw_socket_1), (string)($s2) => array('type'=>'read_raw', 'obj'=>$raw_socket_2), (string)($s3) => array('type'=>'read_udp', 'obj'=>$raw_socket_3), ); // var_dump( $socket_map ); /* $raw_socket_4 = new RawSocket(); $raw_socket_4->create_listen_socket($local_ip); while( true ){ $read_ret = $raw_socket_4->socket_recv_raw( ); if( $read_ret !== false && null !== $read_ret ){ echo json_encode( $read_ret ) . "\n"; } } */ $g_real_map = array(); $g_fake_map = array(); $g_fake_request_info = array( 'real_ip'=>'', 'real_port'=>'', 'fake_ip'=>'', 'fake_port'=>'', 'send_data_len'=>0, 'receive_data_len'=>0, 'send_data_count'=>0, 'init_seq'=>0, 'next_seq'=>0, 'next_ack'=>0, 'need_deal'=>array(), 'need_deal_seq_min'=>0, 'need_deal_seq_next'=>0, 'update_time'=>0, 'state'=>'INIT', 'update_time'=>0, 'real_next_seq'=>0, // 与 next_seq 同步更新 'real_init_seq'=>0, 'test_init_seq'=>0, // 测试服务器开始序号 'latest_ack'=>0, // 测试服务器最后的确认 ); function add_need_deal_packet( & $fake_info, & $ip_packet, $real_time_request=true ){ global $g_cur_version; $ip_packet['cur_version'] = $g_cur_version; $ip_packet['enqueue_state'] = true; isset( $ip_packet['enqueue_count'] )? $ip_packet['enqueue_count']++ : $ip_packet['enqueue_count'] = 1; $fake_info['need_deal'][] = $ip_packet; if( count($fake_info['need_deal']) == 1 ){ $fake_info['need_deal_seq_min'] = $fake_info['need_deal_seq_max'] = $ip_packet['tcp']['seq_num']; $fake_info['need_deal_seq_next'] = $ip_packet['tcp']['seq_num']; }else if( $real_time_request && $fake_info['need_deal_seq_next'] != $ip_packet['tcp']['seq_num'] ){ echo "ip_packet:" . json_encode( $ip_packet ) . "\n"; $tmp_fake_info = $fake_info; $tmp_fake_info['need_deal_count'] = count( $tmp_fake_info['need_deal'] ); unset( $tmp_fake_info['need_deal'] ); echo "fake_info: " . json_encode( $tmp_fake_info ) . "\n"; echo ( "FATAL, may be lost packet\n" ); } if( $real_time_request ){ $fake_info['need_deal_seq_next'] = get_next_seq( $fake_info['need_deal_seq_next'], $ip_packet['tcp']['data_len'] ) ; } } $g_cur_version = 1; function get_offset( $cur, $old ){ $offset = $cur - $old; if( $offset (0xffffffff>>1) ){ $offset += (0xffffffff+1); } return $offset; } function get_next_seq( $cur, $inc ){ $next = $cur + $inc; if( $next > 0xffffffff ){ $next %= (0xffffffff+1); } return $next; } function is_before( $cur, $old ){ $diff = $cur - $old; if( $diff > 0 ) return true; if( $diff >1) ) return true; return false; } function is_after_or_equal( $cur, $old ){ $diff = $cur - $old; if( $diff >= 0 ) return true; if( $diff >1) ) return true; return false; } function is_before_or_equal( $cur, $old ){ return is_after_or_equal( $old, $cur ); } function is_after( $cur, $old ){ return is_before( $old, $cur ); } function is_real_after( $fake_info, $cur ){ $old = $fake_info['real_next_seq']; $diff = $cur - $old; if( $diff > 0 ) return true; //if( $diff (0xffffffff>>1) ) return true; if( $diff >1) ) return true; return false; } function is_real_before( $fake_info, $cur ){ return ! is_real_after($fake_info, $cur ); } function is_real_equal( $fake_info, $cur ){ return $fake_info['real_next_seq'] == $cur; } function update_next_seq( & $fake_info, $inc ){ $fake_info['next_seq'] = get_next_seq( $fake_info['next_seq'] , $inc ); $fake_info['real_next_seq'] = get_next_seq( $fake_info['real_next_seq'] , $inc ); } function update_next_ack( & $fake_info, $inc ){ $fake_info['next_ack'] = get_next_seq( $fake_info['next_ack'] , $inc ); } /* function get_fake_offset( $fake_info, $cur ){ return get_offset( $cur , $fake_info['next_seq'] ); } function get_real_offset( $fake_info, $cur ){ return get_offset( $cur , $fake_info['real_next_seq'] ); } */ function set_update_time( & $fake_info ){ $cur = intval( microtime( true ) * 1000 ); $fake_info['update_time'] = $cur; } function deal_delay_data( & $fake_info, $not_crontab = true ){ global $g_cur_version; if( $not_crontab ) { set_update_time( $fake_info ); } if( count($fake_info['need_deal']) = $latest_undeal_version ){ break; } break; if( 0 == count($fake_info['need_deal']) ){ break; } } echo "need_deal_END\n"; } function crontab_task( ){ global $g_fake_map, $g_real_map; // echo "crontab_task..............\n"; $now = intval( microtime(true)*1000 ); if( count( $g_real_map ) == 0 ){ // echo "g_real_map is emtpy\n"; }else{ // echo "g_real_map count:" . count( $g_real_map ) . "\n"; } foreach( $g_real_map as & $fake_info ){ /* echo "crontab_task................................\n"; $update_time = $fake_info['update_time']; echo "now[ $now ], update_time[ $update_time ]\n"; echo sprintf("now_update_time_diff:%d\n", $now - $update_time); */ $diff = $now - $fake_info['update_time']; // echo "diff: " . $diff . "\n"; if( count($fake_info['need_deal']) > 0 ){ //&& ($now - $fake_info['update_time'] > -1) ){ deal_delay_data( $fake_info, false ); } // echo $fake_info['state'] . "\n"; if( $fake_info['state'] == 'TIME_WAIT' && $diff > 1000*3 ){ $real_ip_port_key = $fake_info['real_ip'] .":" . $fake_info['real_port']; $fake_ip_port_key = $fake_info['fake_ip'] .":" . $fake_info['fake_port']; unset( $g_real_map[ $real_ip_port_key ] ); unset( $g_fake_map[ $fake_ip_port_key ] ); return; }else if( $diff > 1000*3 ){ // var_dump( $fake_info ); // die( 0 ); } } } function receive_test_server_request( $ip_packet ){ global $g_real_map, $g_fake_map, $g_real_request_info, $g_fake_request_info; global $g_send_socket; global $g_remote_test_ip; global $g_cur_version; $cur_time = microtime( true ); // echo "receive_test_server_response.....................test_server\n"; $TCP_SYN = 1socket_send( $fake_info['fake_ip'], $fake_info['fake_port'], $src_ip, $src_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_ACK, '', null, null); deal_delay_data( $fake_info ); return; }else if( $status == 'ESTABLISHED' ){ echo "WARNING: receive dumplicated syn from test server.\n"; deal_delay_data( $fake_info ); return; } /* log2( sprintf("line[%d],%s", __LINE__ , "WARNING: syn from test server, status not match:\nfake_info:" . json_encode( $fake_info ) . ";\nip_packet:" . json_encode($ip_packet)) ); packet_debug( $ip_packet ); */ return; } if( is_after_or_equal( $ack_num, $fake_info['latest_ack'] ) ){ $fake_info['latest_ack'] = $ack_num; } // $fake_info['latest_ack'] = $ack_num; //服务端返回数据. if( $flag_ack && ! $flag_fin && ! $flag_rst && $tcp_data_len > 0 ){ // && is_before_or_equal( $ack_num, $fake_info['next_seq'] ) // 过时的ack // && is_before_or_equal( $seq_num, $fake_info['next_ack'] ) ){ // 过时的seq if( $seq_num == $fake_info['next_ack'] ){ update_next_ack( $fake_info, $tcp_data_len ); } $g_send_socket->socket_send( $fake_info['fake_ip'], $fake_info['fake_port'], $src_ip, $src_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_ACK, '', null, null); deal_delay_data( $fake_info ); return; } // 测试服务器返回数据包无序, 返回数据包的ack_num可以比fake_info的seq小,因为fake_client 可以在没有收到ack的情况下继续发数据包. if( $seq_num != $fake_info['next_ack'] ){ // echo ( sprintf("line[%d],%s", __LINE__ , "WARNING: receive unsorted packet from test server :\nfake_info:" . json_encode( $fake_info ) // . ";\nip_packet:" . json_encode($ip_packet)) ); deal_delay_data( $fake_info ); return; } // 收到FIN if( $flag_fin ){ // fake client 被动关闭 if( $fake_info['state'] == 'ESTABLISHED' ){ $fake_info['state'] = 'CLOSE_WAIT'; //$fake_info['next_ack'] += (1 + $tcp_data_len); update_next_ack( $fake_info, 1+$tcp_data_len ); $fake_info['state'] = 'LAST_ACK'; $g_send_socket->socket_send( $fake_info['fake_ip'], $fake_info['fake_port'], $src_ip, $src_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_FIN | $TCP_ACK, '', null, null); //$fake_info['next_seq']++; update_next_seq( $fake_info, 1 ); return; // fake client 主动关闭 } else if( $fake_info['state'] == 'TIME_WAIT' ) { echo ("WARNING: receive FIN from test server, fake_info state is TIME_WAIT, " . json_encode( $fake_info ) . ";\nip_packet:" . json_encode($ip_packet) ); return; // 同时关闭 }else if( $fake_info['state'] == 'FIN_WAIT_1'){ // $fake_info['next_ack'] += 1 + $tcp_data_len; update_next_ack( $fake_info, 1+$tcp_data_len ); $g_send_socket->socket_send( $fake_info['fake_ip'], $fake_info['fake_port'], $src_ip, $src_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_ACK, '', null, null); $fake_info['state'] = 'CLOSING'; return; // 主动关闭对方ACK 先于FIN到达 }else if( $fake_info['state'] == 'FIN_WAIT_2'){ //$fake_info['next_ack'] += 1 + $tcp_data_len; update_next_ack( $fake_info, 1+$tcp_data_len ); $g_send_socket->socket_send( $fake_info['fake_ip'], $fake_info['fake_port'], $src_ip, $src_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_ACK, '', null, null); $fake_info['state'] = 'TIME_WAIT'; return; } log2( "WARNING: fin from test server, status not match, fake_info:" . json_encode( $fake_info ) ); packet_debug( $ip_packet ); return; } if( $flag_rst ){ $msg = "recv RST from test server"; log2( $msg ); return; } if( $flag_ack && ! $flag_fin && ! $flag_rst && in_array($status, array('FIN_WAIT_1', 'LAST_ACK', 'CLOSING')) && $tcp_data_len == 0 ){ // 主动关闭,收到FIN的ack. if( $status == 'FIN_WAIT_1' ){ // echo "receive FIN_WAIT_1 ACK !!!!!!!!!!!!!!!!!!!!!!!!\n"; $fake_info['state'] = 'FIN_WAIT_2'; return; } // 被动关闭,收到FIN的ack. if( $status == 'LAST_ACK' ){ $fake_info['state'] = 'CLOSED'; return; } // 同时关闭. if( $status == 'CLOSING' ){ $fake_info['state'] = 'TIME_WAIT'; return; } } if( $flag_ack && ! $flag_fin && ! $flag_rst && in_array($status , array( 'ESTABLISHED', 'TIME_WAIT', 'FIN_WAIT_2') ) && $tcp_data_len == 0 ){ if( $ack_num 0 ){ echo "Notice: " . $type . "\n"; $deal_real_time_request = false; $deal_retry = true; } echo sprintf("DEBUG: %d : %d\n", $deal_real_time_request, isset($ip_packet['cur_version']) ? $ip_packet['cur_version'] : -1); $src_ip = $ip_packet['src_ip']; $src_port = $ip_packet['tcp']['src_port']; $dest_ip = $ip_packet['dest_ip']; $dest_port = $ip_packet['tcp']['dest_port']; $tcp_hdr = $ip_packet['tcp']; $flag_syn = $tcp_hdr['SYN']; $flag_ack = $tcp_hdr['ACK']; $flag_fin = $tcp_hdr['FIN']; $flag_rst = $tcp_hdr['RST']; $seq_num = $tcp_hdr['seq_num']; $ack_num = $tcp_hdr['ack_num']; $tcp_data = $tcp_hdr['tcp_data']; $tcp_data_len = strlen( $tcp_data ); $flag_str = ''; if( $flag_ack ){ $flag_str .= "ACK"; } if( $flag_syn ){ $flag_str .= "SYN"; } if( $flag_fin ){ $flag_str .= "FIN"; } if( $flag_rst ){ $flag_str .= "RST"; } $ip_port_key = $src_ip . ":" . $src_port; $cur = intval( microtime(true)*1000 ); // 建立连接 if( $flag_syn === true && $flag_ack === false ){ if( isset($g_real_map[ $ip_port_key ]) ){ $fake_info = & $g_real_map[ $ip_port_key ]; if( $fake_info['real_init_seq'] != $seq_num ){ $fake_ip_port_key = $fake_info['fake_ip'] .":" . $fake_info['fake_port']; unset( $g_real_map[ $ip_port_key ] ); unset( $g_fake_map[ $fake_ip_port_key ] ); }else{ return; } } echo "receive REAL_SYN ,ip_packet:" . json_encode($ip_packet) . "\n"; //$fake_ip = $src_ip; $fake_port = $src_port; //$fake_ip = "11.11.11.11"; $fake_ip = "193.168.56.121"; $g_fake_port_indx ++; if( $g_fake_port_indx > 60000 ){ $g_fake_port_indx = 20000; } $fake_port = $g_fake_port_indx; $real_ip_port_key = $src_ip .":" . $src_port; $fake_ip_port_key = $fake_ip .":" . $fake_port; $g_fake_map[ $fake_ip_port_key ] = $g_fake_request_info; $g_real_map[ $real_ip_port_key ] = & $g_fake_map[ $fake_ip_port_key ]; $fake_info = & $g_fake_map[ $fake_ip_port_key ]; $fake_info['real_ip'] = $src_ip; $fake_info['real_port'] = $src_port; $fake_info['fake_ip'] = $fake_ip; $fake_info['fake_port'] = $fake_port; $fake_info['real_next_seq'] = $seq_num + 1; $fake_info['relative_seq'] = $fake_info['next_seq'] = $fake_info['init_seq'] = 0xffffffff; $fake_info['relative_seq'] = $fake_info['next_seq'] = $fake_info['init_seq'] = $seq_num; $fake_info['real_relative_seq'] = $fake_info['real_next_seq'] = $fake_info['real_init_seq'] = $seq_num; $fake_info['state'] = 'INIT'; $fake_info['update_time'] = $cur; // 发送syn数据包,建立连接. /* RawSocket::socket_send( $src_ip, $src_port, $dest_ip, $dest_port, $seq_num, $ack_num, $tcp_flag, $tcp_data,$timestamp, $ts_echo ); */ $g_send_socket->socket_send( $fake_ip, $fake_port, $g_remote_test_ip, $dest_port, $fake_info['next_seq'], 0, $TCP_SYN, '', null, null); $fake_info['state'] = 'SYN_SEND'; /* $fake_info['next_seq'] += 1; $fake_info['real_next_seq'] += 1; */ update_next_seq( $fake_info, 1 ); echo "### receive real syn ################## SEND SYN\n"; /* var_dump( $fake_info ); var_dump( $g_remote_test_ip ); die( 0 ); */ return; } $real_ip_port_key = $src_ip .":" . $src_port; if( ! isset( $g_real_map[ $real_ip_port_key ] ) ){ echo "WARNING: real_ip_port_key, no fake_info, " . ", ip_packet:" . json_encode($ip_packet); return; } $fake_info = & $g_real_map[ $real_ip_port_key ]; $fake_ip = $fake_info['fake_ip']; $fake_port = $fake_info['fake_port']; $real_offset =get_offset( $seq_num, $fake_info['real_next_seq'] ); if( $tcp_data_len > 0 || true ){ echo sprintf("DEBUG: type[$type], real_offset, data_len, flags, [ %d ][ %d ][ %s ]\n", $real_offset, $tcp_data_len, $flag_str); } // fake client 处理较慢 //if( is_real_after($fake_info, $seq_num) && $tcp_data_len > 0 ){ if( $real_offset > 0 && $tcp_data_len > 0 ){ echo sprintf("NOTICE: test is slow, real_offset[%d], tcp_data_len[%d]\n", $real_offset, $tcp_data_len); echo sprintf("line[%d], %s", __LINE__ , "ADD NEED DEAL DATA\n"); /* $ip_packet['cur_version'] = $g_cur_version; $fake_info['need_deal'][] = $ip_packet; */ add_need_deal_packet( $fake_info, $ip_packet, $deal_real_time_request ); if( $deal_real_time_request ){ echo "will deal_delay_data\n"; deal_delay_data( $fake_info ); } return; } if( $real_offset 0 ){ echo "FATAL: fin packet with tcp_data_len > 0, ip_packet: " . json_encode( $ip_packet ) . "\n"; die( 255 ); } if( $fake_info['state'] == 'ESTABLISHED' && $real_offset == 0 && is_after_or_equal($fake_info['latest_ack'], $fake_info['next_seq']) && $cur - $fake_info['update_time'] > 30 * 1000 // 30s ){ $g_send_socket->socket_send( $fake_ip, $fake_port, $g_remote_test_ip, $dest_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_FIN | $TCP_ACK, '', null, null); $fake_info['state'] = 'FIN_WAIT_1'; // 更新next_seq, real_next_seq update_next_seq( $fake_info, 1 ); echo "### receive real fin ################## SEND FIN\n"; return; }else{ // return; /* $tmp = $fake_info; $tmp['need_deal'] = count( $tmp['need_deal'] ); echo sprintf( "line[%d],%s", __LINE__ , "WARNING: receive FIN from real client, fake info state is not ESTABLISHED, fake_info:" . json_encode( $tmp ) . ", ip_packet:" . json_encode($ip_packet) ); */ add_need_deal_packet( $fake_info, $ip_packet, $deal_real_time_request ); if( $deal_real_time_request ){ echo "will deal_delay_data\n"; deal_delay_data( $fake_info ); } return; } return; } if( $flag_rst ){ $msg = "WARNING, recv RST from real client"; log2( $msg ); return; } if( $flag_ack && $tcp_data_len > 0 ){ //if( $fake_info['state'] == 'ESTABLISHED' && is_after_or_equal($fake_info['latest_ack'], $fake_info['next_seq']) ){ if( $fake_info['state'] == 'ESTABLISHED' ){ echo "### receive real data ################## SEND DATA\n"; $g_send_socket->socket_send( $fake_ip, $fake_port, $g_remote_test_ip, $dest_port, $fake_info['next_seq'], $fake_info['next_ack'], $TCP_ACK | $TCP_PSH, $tcp_data, null, null); $fake_info['send_data_len'] += $tcp_data_len; $fake_info['send_data_count'] ++; // 更新next_seq, real_next_seq update_next_seq( $fake_info, $tcp_data_len ); if( isset($ip_packet['cur_version']) ){ echo "NOTICE: deal backup data success\n"; } }else{ echo sprintf("line[%d], %s", __LINE__ , "ADD NEED DEAL DATA\n"); add_need_deal_packet( $fake_info, $ip_packet, $deal_real_time_request ); } if( $deal_real_time_request ){ echo "will deal_delay_data 2\n"; deal_delay_data( $fake_info ); } return; } if( $flag_ack && ! $flag_fin && ! $flag_rst && 0 == $tcp_data_len ){ echo "### receive real ack #################### IGNORE ACK\n"; return; } /* log2( sprintf("line[%d],%s", __LINE__ , "WARNING: no default, receive packet from client, status not match, fake_info:" . json_encode( $fake_info ) ) . ", ip_packet:" . json_encode($ip_packet) ); packet_debug( $ip_packet ); echo "\n"; */ $tmp = $fake_info; $tmp['need_deal'] = count( $tmp['need_deal'] ); echo sprintf( "WARNING, need die, line[%d],%s", __LINE__ , ", fake_info:" . json_encode( $tmp ) . ", ip_packet:" . json_encode($ip_packet) . "\n" ); return; return; } function log2( $msg ){ echo $msg . "\n"; echo "back_trace: " . json_encode( debug_backtrace() ) . "\n"; } function packet_debug( $ip_packet ){ $src_ip = $ip_packet['src_ip']; $src_port = $ip_packet['tcp']['src_port']; $dest_ip = $ip_packet['dest_ip']; $dest_port = $ip_packet['tcp']['dest_port']; $tcp_hdr = $ip_packet['tcp']; $flag_syn = $tcp_hdr['SYN']; $flag_ack = $tcp_hdr['ACK']; $flag_fin = $tcp_hdr['FIN']; $flag_rst = $tcp_hdr['RST']; $seq_num = $tcp_hdr['seq_num']; $ack_num = $tcp_hdr['ack_num']; $tcp_data = $tcp_hdr['tcp_data']; $ip_port_key = $src_ip . ":" . $src_port; $cur = time(); echo sprintf("read_raw_tcp: %s:%s --> %s:%s\n", $src_ip, $src_port, $dest_ip, $dest_port ); echo sprintf("\t: seq_num[%d], ack_num[%d], SYN:%d, ACK:%d, FIN:%d, RST:%d\n", $tcp_hdr['seq_num'], $tcp_hdr['ack_num'], $tcp_hdr['SYN'], $tcp_hdr['ACK'], $tcp_hdr['FIN'], $tcp_hdr['RST']); } $select_read = array( $s2, $s3 ); // $select_read = array( $s3 ); $mq_key = ftok( dirname(__FILE__), 'a'); $mq = msg_get_queue($mq_key, 0666); msg_remove_queue( $mq ); $mq = msg_get_queue($mq_key, 0666); $pid = pcntl_fork(); if( $pid == 0 ){ // tcpcopy进程,从mq里拿数据包,发送伪造请求报文 或 回复test server报文。 while( true ){ $ret = msg_receive($mq, 0, $message_type=1, 10240, $m, true, MSG_IPC_NOWAIT); if( $ret == false ){ crontab_task( ); continue; } $ip_packet = json_decode( $m, true ); switch( $ip_packet['from'] ){ case 'read_raw': receive_real_request( $ip_packet ); break; case 'send_raw': // echo "need send_raw\n"; break; case 'read_udp': // receive_test_server_packet receive_test_server_request( $ip_packet ); break; default: echo "NOTICE:error\n"; break; } } }else{ // 抓包进程,单纯从socket接收IP报文,防止进程过慢而丢包. $need_send = array(); while( true ){ $need_read = $select_read; $select_ret = socket_select( $need_read, $need_write = null, $expect, 1, 0 ); if( false === $select_ret ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; continue; } if( $select_ret 0 ){ foreach( $need_read as $s ){ $type = $socket_map[ (string)$s ]['type']; $obj = $socket_map[ (string)$s ]['obj']; switch( $type ){ case 'read_raw': // echo "need_read_raw\n"; $read_ret = $obj->socket_read_raw( ); if( $read_ret === false || null === $read_ret ){ continue; // echo json_encode( $read_ret ) . "\n\n"; }else{ // echo json_encode( $read_ret ) . "\n\n"; } // receive_real_packet // receive_real_request( $read_ret ); $ip_packet = & $read_ret; $ip_packet['from'] = 'read_raw'; // $ret = msg_send($mq, 1, json_encode( $ip_packet), true, false, $msg_err ); $need_send[] = $ip_packet; break; case 'send_raw': // echo "need send_raw\n"; break; case 'read_udp': // echo "need read udp --------------------- read udp packet\n"; $read_ret = $obj->socket_read_udp( ); if( $read_ret === false || null === $read_ret ){ continue; }else{ // echo json_encode( $read_ret ) . "\n\n"; } // receive_test_server_packet // receive_test_server_request( $read_ret ); $ip_packet = & $read_ret; $ip_packet['from'] = 'read_udp'; // $ret = msg_send($mq, 1, json_encode( $ip_packet), true, false, $msg_err ); $need_send[] = $ip_packet; break; default: echo "NOTICE:error\n"; break; } if( false && $ret == false ){ $mq_stat = msg_stat_queue( $mq ); echo "NOTICE:" . $mq_stat['msg_qnum'] . "\n"; echo "FATAL: msg_send_ret false\n"; var_dump( $msg_err ); //die( "FATAL: msg_send_error" ); $need_send[] = $ip_packet; } }//foreach }//if $mq_stat = msg_stat_queue( $mq ); if( $mq_stat['msg_qnum'] > 100 ){ continue; } if( count($need_send) > 0 ){ foreach( $need_send as $k => $ip_packet ){ $ret = msg_send($mq, 1, json_encode( $ip_packet), true, false, $msg_err ); if( $ret ==true ){ unset( $need_send[$k] ); $mq_stat = msg_stat_queue( $mq ); if( $mq_stat['msg_qnum'] > 100 ){ break; } }else{ break; } } } }//while die( 0 ); } while( true ){ $need_read = $select_read; $select_ret = socket_select( $need_read, $need_write = null, $expect, 1, 0 ); if( false === $select_ret ){ echo "socket_last_error_str:" . socket_strerror(socket_last_error()) . "\n"; continue; } if( $select_ret 0 ){ foreach( $need_read as $s ){ $type = $socket_map[ (string)$s ]['type']; $obj = $socket_map[ (string)$s ]['obj']; switch( $type ){ case 'read_raw': // echo "need_read_raw\n"; $read_ret = $obj->socket_read_raw( ); if( $read_ret === false || null === $read_ret ){ continue; // echo json_encode( $read_ret ) . "\n\n"; }else{ // echo json_encode( $read_ret ) . "\n\n"; } // receive_real_packet receive_real_request( $read_ret ); break; case 'send_raw': // echo "need send_raw\n"; break; case 'read_udp': // echo "need read udp --------------------- read udp packet\n"; $read_ret = $obj->socket_read_udp( ); if( $read_ret === false || null === $read_ret ){ continue; }else{ // echo json_encode( $read_ret ) . "\n\n"; } // receive_test_server_packet receive_test_server_request( $read_ret ); break; default: echo "NOTICE:error\n"; break; } } } } die( 0 ); function get_asc( $str ){ echo "get_asc:\n"; $arr = str_split( $str,1 ); foreach( $arr as $v ){ echo ord( $v ) . "," . bin2hex($v) . "\n"; } echo "\n"; } function str2int( $str, $size=4 ){ $ret = 0; for( $i=0; $i <p> </p> <p> </p> <p> </p> <p>2. intercept.py</p> <p>抓到测试服务器返回的数据,将其发送到tcpcopy进程。</p> <pre class="brush:php;toolbar:false">#!/usr/bin/python import socket import struct import binascii remote_addr = ("192.168.56.101", 20000) address=('localhost',20000) udp_s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #udp_s.bind( address ) #print udp_s.getsockname() s=socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(0x0800)) while True: pkt = s.recvfrom(65532) #print pkt[1] ethernetHeader=pkt[0][0:14] eth_hdr = struct.unpack("!6s6s2s",ethernetHeader) #print eth_hdr <span style="white-space:pre"> </span> eth_hdr2 = struct.unpack("!6s6sH",ethernetHeader) if( eth_hdr2[2] != 0x0800 ): continue print 'IP:' print len(pkt[0]) ipHeader = pkt[0][14:34] ip_hdr = struct.unpack("!12s4s4s",ipHeader) #ip_header_len = 4* (ord(ip_hdr[0][0]) & 0xf) ip_header_len = (struct.unpack("!1B11x", ip_hdr[0])[0] & 0x0f) > 12) print tcp_hdr_len ip_tcp_header = pkt[0][14:14+ip_header_len+tcp_hdr_len] print len(ip_tcp_header) print "real_len:%d, ip:%d, tcp:%d" % ((ip_header_len + tcp_hdr_len), ip_header_len, tcp_hdr_len ) print remote_addr ret = udp_s.sendto( ip_tcp_header, remote_addr ) print ret print "Source IP address:"+socket.inet_ntoa(ip_hdr[1]) print "Destination IP address:"+socket.inet_ntoa(ip_hdr[2]) # tcpHeader = pkt[0][34:54] # tcp_hdr = struct.unpack("!HH16s",tcpHeader)
3. 环境设置:
1). 环境介绍:
101: centos4, online server.
104: centos5, fake router.
102: centos4-1, test server.
103: centos4-2, real client.
2). 102设置路由:
route add -host 193.168.56.121 gw 192.168.56.104
4. 注意:
104 上禁用 ICMP redirects。
原本想用PHP写raw socket玩玩的,原来看过tcpcopy代码,用php写了个试试,没想还跑通了...