截获所有以太网帧数据并进行具体分析
2012-07-11 08:47 Rollen Holt 阅读(3523) 评论(0) 编辑 收藏 举报/* capture_packet.c - 截获所有以太网帧数据并进行具体分析 */ /* 常用函数的头文件 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <unistd.h> #include <signal.h> /* 与网络相关的头文件 */ #include <netinet/ip_icmp.h> #include <net/if_arp.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netinet/ip.h> #include <netdb.h> #include <netinet/tcp.h> #include <netinet/udp.h> #include <signal.h> #include <net/if.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/if_ether.h> #include <net/ethernet.h> #include <linux/igmp.h> #include <netinet/tcp.h> /* 全局变量结构的结构体原型 - 包含要记录的任何全局信息 */ struct global_info { unsigned int bytes; /* 网卡接收的总字节数 */ unsigned int packet_num; /* 网卡接受的帧的总数量 */ unsigned int packet_arp; /* 接收到的arp包的数量 */ unsigned int packet_rarp; /* 接收到的rarp包的数量 */ unsigned int packet_ip; /* 接收到的ip包的数量 */ unsigned int packet_icmp; /* 接收到的icmp包的数量 */ unsigned int packet_igmp; /* 接收到的igmp包的数量 */ unsigned int packet_tcp; /* 接收到的tcp包的数量 */ unsigned int packet_udp; /* 接收到的udp包的数量 */ int print_flag_frame; /* 是否打印帧头信息标志, 1表示打印, 0表示不打印 */ int print_flag_arp; /* 是否打印arp头信息标志 */ int print_flag_ip; /* 是否打印ip头信息标志 */ int print_flag_rarp; /* 是否打印rarp头信息标志 */ int print_flag_tcp; /* 是否打印tcp头信息标志 */ int print_flag_udp; /* 是否打印udp头信息标志 */ int print_flag_icmp; /* 是否打印icmp头信息标志 */ int print_flag_igmp; /* 是否打印igmp头信息标志 */ }; /* 定义一个全局变量,用于存储全局信息 */ struct global_info global; struct ip_pair { unsigned int source_ip; unsigned int dest_ip; unsigned int count; }; /* 定义一个用于存储ip对的结构体数组 */ struct ip_pair ip_pair[10000]; /* 一个用于初始化全局信息的函数 */ void init_global( struct global_info * var ); /* 一个用于打印全局信息的函数 */ void print_global( struct global_info var ); /* 打印一个错误,并退出 */ void error_and_exit( char * msg, int exit_code ); /* 设置网卡成混杂模式 */ int set_card_promisc( char * interface_name, int sock ); /* 把mac地址转换一个字符串 */ void mac_to_str( char * buf, char * mac_buf ); /* 用于打印帮助信息 */ void help( void ); /* 截获网卡帧数据,并进行数据分用*/ void do_frame( int sockfd ); /* 处理ip层数据 */ void do_ip( char * data ); /* 打印ip头信息 */ void print_ip( struct iphdr * ); /* 处理arp层数据 */ void do_arp( char * data ); /* 打印arp头信息 */ void print_arp( struct arphdr * ); /* 处理rarp数据 */ void do_rarp( char * data ); /* 处理tcp层数据 */ void do_tcp( char * data ); /* 打印tcp层头信息 */ void print_tcp( struct tcphdr * ); /* 处理udp层数据 */ void do_udp( char * data ); /* 打印udp层头信息 */ void print_udp( struct udphdr * ); /* 处理icmp层数据 */ void do_icmp( char * data ); /* 打印icmp头信息 */ void print_icmp( struct icmphdr * ); /* 处理igmp层数据 */ void do_igmp( char * data ); /* 打印igmp头信息 */ void print_igmp( struct igmphdr * ); /* 初始化一个全局结构体 */ void init_global( struct global_info * var ) { var->bytes = 0; var->packet_num = 0; var->packet_arp = 0; var->packet_rarp = 0; var->packet_ip = 0; var->packet_icmp = 0; var->packet_igmp = 0; var->packet_tcp = 0; var->packet_udp = 0; var->print_flag_frame = 0; var->print_flag_arp = 0; var->print_flag_ip = 0; var->print_flag_rarp = 0; var->print_flag_tcp = 0; var->print_flag_udp = 0; var->print_flag_icmp = 0; var->print_flag_igmp = 0; } /* 一个用于打印全局信息的函数 */ void print_global( struct global_info var ) { printf("\n\n********** 全局信息 *****************\n\n"); printf("总共接收字节数: %d kbytes.\n", var.bytes / 1024 ); printf("总共接受包数量: %d\n\n", var.packet_num ); if( var.packet_arp ) printf("接收 arp 包数量: %d\n", var.packet_arp ); if( var.packet_rarp) printf("接收 rarp 包数量: %d\n", var.packet_rarp ); if( var.packet_ip ) printf("接收 ip 包数量: %d\n", var.packet_ip ); if( var.packet_icmp) printf("接收 icmp 包数量: %d\n", var.packet_icmp ); if( var.packet_igmp) printf("接收 igmp 包数量: %d\n", var.packet_igmp ); if( var.packet_tcp ) printf("接收 tcp 包数量: %d\n", var.packet_tcp ); if( var.packet_udp ) printf("接收 udp 包数量: %d\n", var.packet_udp ); printf("\n"); } /* 用于处理当下按ctrl-c时的处理函数 */ void sig_int( int sig ) { print_global( global ); int i; /* for( i=0; i<global.packet_ip; i++ ){ printf("%15s ==>> ", inet_ntoa( *(struct in_addr *)( &ip_pair[i].source_ip ) ) ); printf("%15s \n", inet_ntoa( *(struct in_addr *)( &ip_pair[i].dest_ip ) )); } */ exit( 0 ); } /* 打印错误信息,并退出 */ void error_and_exit( char * msg, int exit_code ) { herror( msg ); exit( exit_code ); } /* 设置网卡模式成混帐模式,这样的话可以截获以太网帧数据 */ int set_card_promisc( char * interface_name, int sock ) { /* 用于套接口ioctl的接口请求结构体 */ struct ifreq ifr; /* 复制网卡名称进入请求结构体的名称元素 */ strncpy(ifr.ifr_name, interface_name ,strlen( interface_name )+1); /* 通过ioctl获得相应信息 */ if((ioctl(sock, SIOCGIFFLAGS, &ifr) == -1)) { error_and_exit("ioctl", 2); } /* 设置网卡模式标志为混杂模式 */ ifr.ifr_flags |= IFF_PROMISC; /* 通过ioctl把参数传递给网卡 */ if(ioctl(sock, SIOCSIFFLAGS, &ifr) == -1 ) error_and_exit("ioctl", 3); } /* 把mac地址转换成字符串 */ void mac_to_str( char * buf, char * mac_buf ) { sprintf( mac_buf, "%02x:%02x:%02x:%02x:%02x:%02x",(unsigned char) *buf, (unsigned char)(*(buf+1)), (unsigned char)(*(buf+2)), (unsigned char)(*(buf+3)), (unsigned char)(*(buf+4)), (unsigned char)(*(buf+5))); mac_buf[17] = 0; } void help( void ) { printf("Usage: capture [-h] [协议名称 ...].\n"); printf("默认情况: 打印所有包信息.\n"); } void print_udp( struct udphdr * pudp ) { printf("==================== udp 头信息 ======================\n"); printf("16位源端口号 : %d\n", ntohs( pudp->source ) ); printf("16位目的端口号: %d\n", ntohs( pudp->dest ) ); printf("16位udp长度: %d\n", ntohs( pudp->len ) ); printf("16位udp校验和: %d\n", ntohs( pudp->check ) ); if( ntohs( pudp->len ) != sizeof(struct udphdr ) && ntohs( pudp->len ) < 20 ){ char * data = ( char * )pudp + sizeof( struct udphdr ); printf("UDP数据: %s\n", data ); } } void do_udp( char * data ) { global.packet_udp ++; struct udphdr * pudp = ( struct udphdr * )data; if( global.print_flag_udp ) print_udp( pudp ); } void print_tcp( struct tcphdr * ptcp ) { printf("==================== tcp 头信息 =====================\n"); printf("源端口号 : %d\n", ntohs( ptcp->source ) ); printf("目的端口号: %d\n", ntohs( ptcp->dest ) ); printf("32位序列号 : %u\n", ntohl( ptcp->seq ) ); printf("32位确认序号: %u\n", ntohl( ptcp->ack_seq ) ); printf("首部长度: %d\n", ptcp->doff * 4 ); printf("6个标志位: \n"); printf(" 紧急指针 urg : %d\n", ptcp->urg ); printf(" 确认序号位 ack : %d\n", ptcp->ack ); printf(" 接受方尽快将报文交给应用层 psh : %d\n", ptcp->psh ); printf(" 重建连接 rst : %d\n", ptcp->rst ); printf(" 用来发起连接的同步序号 syn : %d\n", ptcp->syn ); printf(" 发送端完成任务 fin : %d\n", ptcp->fin ); printf("16位窗口大小: %d\n", ntohs( ptcp->window ) ); printf("16位校验和: %d\n", ntohs( ptcp->check ) ); printf("16位紧急指针: %d\n", ntohs( ptcp->urg_ptr ) ); if( ptcp->doff * 4 == 20 ){ printf("选项数据: 没有\n"); } else { printf("选项数据: %d 字节\n", ptcp->doff * 4 - 20 ); } char * data = ( char * )ptcp; data += ptcp->doff * 4; printf("数据长度: %d 字节\n", strlen(data) ); if( strlen(data) < 10 )printf("数据: %s\n", data ); } void do_tcp( char * data ) { global.packet_tcp ++; struct tcphdr * ptcp; ptcp = ( struct tcphdr * )data; if( global.print_flag_tcp ) print_tcp( ptcp ); } void print_igmp( struct igmphdr * pigmp ) { printf("==================== igmp 包信息 ==========================\n"); printf("igmp 版本: %d\n", pigmp->type & 15 ); printf("igmp 类型: %d\n", pigmp->type >> 4 ); printf("igmp 码: %d\n", pigmp->code ); printf("igmp 校验和: %d\n", ntohs( pigmp->csum ) ); printf("igmp 组地址: %d\n", ntohl( pigmp->group ) ); } void do_igmp( char * data ) { global.packet_igmp ++; struct igmphdr * pigmp = ( struct igmphdr * ) data; if( global.print_flag_igmp ) print_igmp( pigmp ); } void print_icmp( struct icmphdr * picmp ) { printf("==================== icmp 包信息 ===========================\n"); printf("消息类型: %d ", picmp->type ); switch( picmp->type ){ case ICMP_ECHOREPLY: printf("Ping的回显应答\n"); break; case ICMP_DEST_UNREACH: printf("目的不可达\n"); break; case ICMP_SOURCE_QUENCH: printf("源端被关闭\n"); break; case ICMP_REDIRECT: printf("重定相\n"); break; case ICMP_ECHO: printf("ping的回显请求\n"); break; case ICMP_TIME_EXCEEDED: printf("超时\n"); break; case ICMP_PARAMETERPROB: printf("参数问题\n"); break; case ICMP_TIMESTAMP: printf("时间戳请求\n"); break; case ICMP_TIMESTAMPREPLY: printf("时间戳应答\n"); break; case ICMP_INFO_REQUEST: printf("信息请求\n"); break; case ICMP_INFO_REPLY: printf("信息应答\n"); break; case ICMP_ADDRESS: printf("地址掩码请求\n"); break; case ICMP_ADDRESSREPLY: printf("地址掩码应答\n"); break; default: printf("未知消息类型\n"); break; } printf("消息类型的子选项: %d ", picmp->code ); switch( picmp->type ){ case ICMP_ECHOREPLY: printf("Ping的回显应答\n"); break; case ICMP_DEST_UNREACH: switch( picmp->type ){ case ICMP_NET_UNREACH: printf("网络不可到达\n"); break; case ICMP_HOST_UNREACH: printf("主机不可到达\n"); break; case ICMP_PROT_UNREACH: printf("协议不可到达\n"); break; case ICMP_PORT_UNREACH: printf("端口不可到达\n"); break; case ICMP_FRAG_NEEDED: printf("需要进行分片,但是又设置不分片位\n"); break; case ICMP_SR_FAILED: printf("源站选路失败\n"); break; case ICMP_NET_UNKNOWN: printf("目的网络不认识\n"); break; case ICMP_HOST_UNKNOWN: printf("目的主机不认识\n"); break; case ICMP_HOST_ISOLATED: printf("源主机北隔离\n"); break; case ICMP_NET_ANO: printf("目的网络被强制禁止\n"); break; case ICMP_HOST_ANO: printf("目的主机被强制禁止\n"); break; case ICMP_NET_UNR_TOS: printf("由于服务类型TOS,网络不可到达\n"); break; case ICMP_HOST_UNR_TOS: printf("由于服务类型TOS,主机不可到达\n"); break; case ICMP_PKT_FILTERED: printf("由于过滤,通信被强制禁止\n"); break; case ICMP_PREC_VIOLATION: printf("主机越权\n"); break; case ICMP_PREC_CUTOFF: printf("优先权中止生效\n"); break; default: printf("未知代码\n"); break; } break; case ICMP_SOURCE_QUENCH: printf("源端被关闭\n"); break; case ICMP_REDIRECT: switch( picmp->type ){ case ICMP_REDIR_NET: printf("对网络重定向\n"); break; case ICMP_REDIR_HOST: printf("对主机重定向\n"); break; case ICMP_REDIR_NETTOS: printf("对服务类型和网络重定向\n"); break; case ICMP_REDIR_HOSTTOS: printf("对服务类型和主机重定向\n"); break; defalut: printf("未知代码\n"); break; } break; case ICMP_ECHO: printf("ping的回显请求\n"); break; case ICMP_TIME_EXCEEDED: switch( picmp->type ){ case ICMP_EXC_TTL: printf("在传输期间生存时间为0\n"); break; case ICMP_EXC_FRAGTIME: printf("在数据组装期间生存时间为0\n"); break; default: printf("未知代码\n"); break; } break; case ICMP_PARAMETERPROB: switch( picmp->type ){ case 0: printf("IP首部错误(包括各种差错)\n"); break; case 1: printf("缺少必须的选项\n"); break; default: printf("原因未知\n"); break; } break; case ICMP_TIMESTAMP: printf("时间戳请求\n"); break; case ICMP_TIMESTAMPREPLY: printf("时间戳应答\n"); break; case ICMP_INFO_REQUEST: printf("信息请求\n"); break; case ICMP_INFO_REPLY: printf("信息应答\n"); break; case ICMP_ADDRESS: printf("地址掩码请求\n"); break; case ICMP_ADDRESSREPLY: printf("地址掩码应答\n"); break; default: printf("未知消息类型\n"); break; } printf("校验和: %d\n", ntohs(picmp->checksum) ); } void do_icmp( char * data ) { global.packet_icmp ++; struct icmphdr * picmp = ( struct icmphdr * ) data; if( global.print_flag_icmp ) print_icmp( picmp ); } void print_ip( struct iphdr * iph ) { printf("=============== ip 头信息 ===============\n"); printf("IP 首部长度:%d\n", iph->ihl * 4 ); printf("IP 版本 :%d\n", iph->version ); printf("服务类型(tos): %d\n", iph->tos ); printf("总长度字节: %d\n", ntohs(iph->tot_len) ); printf("16位标识: %d\n", ntohs(iph->id) ); printf("frag off: %d\n", ntohs(iph->frag_off) ); printf("8位生存事件: %d\n", iph->ttl ); printf("8位协议: %d\n", iph->protocol ); printf("16位首部校验和: %d\n", ntohs(iph->check) ); printf("32位源IP地址 : %s\n", inet_ntoa( *(struct in_addr *)(&iph->saddr)) ); printf("32位目的IP地址: %s\n", inet_ntoa( *(struct in_addr *)(&iph->daddr)) ); } void ip_count( struct iphdr * iph ) { ip_pair[ global.packet_ip - 1 ].source_ip = iph->saddr; ip_pair[ global.packet_ip - 1 ].dest_ip = iph->daddr; } void do_ip( char * data ) { global.packet_ip ++; struct iphdr *pip; pip = ( struct iphdr * ) data; /* pip = point to ip layer */ if( global.print_flag_ip ) print_ip( pip ); ip_count( pip ); char * pdata = data + pip->ihl * 4; switch( pip->protocol ){ case IPPROTO_ICMP: do_icmp( pdata ); break; case IPPROTO_IGMP: do_igmp( pdata ); break; case IPPROTO_TCP: do_tcp( pdata ); break; case IPPROTO_UDP: do_udp( pdata ); break; default: printf("IP: 未知其上层协议.\n"); break; } } void print_arp( struct arphdr * parp ) { printf("硬件类型: %d ", ntohs(parp->ar_hrd) ); switch( ntohs( parp->ar_hrd ) ){ case ARPHRD_ETHER: printf("Ethernet 10/100Mbps.\n"); break; case ARPHRD_EETHER: printf("Experimental Ethernet.\n"); break; case ARPHRD_AX25: printf("AX.25 Level 2.\n"); break; case ARPHRD_PRONET: printf("PROnet token ring.\n"); break; case ARPHRD_IEEE802: printf("IEEE 802.2 Ethernet/TR/TB.\n"); break; case ARPHRD_APPLETLK: printf("APPLEtalk.\n"); break; case ARPHRD_ATM: printf("ATM.\n"); break; case ARPHRD_IEEE1394: printf("IEEE 1394 IPv4 .\n"); break; default: printf("Unknow.\n"); break; } printf("映射的协议地址类型: %d ", ntohs(parp->ar_pro) ); switch( ntohs(parp->ar_pro) ){ case ETHERTYPE_IP: printf("IP.\n"); break; default: printf("error.\n"); break; } printf("硬件地址长度: %d\n", parp->ar_hln ); printf("协议地址长度: %d\n", parp->ar_pln ); printf("操作码: %d ", ntohs(parp->ar_op) ); switch( ntohs(parp->ar_op) ){ case ARPOP_REQUEST: printf("ARP 请求.\n"); break; case ARPOP_REPLY: printf("ARP 应答.\n"); break; case ARPOP_RREQUEST: printf("RARP 请求.\n"); break; case ARPOP_RREPLY: printf("RARP 应答.\n"); break; case ARPOP_InREQUEST: printf("InARP 请求.\n"); break; case ARPOP_InREPLY: printf("InARP 应答.\n"); break; case ARPOP_NAK: printf("(ATM)ARP NAK.\n"); break; default: printf("arp 操作码错误.\n"); break; } char * addr = (char*)(parp + 1); char buf[18]; mac_to_str( addr, buf ); printf("发送端以太网地址: %s\n", buf ); printf("发送端IP地址: %s\n", inet_ntoa( *(struct in_addr *)(addr+6) )); mac_to_str( addr+10, buf ); printf("目的以太网地址: %s\n", buf ); printf("目的IP地址: %s\n", inet_ntoa( *(struct in_addr *)(addr+16) )); } void do_arp( char * data ) { global.packet_arp ++; struct arphdr * parp; parp = ( struct arphdr * ) data; if( global.print_flag_arp ) { printf("============= arp 头信息 ==============\n"); print_arp( parp ); } } void do_rarp( char * data ) { global.packet_rarp ++; struct arphdr * parp; parp = ( struct arphdr * ) data; if( global.print_flag_rarp ){ printf("============= rarp 头信息 =============\n"); print_arp( parp ); } } /* 打印以太网帧的包头信息 */ void print_frame( struct ether_header * peth ) { /* 定义一个数组,用于存储把mac地址转换成字符串后的字符串 */ char buf[ 18 ]; printf("\n================================== 第 %d 个包 =======================================\n\n", global.packet_num ); printf("==== 以太网帧信息 =====\n"); char * shost = peth->ether_shost; mac_to_str( shost, buf ); printf("源以太网地址: %s\n", buf ); char * dhost = peth->ether_dhost; mac_to_str( dhost, buf ); printf("目的以太网地址:%s\n", buf ); } /* 用于从网卡接受一帧数据,同时根据以太网协议字段传递数据给相应的上层协议处理 */ void do_frame( int sock ) { /* 用于存储一帧数据 */ char frame_buf[ 2000 ]; /* 清空帧数据缓冲区 */ bzero( frame_buf, sizeof(frame_buf) ); int len = sizeof( frame_buf ); /* 用于存储接受字节数 */ int recv_num; /* 用于存储发送方的地址信息 */ struct sockaddr_in addr; /* 从网卡接收一帧数据 */ recv_num = recvfrom( sock, (char *)frame_buf, sizeof( frame_buf ), 0, ( struct sockaddr * )&addr, &len ); /* 所接收的包的总数自加1 */ global.packet_num ++; /* 从网卡接收的字节总数 */ global.bytes += recv_num; /* 打印接收的包是第几个包 */ //printf("此帧数据长度: %d\n", recv_num ); /* 定义一个用于指向以太网帧的指针 (这里我们只考虑最常见的以太网帧的情况) */ struct ether_header * peth; /* 让以太网头指针指向从网卡接受到的帧的数据的开头 */ peth = (struct ether_header *)frame_buf; /* 传递以太网帧首地址给打印以太网帧信息的打印函数 */ if( global.print_flag_frame ) print_frame( peth ); /* 定义一个数据指针,用于指向以太网帧的数据部分 */ char * pdata; /* 让 pdata 指向以太网帧的数据部分 */ pdata = frame_buf + sizeof( struct ether_header ); /* 根据以太网帧的协议字段进行数据分用 - 也就是进行数据拆封,根据协议字段调用相应层的处理函数 */ switch( ntohs( peth->ether_type ) ){ case ETHERTYPE_PUP: break; case ETHERTYPE_IP: do_ip( pdata ); break; case ETHERTYPE_ARP: do_arp( pdata ); break; case ETHERTYPE_REVARP: do_rarp( pdata ); break; default: printf("Unkonw ethernet type %d %x.\n", ntohs(peth->ether_type), ntohs(peth->ether_type) ); break; } } /* 主函数, 处理命令行输入, 设置好全局变量, 并调用接受和处理帧的函数 */ int main( int argc, char ** argv ) { /* 用于存储套接口文件描述符 */ int sockfd; /* 初始化全局变量 */ init_global( &global ); if( argc == 1 ) { /* 表示打印所有包头信息 */ global.print_flag_frame = 1; global.print_flag_arp = 1; global.print_flag_ip = 1; global.print_flag_rarp = 1; global.print_flag_tcp = 1; global.print_flag_udp = 1; global.print_flag_icmp = 1; global.print_flag_igmp = 1; } else { /* 帮助 或者 通过指定协议名称只打印某层些协议 */ if( !strcasecmp( argv[1], "-h" ) ){ help(); exit( 0 ); } else { int i; for( i=1; i < argc; i++ ){ if( !strcasecmp( argv[i], "frame" ) ) global.print_flag_frame = 1; else if( !strcasecmp( argv[i], "arp" ) ) global.print_flag_arp = 1; else if( !strcasecmp( argv[i], "rarp" ) ) global.print_flag_rarp = 1; else if( !strcasecmp( argv[i], "ip" ) ) global.print_flag_ip = 1; else if( !strcasecmp( argv[i], "tcp" ) ) global.print_flag_tcp = 1; else if( !strcasecmp( argv[i], "udp" ) ) global.print_flag_udp = 1; else if( !strcasecmp( argv[i], "icmp" ) ) global.print_flag_icmp = 1; else if( !strcasecmp( argv[i], "igmp" ) ) global.print_flag_igmp = 1; } } } /* 通过协议族AF_PACKET类信SOCK_RAW, 类型SOCK_RAW创建一个用于可以接受网卡帧数据的套接口,同时返回套就口文件描述符 */ if( (sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) == -1 ) error_and_exit( "socket", 1 ); /* 如果发生错误,返回错误值, 并退出 */ /* 设定网卡eth0成混杂模式 */ set_card_promisc( "eth0", sockfd ); /* 设定信号处理函数, 下面是设置当我们按下ctrl-c时所调用的处理函数 */ signal( SIGINT, sig_int ); /* 无限循环接收以太网卡数据帧, 并进行数据分用,直到你按下ctrl-c */ while( 1 ){ do_frame( sockfd ); } return 0; }
参考资料:
http://www.cnblogs.com/rollenholt/articles/2585432.html
http://www.binarytides.com/blog/c-packet-sniffer-code-with-libpcap-and-linux-sockets-bsd/
http://www.binarytides.com/blog/packet-sniffer-code-in-c-using-linux-sockets-bsd-part-2/
http://www.binarytides.com/blog/packet-sniffer-code-in-c-using-linux-sockets-bsd/
==============================================================================
本博客已经废弃,不在维护。新博客地址:http://wenchao.ren
我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他
们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其
实我是一个程序员
==============================================================================