tcp连接Recv-Q、syn flood攻击及backlog监控

1、Recv-Q和Send-Q


1.1、非listening状态的tcp连接

[root@centos64-1 ~]# netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name   
tcp        0    284 192.168.132.128:22          192.168.132.1:32242         ESTABLISHED 4264/sshd           
tcp        0      0 192.168.132.128:9000        192.168.132.1:42029         ESTABLISHED 6552/java 

等同于 ss -n 命令

里面的Recv-Q 表示此连接等待用户接收的byte个数(已到内核,但还未被用户进程read的字节个数)

Send-Q 表示发送还未被ack的字节个数

Recv-Q
实验:

服务端卡死到accept前面:
tcp连接Recv-Q、syn flood攻击及backlog监控_第1张图片


客户端走到发送完处

tcp连接Recv-Q、syn flood攻击及backlog监控_第2张图片

注意:虽然此时服务端并未accpet此连接,但客户端还是能走到发送完成的代码处的。


可以看到,此时服务端的recv-q已经有等待用户进程read的数据了(另外,服务器端,只有没有不read,此处会一直显示等待接收的字节数)。
tcp连接Recv-Q、syn flood攻击及backlog监控_第3张图片

1.2、listen状态的tcp连接

netstat -lntp

ss -l -n  (能看出效果)


里面的Recv-Q 表示等待accept的连接个数,即完成队列的个数

实验:
服务端走到accept前面:

tcp连接Recv-Q、syn flood攻击及backlog监控_第4张图片


客户端走到发送数据完成处:

tcp连接Recv-Q、syn flood攻击及backlog监控_第5张图片


可以看到,recv-q里面已经显示出了等待accpet的完成状态的连接(此时是established状态)

tcp连接Recv-Q、syn flood攻击及backlog监控_第6张图片


2、syn flooding攻击

为了应对SYN flooding(即客户端只发送SYN包发起握手而不回应ACK完成连接建立,填满server端的半连接队列,让它无法处理正常的握手请求),Linux实现了一种称为SYN cookie的机制,通过net.ipv4.tcp_syncookies控制,设置为1表示开启。简单说SYN cookie就是将连接信息编码在ISN(initial sequence number)中返回给客户端,这时server不需要将半连接保存在队列中,而是利用客户端随后发来的ACK带回的ISN还原连接信息,以完成连接的建立,避免了半连接队列被攻击SYN包填满。对于一去不复返的客户端握手,不理它就是了。


1、增大队列SYN最大半连接数
在Linux中执行命令"sysctl -a|grep net.ipv4.tcp_max_syn_backlog",在返回的"net.ipv4.tcp_max_syn_backlog=256"中显示 Linux队列的最大半连接容量是256.这个默认值对于Web服务器来说是远远不够的,一次简单的SYN攻击就足以将其完全占用.因此,防御DOS攻击 最简单的办法就是增大这个默认值,在Linux中执行命令"sysctl -w et.ipv4.tcp_max_syn_backlog=3000",这样就可以将队列SYN最大半连接数容量值改为3000了

2、减小超时值
在Linux中建立TCP连接时,在客户端和服务器之间创建握手过程中,当服务器未收到客户端的确认包时,会重发请求包,一直到超时才将此条目从未连接队 列是删除,也就是说半连接存在一定的存活时间,超过这个时间,半连接就会自动断开,在上述SYN攻击测试中,当经过较长的的时间后,就会发现一些半连接已 经自动断开了.半连接存活时间实际上是系统所有重传次数等待的超时时间之和,这个值越大,半连接数占用的Backlog队列的时间就越长,系统能处理的 SYN请求就越少,因此,缩短超时时间就可以有效防御SYN攻击,这可以通过缩小重传超时时间和减少重传次数来实现.在Linux中默认的重传次数为5 次,总超时时间为3分钟,在Linux中执行命令"sysctl -w net.ipv4.tcp_synack_retries=1",将超时重传次数设置为1.
sysctl -w net.ipv4.tcp_synack_retries=3 
sysctl -w net.ipv4.tcp_syn_retries=3



3、利用SYN cookie来防御DOS攻击
除了在TCP协议栈中开辟一个内存空间来存储半连接数之外,为避免因为SYN请求数量太多,导致该队列被填满的情况下,Linux服务器仍然可以处理新的 SYN连接,可以利用SYN Cookie技术来处理SYN连接.什么是SYN Cookie呢?SYN Cookie是用一个Cookie来响应TCP SYN请求的,在正常的TCP连接过程中,当服务器接收一个SYN数据包,就会返回一个SYN -ACK包来应答,然后进入TCP -SYN -RECV(半开放连接)状态来等待最后返回的ACK包.服务器用一个数据空间来描述所有未决的连接,然而这个数据空间的大小是有限的,所以攻击者将塞满 这个空间,在TCP SYN COOKIE的执行过程中,当服务器收到一个SYN包的时候,他返回一个SYN -ACK包,这个数据包的ACK序列号是经过加密的,它由TCP连接的源地址和端口号,目标地址和端口号,以及一个加密种子经过HASH计算得出的,然后 服务器释放所有的状态.如果一个ACK包从客户端返回后,服务器重新计算COOKIE来判断它是不是上个SYN -ACK的返回包.如果是的话,服务器就可以直接进入TCP连接状态并打开连接.这样服务器就可以避免守候半开放连接了,在Linux中执行命 令"echo "echo "1" > / proc/sys/net/ipv4/tcp_syncookies"> > /etc/rc_local",这样即可启动SYN Cookie,并将其添加到了Linux的启动文件,这样即使系统重启也不影响SYN Cookie的激活状态.

4、过滤可疑的IP
iptables -A INPUT -s IP地址 -j REJECT
iptables -A INPUT -s IP地址/24 -j REJECT
route add -net IP地址 netmask 255.255.255.0 reject


3、backlog监控

对于运行中的程序,总要关心下当前服务的backlog的两个队列是否有问题,是否有客户端连接不上的问题,及队列溢出时,从哪里能够看到。

netstat -s    //自系统启动开始到现在的统计和


TcpExt:
    26 resets received for embryonic SYN_RECV sockets
    2 TCP sockets finished time wait in fast timer
    39 delayed acks sent
    1 delayed acks further delayed because of locked socket
    126 times the listen queue of a socket overflowed
    126 SYNs to LISTEN sockets ignored

    1008 packets directly queued to recvmsg prequeue.
    1435 packets directly received from prequeue
    761 packets header predicted
    4449 acknowledgments not containing data received
    535 predicted acknowledgments
    TCPDSACKUndo: 4
    0 TCP data loss events
    4 other TCP timeouts
    4 DSACKs received
    3 connections reset due to early user close



实验:

类似于上面的实验:

1、启动服务端时,把backlog的值设置小一点,如 3 ,并让代码走到accept前面停止

2、启动多个客户端,如20个客户端,让代码走到发送完成处

3、执行 netstat -s  观看TcpExt 字段处,是否有异常队列的计数


需要注意的是:

1、当客户端连接不上时,会重复去连接,所以上面的溢出个数就是多个客户端多次连接后的溢出个数

2、上面的计数是从自系统启动开始到现在的统计和,重启后,置0 ,从头开始计算



测试代码:

java -Xdebug -Xrunjdwp:transport=dt_socket,address=9000,server=y,suspend=y babyserver/Server 8080 10

package babyserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class Server {
	ServerSocket serverSocket;

	public Server(int port,int backlog) {
		try {
			serverSocket = new ServerSocket(port,backlog);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		if (args.length > 0) {
			int port = Integer.valueOf(args[0]);
			Server server = null;
			if(args.length > 1) {
				server = new Server(port , Integer.valueOf(args[1]));
			}else{
				server = new Server(port,50);
			}
			server.service();
		}
		System.out.println("server over");
	}

	public void service() {
		while (true && null != serverSocket) {
			Socket socket = null;
			try {
				socket = serverSocket.accept();// 从连接队列中取出一个连接,如果没有则等待
				handler(socket);
			//	log("");
			//	log("");
				log("");
				// 接收和发送数据
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (socket != null)
						socket.close();// 与一个客户端通信结束后,要关闭Socket
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	int num=0;
	void handler(Socket socket) {
		BufferedReader br = null;
		PrintWriter pw = null;
		try {
			log("new connection["+num+"] remote info :" + socket.getInetAddress() + ":"+ socket.getPort() + "  ,local port:"+socket.getLocalPort());
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream(), true);
			String msg = br.readLine();
			while (null != msg && !msg.isEmpty()) { // 接收和发送数据, 直到通信结束
				log("head["+msg+"]");
				msg = br.readLine();
			}
			String res = "helloword!"+num++;
			log("response:"+res);
			pw.println(res);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			try {
				if(null != br) br.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				if(null != pw) pw.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				if(null != socket) socket.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	void log(String log){
		if(!log.isEmpty()){
			System.out.println(new Date().toLocaleString() +"  " + log);
		}else{
			System.out.println();
		}
		
	}
}


package baby;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Client {

	public static void main(String[] args) {
		ExecutorService executorService = Executors.newCachedThreadPool();
		
		for(int i=0;i<50;i++){
			final int fi = i;
			executorService.submit(new Callable() {

				@Override
				public Object call() throws Exception {
					System.out.println("begin:"+fi);
					pro();
					System.out.println("end:"+fi);
					return null;
				}
			});
		}
		try {
			Thread.sleep(1000*60*50);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	
	static void pro(){
		Socket socket = null;
		InputStream bis = null;
		OutputStream bos = null;
		try {
			socket = new Socket("192.168.132.128", 8080);
			
			System.out.println(socket.isConnected());
			bis = socket.getInputStream();
			bos = socket.getOutputStream();
		//	Thread.sleep(1000 * 60 * 5);
			String msg = "hello,i am client\r\n";
			bos.write(msg.getBytes());
			bos.write("\r\n".getBytes());
		//	bos.close();
			bos.flush();
		//	socket.
			byte[] buffer = new byte[1024*10];
			
			int len = bis.read(buffer);
			String response = new String(buffer,0,len);
			System.out.println("response:"+response);
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally{
			if (null != bis)
				try {
					bis.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			if (null != bis)
				try {
					bos.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			if (null != bis)
				try {
					socket.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
		}
	}
}
 
  

你可能感兴趣的:(tcp连接Recv-Q、syn flood攻击及backlog监控)