Linux服务器编程|打开我的收藏夹 --TCP篇

Linux服务器编程|打开我的收藏夹 --TCP篇
文章图片


文章目录

    • TCP/IP 类
      • [\[看雪2018峰会回顾\] TCP的厄运,网络协议侧信道分析及利用](https://bbs.pediy.com/thread-245982.htm)
    • 解决CLOSE_WAIT、TIME_WAIT连接状态过多正确姿势
      • 正确的tcp优化姿势
    • TCP/IP常见攻击
    • 粘包
      • C
      • S

TCP/IP 类 [看雪2018峰会回顾] TCP的厄运,网络协议侧信道分析及利用
解决CLOSE_WAIT、TIME_WAIT连接状态过多正确姿势 像这种啊,你现在问我我肯定是不会背的,不过让我抽出手来拿几个项目练练,嘿嘿,就OK了。
先来看下一台生产环境中的各种tcp状态的连接数:
netstat -n| awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

然后我们来分析下占比比较高的几种状态产生的原因和解决方法:
  • CLOSE_WAIT
    咳咳,这就,很奇葩啊,是你的用户量巨大吗?不然就是服务器出问题了。如部分情况下不会执行socket的close方法,解决方法是查程序。
  • TIME_WAIT
    time_wait是一个需要特别注意的状态,他本身是一个正常的状态,只在主动断开那方出现,每次tcp主动断开都会有这个状态的,维持这个状态的时间是2个msl周期(2分钟),设计这个状态的目的是为了防止我发了ack包对方没有收到可以重发。那如何解决出现大量的time_wait连接呢?千万不要把tcp_tw_recycle改成1,正确的姿势应该是降低msl周期,也就是tcp_fin_timeout值,同时增加time_wait的队列,防止满了。
【Linux服务器编程|打开我的收藏夹 --TCP篇】为什么不能把tcp_tw_recycle改成1?
改成1的后果是会导致一个路由器后面用户有人能连你服务器有人telnet不通。
原因是tcp_tw_recycle改成1后会启用tcp的tcp_timestamps功能,这个功能简单说就是所有的通信包是时间戳递增的,如果收到了同一个IP下时间戳小的包那就说明是个老数据包,就会丢弃这个包。
而一个路由器下每台电脑的时间戳不是完全一致的,有的电脑的时间戳会小,导致这些电脑发出的通信包被直接丢弃了。
正确的tcp优化姿势
(回头做毕设的时候试一下)
修改/etc/sysctl.conf 文件,内容如下:
net.nf_conntrack_max = 1000000 #发送keepalive的心跳 net.ipv4.tcp_keepalive_time = 1200 net.ipv4.tcp_tw_reuse = 0 #不能改成1 否则NAT后面的客户端可能连接不上 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_fin_timeout = 30 #timewait队列最大数量 net.ipv4.tcp_max_tw_buckets = 262144 #建立连接握手过程的ack重发次数从5到3 net.ipv4.tcp_synack_retries = 3 #缓存syn请求数量 解决messsage日志中syn溢出日志 net.ipv4.tcp_max_syn_backlog = 8192 net.ipv4.tcp_syncookies = 1 #linux收包缓存 net.core.rmem_default=262144 net.core.wmem_default=262144 net.core.rmem_max=4194304 net.core.wmem_max=4194304 net.core.somaxconn=1024 vm.overcommit_memory = 1 vm.max_map_count=262144

TCP/IP常见攻击 了解一下哈,我哥说,我要走到那个位置,别的技术可以不用很深,但是一定要有足够的了解。这不还有些年嘛,时间紧迫了啊!!!
1、IP欺骗,伪造某台主机的 IP 地址的技术。我不会,不过没事没事,我有一个好兄弟跟我一样的喜欢在自己的领域深耕,他就是网络安全领域的。
2、SYN Flooding,这个我知道,我给你握两次手,第三次我不理你了嘿、、、然后你的资源就这样被我鸽干净了。
3、UDP Flooding,UDP 洪泛是也是一种拒绝服务攻击,将大量的用户数据报协议(UDP)数据包发送到目标服务器,目的是压倒该设备的处理和响应能力。防火墙保护目标服务器也可能因 UDP 泛滥而耗尽,从而导致对合法流量的拒绝服务。
大多数操作系统部分限制了 ICMP 报文的响应速率,以中断需要 ICMP 响应的 DDoS 攻击。这种缓解的一个缺点是在攻击过程中,合法的数据包也可能被过滤。如果 UDP Flood 的容量足够高以使目标服务器的防火墙的状态表饱和,则在服务器级别发生的任何缓解都将不足以应对目标设备上游的瓶颈。
4、TCP 重置攻击
问题不大,对长连接还有点用,短链接,等他破译了我的序列号,我早自己断开了。。
5、中间人攻击
开头那链接里有。
攻击中间人攻击英文名叫 Man-in-the-MiddleAttack,简称「MITM攻击」。指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方 直接对话,但事实上整个会话都被攻击者完全控制。
6、DDOS
分布式拒绝服务。指的是处于不同位置的多个攻击者同时向一个或数个目标发动攻击,是一种分布的、协同的大规模攻击方式。
粘包 不扯那些虚的,直接上代码,然后项目里面做基准测试便知:
C
#include/* See NOTES */ #include #include #include /* superset of previous */ #include #include #include #include #include #include #include #define MAX_BUF1024 //自定义封包的结构体 typedef struct _my_pkt {int len; //自定义的包头,包含了包的大小 char buf[MAX_BUF]; //包存放的数据 }my_pkt; void handle(int sig) {printf("recv : %d\n",sig); exit(0); }//@ssize_t:返回读的长度 若ssize_t 0)//当剩余需要读取的个数>0 {if((nread= read(fd,bufp,nleft)) < 0)//成功读取的个数小于0,则判断出错的原因 {//如果errno被设置为EINTR为被信号中断,如果是被信号中断继续, //不是信号中断则退出。 if(errno == EINTR) {continue; } perror("write"); exit(-1); } else if(nread == 0)//若对方已关闭 {return cnt - nleft; }bufp += nread; //将 字符串指针向后移动已经成功读取个数的大小。 nleft -= nread; //需要读取的个数=需要读取的个数-以及成功读取的个数 }return cnt; }//@ssize_t:返回写的长度 -1失败 //@buf:待写数据首地址 //@count:待写长度 ssize_t writen(int fd,const void *buf,size_t cnt) {size_t nleft = cnt; //需要写入的个数 ssize_t nwritten = 0; //已经成功写入的个数 char * bufp = (char *)buf; //接参数while(nleft > 0)//如果需要写入的个数>0 {//如果写入成功的个数<0 判断是否是被信号打断 if((nwritten= write(fd,bufp,nleft)) < 0) {if(errno == EINTR)//信号打断,则继续 {continue; } perror("write"); exit(-1); } //需要写入的数据个数>0 //如果成功写入的个数为0 则继续 else if(nwritten == 0) {continue; }bufp += nwritten; //将bufp指针向后移动已经 nleft -= nwritten; //剩余个数 }return cnt; }int main() {int clt_fd; struct sockaddr_in serv_addr; char addr_dst[INET_ADDRSTRLEN] = { 0}; ssize_t ret; my_pkt sendbuf; my_pkt recvbuf; int num = 0; memset(&sendbuf,0,sizeof(sendbuf)); //清空结构体 memset(&recvbuf,0,sizeof(recvbuf)); //清空结构体signal(SIGUSR1,handle); clt_fd = socket(AF_INET,SOCK_STREAM,0); if(-1 == clt_fd) {perror("socket"); exit(-1); } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8001); serv_addr.sin_addr.s_addr = inet_addr("192.168.1.110"); if(connect(clt_fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) == -1) {perror("connect"); exit(-1); } printf("Connect successfully\t%s at PORT %d\n",inet_ntop(AF_INET,&serv_addr.sin_addr,addr_dst,sizeof(addr_dst)),ntohs(serv_addr.sin_port)); while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin) != NULL) {num = strlen(sendbuf.buf); sendbuf.len = htonl(num); writen(clt_fd,&sendbuf,sizeof(sendbuf.len)+num); ret = readn(clt_fd,&(recvbuf.len),sizeof(recvbuf.len)); //读包头 4个字节 if(-1 == ret) {perror("readn for len"); exit(-1); } else if(ret < 4)//如果读取的个数小于4,则对方已经关闭 {printf("client close"); break; }num = ntohl(recvbuf.len); //将网络数据转换为本地数据结构,比如网络数据为大端,而本地数据为小端 ret = readn(clt_fd,recvbuf.buf,num); //根据包头里包含的大小读取数据 if(-1 == ret) {perror("readn for buf"); exit(-1); } else if(ret < num)//如果读取的数据的大小小于封包包头中包的大小,那么对方已经关闭 {printf("client close"); break; } fputs(recvbuf.buf,stdout); //将数据打印出memset(&sendbuf,0,sizeof(sendbuf)); //清空结构体 memset(&recvbuf,0,sizeof(recvbuf)); //清空结构体 }return 0; }

S
/*server04*/#include/* See NOTES */ #include #include #include /* superset of previous */ #include #include #include #include #include #include #include #define MAX_CLIENT 10 #define MAX_BUF1024//自定义封包的结构体 typedef struct _my_pkt {int len; //自定义的包头,包含了包的大小 char buf[MAX_BUF]; //包存放的数据 }my_pkt; void handle(int sig)//辅助杀死子进程或者父进程 {printf("recv : %d\n",sig); exit(0); }//@ssize_t:返回读的长度 若ssize_t 0)//当剩余需要读取的个数>0 {if((nread= read(fd,bufp,nleft)) < 0)//成功读取的个数小于0,则判断出错的原因 {//如果errno被设置为EINTR为被信号中断,如果是被信号中断继续, //不是信号中断则退出。 if(errno == EINTR) {continue; } perror("write"); exit(-1); } else if(nread == 0)//若对方已关闭 {return cnt - nleft; }bufp += nread; //将 字符串指针向后移动已经成功读取个数的大小。 nleft -= nread; //需要读取的个数=需要读取的个数-以及成功读取的个数 }return cnt; }//@ssize_t:返回写的长度 -1失败 //@buf:待写数据首地址 //@count:待写长度 ssize_t writen(int fd,const void *buf,size_t cnt) {size_t nleft = cnt; //需要写入的个数 ssize_t nwritten = 0; //已经成功写入的个数 char * bufp = (char *)buf; //接参数while(nleft > 0)//如果需要写入的个数>0 {//如果写入成功的个数<0 判断是否是被信号打断 if((nwritten= write(fd,bufp,nleft)) < 0) {if(errno == EINTR)//信号打断,则继续 {continue; } perror("write"); exit(-1); } //需要写入的数据个数>0 //如果成功写入的个数为0 则继续 else if(nwritten == 0) {continue; }bufp += nwritten; //将bufp指针向后移动已经 nleft -= nwritten; //剩余个数 }return cnt; }void do_service(int fd) {my_pkt rcv_pkt; //定义了封包结构体 int num = 0; //数据包长度--封包的包头 int ret = 0; while(1) {memset(&rcv_pkt,0,sizeof(rcv_pkt)); //清空结构体 ret = readn(fd,&(rcv_pkt.len),sizeof(rcv_pkt.len)); //读包头 4个字节 if(-1 == ret) {perror("readn for len"); exit(-1); } else if(ret < 4)//如果读取的个数小于4,则对方已经关闭 {printf("client close"); break; }num = ntohl(rcv_pkt.len); //将网络数据转换为本地数据结构,比如网络数据为大端,而本地数据为小端 ret = readn(fd,rcv_pkt.buf,num); //根据包头里包含的大小读取数据 if(-1 == ret) {perror("readn for buf"); exit(-1); } else if(ret < num)//如果读取的数据的大小小于封包包头中包的大小,那么对方已经关闭 {printf("client close"); break; } fputs(rcv_pkt.buf,stdout); //将数据打印出 //将接受到的数据再直接发出去。 writen(fd,&rcv_pkt,sizeof(rcv_pkt.len)+num); //注意写数据的时候,多加包头长度(len)部分 }}int main() {int serv_fd,con_fd; //服务器端至少要有两个套接字文件描述符--一个用来监听,一个/其余多个用来和客户端通信 struct sockaddr_in serv_addr; //IPV4套接字结构体--服务器 struct sockaddr_in clt_addr; //IPV4套接字结构体--客户端int optvar; //地址复用使用的参数 pid_t pid; //子进程PIDsocklen_t addr_len; signal(SIGUSR1,handle); //注册新号和处理函数serv_fd = socket(AF_INET,SOCK_STREAM,0); //建立套接字 if(-1 == serv_fd) {perror("socket"); exit(-1); }if(setsockopt(serv_fd, SOL_SOCKET,SO_REUSEADDR,&optvar,sizeof(optvar))== -1 )//地址复用 {perror("setsockopt"); exit(-1); }/*设置地址*/ bzero(&serv_addr,sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(8001); serv_addr.sin_addr.s_addr = htons(INADDR_ANY); if(bind(serv_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) == -1)//绑定端口 {perror("bind"); exit(-1); }/*一旦调用listen函数--套接字就会变成被动套接字--用来监听客户端,让客户端连接他 被动套接字--只能接受连接,不能主动发送连接 做了两个队列: 一个已经完成三次握手,建立连接的队列--客户端发connect请求被响应,已经成功完成连接 一个是未完成成三次握手的队列--正在握手 */ if(listen(serv_fd,MAX_CLIENT)== -1)//开始监听 {perror("listen"); exit(-1); }addr_len = sizeof(clt_addr); printf("Accepting connections ...\n"); while(1) {if((con_fd = accept(serv_fd,(struct sockaddr *)&clt_addr,&addr_len)) == -1)//可以支持多并发访问 {perror("accept"); exit(-1); }printf("received from %s at PORT %d\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port)); pid = fork(); //创建子进程if(pid == -1) {perror("fork"); close(serv_fd); exit(-1); } else if(pid == 0)//子进程负责接收客户端数据 {close(serv_fd); do_service(con_fd); close(con_fd); kill(getppid(),SIGUSR1); exit(1); } else//父进程负责监听客户端连接请求 {close(con_fd); } }return 0; }

篇幅有点长了哈,本来还有HTTP和DNS的,下次,下次哈。

    推荐阅读