一、无效请求的处理
通常来说,系统都是为了给“合法”请求提供服务,但是总有一些异常发生,也就是意料之外的事情。类似的,如果异常发生在一些知名的公司,这些事件可能就是“公关危机”。对于软件来说,通常这些异常情况也就是考验一个产品健壮性的机会,不同质量的软件对于这种异常的处理在很大程度上决定了软件的质量。当然对于软件的学习和理解来说,通常不会提升到这么高的高度,但是对于理解一个软件的正常流程来说通常异常情况更能加深对于软件的理解。
由于apache是最为流行的http服务器,所以通常很容易用这个端口来测试一些端口行为,而这些测试大多发送的又是二进制协议,这些表示标准的http请求,那么apache对于这些异常的请求将会如何处理呢?
二、简单测试
在启动了httpd服务器的本地机器上执行向端口输入二进制数据,这里输入的是一个不包含回车符的0x1字符,下面是通过抓包获得的数据
1、不添加回车符
tsecer@harry: (echo -ne 'x1'; sleep 1000; ) >/dev/tcp/127.0.0.1/80
另一个窗口抓包,可以看到服务器在等待了20s之后主动关闭了链路,但是在apache的错误日志中默认没有打印这个链接的记录。
tsecer@harry: tcpdump -i any -nns0 -A port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
08:02:15.367902 IP 127.0.0.1.60740 > 127.0.0.1.80: Flags [S], seq 4067916201, win 43690, options [mss 65495,sackOK,TS val 9933863 ecr 0,nop,wscale 7], length 0
E..<..@.@.O+.........D.P.wy..........0.........
...'........
08:02:15.367982 IP 127.0.0.1.80 > 127.0.0.1.60740: Flags [S.], seq 2987582136, ack 4067916202, win 43690, options [mss 65495,sackOK,TS val 9933863 ecr 9933863,nop,wscale 7], length 0
E..<..@.@.<..........P.D.....wy......0.........
...'...'....
08:02:15.368045 IP 127.0.0.1.60740 > 127.0.0.1.80: Flags [.], ack 1, win 342, options [nop,nop,TS val 9933863 ecr 9933863], length 0
E..4..@.@.O2.........D.P.wy........V.(.....
...'...'
08:02:15.372126 IP 127.0.0.1.60740 > 127.0.0.1.80: Flags [P.], seq 1:2, ack 1, win 342, options [nop,nop,TS val 9933867 ecr 9933863], length 1
E..5..@.@.O0.........D.P.wy........V.).....
...+...'.
08:02:15.372260 IP 127.0.0.1.80 > 127.0.0.1.60740: Flags [.], ack 2, win 342, options [nop,nop,TS val 9933868 ecr 9933867], length 0
E..4v.@.@../.........P.D.....wy....V.(.....
...,...+
08:02:35.424961 IP 127.0.0.1.80 > 127.0.0.1.60740: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 9953920 ecr 9933867], length 0
E..4v.@.@............P.D.....wy....V.(.....
.......+
08:02:35.425279 IP 127.0.0.1.60740 > 127.0.0.1.80: Flags [.], ack 2, win 342, options [nop,nop,TS val 9953921 ecr 9953920], length 0
E..4..@.@.O0.........D.P.wy........V.(.....
........
2、添加回车符
tsecer@harry: (echo -e 'x1'; sleep 1000; ) >/dev/tcp/127.0.0.1/80
对应的输出,可以看到服务器马上回包,提示Method Not Implemented
tsecer@harry: tcpdump -i any -nns0 -A port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 65535 bytes
08:06:05.694009 IP 127.0.0.1.60741 > 127.0.0.1.80: Flags [S], seq 3305081832, win 43690, options [mss 65495,sackOK,TS val 10164189 ecr 0,nop,wscale 7], length 0
E..<.5@.@............E.P.............0.........
............
08:06:05.694114 IP 127.0.0.1.80 > 127.0.0.1.60741: Flags [S.], seq 4112322835, ack 3305081833, win 43690, options [mss 65495,sackOK,TS val 10164189 ecr 10164189,nop,wscale 7], length 0
E..<..@.@.<..........P.E.............0.........
............
08:06:05.694142 IP 127.0.0.1.60741 > 127.0.0.1.80: Flags [.], ack 1, win 342, options [nop,nop,TS val 10164189 ecr 10164189], length 0
E..4.6@.@............E.P...........V.(.....
........
08:06:05.702012 IP 127.0.0.1.60741 > 127.0.0.1.80: Flags [P.], seq 1:3, ack 1, win 342, options [nop,nop,TS val 10164197 ecr 10164189], length 2
E..6.7@.@............E.P...........V.*.....
.........
08:06:05.702118 IP 127.0.0.1.80 > 127.0.0.1.60741: Flags [.], ack 3, win 342, options [nop,nop,TS val 10164197 ecr 10164197], length 0
E..4..@.@.5..........P.E...........V.(.....
........
08:06:05.777740 IP 127.0.0.1.80 > 127.0.0.1.60741: Flags [P.], seq 1:214, ack 3, win 342, options [nop,nop,TS val 10164273 ecr 10164197], length 213
E.. ..@.@.4..........P.E...........V.......
...1....<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>501 Method Not Implemented</title>
</head><body>
<h1>Method Not Implemented</h1>
<p>. to /index.html not supported.<br />
</p>
</body></html>
08:06:05.777765 IP 127.0.0.1.60741 > 127.0.0.1.80: Flags [.], ack 214, win 350, options [nop,nop,TS val 10164273 ecr 10164273], length 0
E..4.8@.@............E.P...........^.(.....
...1...1
08:06:05.783629 IP 127.0.0.1.80 > 127.0.0.1.60741: Flags [F.], seq 214, ack 3, win 342, options [nop,nop,TS val 10164279 ecr 10164273], length 0
E..4..@.@.5..........P.E...........V.(.....
...7...1
08:06:05.824534 IP 127.0.0.1.60741 > 127.0.0.1.80: Flags [.], ack 215, win 350, options [nop,nop,TS val 10164320 ecr 10164279], length 0
E..4.9@.@............E.P...........^.(.....
...`...7
三、apache的处理方法
读取输入请求的调用链为
(gdb) bt
#0 0x0000003367aeaa8d in poll () from /lib64/libc.so.6
#1 0x00007f7181154afe in apr_poll (aprset=aprset@entry=0x7f717dc6fca0,
num=num@entry=1, nsds=nsds@entry=0x7f717dc6fc84, timeout=<optimized out>)
at poll/unix/poll.c:120
#2 0x00007f717fefad28 in reqtimeout_filter (f=0x7f7178003638,
bb=0x7f7160001ae0, mode=<optimized out>, block=<optimized out>,
readbytes=<optimized out>) at mod_reqtimeout.c:280
#3 0x0000000000431d8f in ap_rgetline_core (s=s@entry=0x7f7160000990, n=8192,
read=read@entry=0x7f717dc6fdd8, r=r@entry=0x7f7160000960,
fold=fold@entry=0, bb=bb@entry=0x7f7160001ae0) at protocol.c:232
#4 0x0000000000433cef in read_request_line (bb=0x7f7160001ae0,
r=0x7f7160000960) at protocol.c:599
#5 ap_read_request (conn=conn@entry=0x7f71780031e0) at protocol.c:967
#6 0x0000000000455d29 in ap_process_http_async_connection (c=0x7f71780031e0)
at http_core.c:135
#7 ap_process_http_connection (c=0x7f71780031e0) at http_core.c:228
#8 0x000000000044e950 in ap_run_process_connection (c=0x7f71780031e0)
at connection.c:41
#9 0x00000000004604fc in process_socket (my_thread_num=0, my_child_num=0,
cs=0x7f7178003168, sock=<optimized out>, p=<optimized out>,
thd=<optimized out>) at event.c:917
---Type <return> to continue, or q <return> to quit---
#10 worker_thread (thd=<optimized out>, dummy=<optimized out>) at event.c:1744
#11 0x0000003368207f33 in start_thread () from /lib64/libpthread.so.0
#12 0x0000003367af4ead in clone () from /lib64/libc.so.6
(gdb)
在其中的reqtimeout_filter函数中,会执行
static apr_status_t have_lf_or_eos(apr_bucket_brigade *bb)
{
apr_bucket *b = APR_BRIGADE_LAST(bb);
for ( ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_PREV(b) ) {
const char *str;
apr_size_t len;
apr_status_t rv;
if (APR_BUCKET_IS_EOS(b))
return APR_SUCCESS;
if (APR_BUCKET_IS_METADATA(b))
continue;
rv = apr_bucket_read(b, &str, &len, APR_BLOCK_READ);
if (rv != APR_SUCCESS)
return rv;
if (len == 0)
continue;
if (str[len-1] == APR_ASCII_LF)
return APR_SUCCESS;
}
return APR_INCOMPLETE;
}
如果找不到换行符并且不是输入结束,则需要继续等待。
默认初始值为
request_rec *ap_read_request(conn_rec *conn)
{
……
r->status = HTTP_OK; /* Until further notice */
由于没有处理,所以这个地方会直接返回等待下一个输入。
static int process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock,
event_conn_state_t * cs, int my_child_num,
int my_thread_num)
……
if (cs->pub.state == CONN_STATE_LINGER) {
if (!start_lingering_close(cs))
return 0;
}
四、对应的apache配置
httpd-2.4.2modulesfiltersmod_reqtimeout.c
#define UNSET -1
#define MRT_DEFAULT_HEADER_TIMEOUT 20
#define MRT_DEFAULT_HEADER_MAX_TIMEOUT 40
#define MRT_DEFAULT_HEADER_MIN_RATE 500
#define MRT_DEFAULT_BODY_TIMEOUT 20
#define MRT_DEFAULT_BODY_MAX_TIMEOUT 0
#define MRT_DEFAULT_BODY_MIN_RATE 500
……
static const command_rec reqtimeout_cmds[] = {
AP_INIT_RAW_ARGS("RequestReadTimeout", set_reqtimeouts, NULL, RSRC_CONF,
"Set various timeout parameters for reading request "
"headers and body"),
{NULL}
};
Allow at least 10 seconds to receive the request including the headers. If the client sends data, increase the timeout by 1 second for every 500 bytes received. But do not allow more than 30 seconds for the request including the headers:
RequestReadTimeout header=10-30,MinRate=500