DDOS导致Nginx 499状态码成因分析

栏目: 编程工具 · 发布时间: 7年前

内容简介:DDOS导致Nginx 499状态码成因分析

Nginx 499状态码的成因分析

27 Apr 2017

Reading time ~1 minute

工作时遇到一个疑似被DDOS的情况,查看网站日志发现Nginx有大量的499状态码,但是正常用浏览器访问网站却能访问,没有任何问题。所以搜索了一些资料,找到了原因并复现了该现象。

猜想

499 状态码是 Nginx 自己定义的特殊返回码,查看资料,Nginx源码中是如下规定的:

ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
ngx_string(ngx_http_error_497_page), /* 497, http to https */
ngx_string(ngx_http_error_404_page), /* 498, canceled */
ngx_null_string,                    /* 499, client has closed connection */

字面上的意思是客户端关闭了连接导致的。我的理解是客户端在发送HTTP请求后,不接收返回包,也不等服务器响应,立即关闭HTTP通道,或者强行关闭tcp连接。这样就会导致服务器在想发送response包给客户端时,找不到客户端的连接,所以提示499的状态码。

测试

针对以上的猜想,我对我自己的博客网站做了测试。用sock和httplib分别写了两段代码如下:

sock:

#encoding:utf-8
#created by noble
import sys
import socket
from multiprocessing import Process as pro
from multiprocessing.dummy import Process as thr

def sendsock(target, url, i):
	obj = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	obj.settimeout(0.8)
	obj.connect((target, 80))
	print i
	obj.send("GET %s HTTP/1.0\r\nHost: %s\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36\r\nAccept: */*\r\n\r\n" %(url, target))
	obj.close()
	print i

if __name__ == '__main__':
	target = sys.argv[1]
	url = "/goods.php?id=51"
	for i in range(1000):      
		t=pro(target=sendsock, args=(target, url, i))
		t.start()

httplib:

#encoding:utf-8
#created by noble
import sys
import socket
import gevent
import httplib
from multiprocessing import Process as pro
from multiprocessing.dummy import Process as thr

def sendhttp(target, url, i, obj):
	print i
	obj.request('GET', url)
	obj.getresponse()

def run(target, url, i, obj):
	jobs=[gevent.spawn(sendhttp,target, url, i, obj) for i in range(10)]
	gevent.joinall(jobs)
	for i in jobs:
		i.join()

if __name__ == '__main__':
	target = sys.argv[1]
	url = "/goods.php?id=51"
	obj = httplib.HTTPConnection(target, 80, timeout=0.5)
	for i in range(1000):      
		t=pro(target=run, args=(target, url, i, obj))
		t.start()

第一段sock的代码全用的多进程,第二段httplib的用多进程和协程结合,然而这两种方法对于我的博客并发4-5千没有任何影响,没有一个499状态码出现。按照网络上文章的解释,这里我猜测是无论是sock还是http,在关闭请求时都没有关闭TCP连接,所以导致了客户端其实没有断开连接,所以服务端依然返回200。所以抓包分析了一下在做sock请求时的代码:

客户端: DDOS导致Nginx 499状态码成因分析

服务端:

服务端的图就不截了,基本就是客户端的反向,基本没有差别。

从TCP流分析,首先开始是TCP三次握手,然后服务端向客户端发送HTTP请求,发送完HTTP请求之后,立刻发送FIN包,请求关闭TCP连接,开始四次挥手的流程。差不多60ms之后,客户端收到了服务端的发来的ACK包,代表服务端进入CLOSE-WAIT状态,此时TCP四次握手并没有完全关闭,服务端依然会将HTTP请求的数据发送给客户端。所以我们看到了NO.504包和NO.561包,length均为1514,这个包就是服务端把HTTP的返回包发给客户端的包,但是客户端接收到这个包之后,由于客户端本身已经关闭连接不接收数据,所以就会发RST包给服务端,代表接收到了异常数据,连接异常关闭。这也就导致了TCP四次挥手没有全部做完,而是异常关闭的。附一张TCP的连接图如下:

DDOS导致Nginx 499状态码成因分析

回到我们的问题上,我们的猜想也可以合理解释了: 即使我们的SOCK连接是发送了http请求后立马就请求关闭TCP连接,但是TCP四次挥手需要时间,在TCP挥手结束之前,服务端就把数据传输了过来,所以返回包依然是200。

二次测试

这个时候我们还无法验证我们的猜想是否正确,所以我找了一个 PHP 的站点,又一次进行了上述实验,在Nginx代理PHP的环境下,上述两个测试脚本均造成了大量499状态码的出现。

抓下包,截图如下: DDOS导致Nginx 499状态码成因分析

这是跑第一个sock脚本的结果,这个TCP流就简单多了,先三次握手,再发送HTTP请求包,再四次挥手,可以看到在此时服务端并没有返回数据给客户端,也就是说明服务端在TCP连接关闭之前,没有生成完网页内容并把内容返回给客户端。此时再看一下服务端的状态,Nginx日志中出现了大量499状态。

DDOS导致Nginx 499状态码成因分析

结论分析

经过两次测试,初步可以得出以下结论:

  1. 499状态码产生的原因是在服务端准备好返回内容并发送之前,客户端已经关闭了和服务端的 TCP 连接。而之所以我的博客不会产生是因为静态页面,Nginx处理的很快,而Nginx主要用作反向代理,准备返回包的内容肯定会有一定的延迟,所以499状态码的出现屡见不鲜。
  2. 经抓包分析,python的httplib模块,在发送完HTTP请求,接受完数据后就会主动关闭TCP连接,下次请求时会再新建一个TCP连接,而不是一直保持一个HTTP长连接不断。
  3. 网上流传的解决措施: proxy_ignore_client_abort on; ,经测试没有任何效果。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

全栈开发之道

全栈开发之道

和凌志 / 电子工业出版社 / 68.00元

全栈(Full Stack)是一种全新的以前端为主导的框架,框架选型聚焦在MEAN(MongoDB、Express、AngularJS、Node.js)上。选用MEAN全栈技术,可以快速地实现敏捷开发,尤其是到了产品的运营阶段,其优势表现得非常明显。本书主要介绍MEAN全栈技术,分为入门篇、基础篇和实战篇,入门篇对全栈进行了概述,基础篇重点介绍了全栈的四个主要技术,即MongoDB、Express......一起来看看 《全栈开发之道》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

URL 编码/解码
URL 编码/解码

URL 编码/解码

MD5 加密
MD5 加密

MD5 加密工具