spdy 协议由于安卓碎片化的存在 暂时还是需要保留一段时间的兼容性

准备升级到nginx1.14的时候发现 work process 会自动 退出, 同时系统日志有 nginx segfault的信息

修改配置,抓取coredump信息,需要做以下内容
nginx 增加

worker_rlimit_core 5000M;
working_directory /path/to/cores/;
$> ulimit -c unlimited
$> mkdir /opt/coredump/ && chown nobody.nobody /opt/coredump/ # 先建目录,还要确认nginx用户可以写此目录
$> echo “/opt/coredump/core-%e-%p-%h-%t” > /proc/sys/kernel/core_pattern

拿到coredump文件后使用gdb分析

gdb /path/to/nginx /path/to/cores/nginx.core
backtrace full

发现问题指向了
src/http/ngx_http_spdy.c:ngx_http_spdy_state_read_data 的
buf->last = ngx_cpymem(buf->last, pos, size);

简单调试发现buf->last是个0, ngx_cpymem会因为内存越界导致coredump

而分析代码 + gdb 断点调试 看到初始化r->request_body->buf的部分: ngx_http_spdy_init_request_body(r) 并未执行

打印r->request_body 内容发现这块被初始化了,对比nginx1.12.2和1.10.3版本发现旧版本则是未做初始化

翻了下调用的部分:ngx_http_request_body: ngx_http_read_client_request_body 可以看到在nginx 1.13.12版本开始会对r->request_body 做了初始化操作,这部分直接导致了SPDY 补丁 的不兼容

所以答案就很简单了,修改下判断条件即可

新补丁放在了:https://github.com/favortel/nginx_patch/blob/master/nginx-1.14.0_spdy_h2.patch
参考文档:
https://toontong.github.io/blog/nginx-gdb-coredump-segfault.html
https://www.nginx.com/resources/wiki/start/topics/tutorials/debugging/
http://lxr.nginx.org/source/src/http/ngx_http_request_body.c
http://lxr.nginx.org/source/src/http/ngx_http_request_body.c?v=nginx-1.12.2

Syntax: add_header name value [always];
Default:
Context: httpserverlocationif in location

Adds the specified field to a response header provided that the response code equals 200, 201 (1.3.10), 204, 206, 301, 302, 303, 304, 307 (1.1.16, 1.0.13), or 308 (1.13.0). The value can contain variables.

There could be several add_header directives. These directives are inherited from the previous level if and only if there are no add_header directives defined on the current level.

add_header指令可以从上一级继承  当且仅当 当前级别没有add_header指令

If the always parameter is specified (1.7.5), the header field will be added regardless of the response code.

 

在全局配置加了个需要使用的header, 现在假如在某个location 或者 server 也需要add_header,则会覆盖掉全局配置的

http {… add_header http-global ‘123’;…}

server {… add_header server-conf ‘321’;…}

则 这个server 是看不到 http-global: 123  这个header的,需要注意

 

其实,proxy_set_header 也是完全相同的情况

Syntax: proxy_set_header field value;
Default:
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
Context: httpserverlocation

Allows redefining or appending fields to the request header passed to the proxied server. The value can contain text, variables, and their combinations. These directives are inherited from the previous level if and only if there are no proxy_set_header directives defined on the current level. By default, only two fields are redefined:

 

nginx proxy模式下502 bad gateway 问题

并发测试的时候发现nginx 502 bad gateway 了,看了下日志发现很多upstream Cannot assign requested address的记录
connect() to 192.168.89.170:80 failed (99: Cannot assign requested address) while connecting to upstream

正常判断应该是端口不够用了
不过,我确实开启了: net.ipv4.tcp_tw_recycle = 1 和 net.ipv4.tcp_tw_reuse = 1两个参数
理论上应该可以把timewait 端口重用,查了下这个参数跟tcp_timestamps有关(http://blog.sina.com.cn/s/blog_781b0c850100znjd.html)

if (tmp_opt.saw_tstamp &&
tcp_death_row.sysctl_tw_recycle &&
(dst = inet_csk_route_req(sk, req)) != NULL &&
(peer = rt_get_peer((struct rtable *)dst)) != NULL &&
peer->v4daddr == saddr) {
if (get_seconds() < peer->tcp_ts_stamp + TCP_PAWS_MSL &&
(s32)(peer->tcp_ts – req->ts_recent) >
TCP_PAWS_WINDOW) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}

tmp_opt.saw_tstamp:该socket支持tcp_timestamp
sysctl_tw_recycle:本机系统开启tcp_tw_recycle选项
TCP_PAWS_MSL:60s,该条件判断表示该源ip的上次tcp通讯发生在60s内
TCP_PAWS_WINDOW:1,该条件判断表示该源ip的上次tcp通讯的timestamp 大于 本次tcp

因此: 应该在proxy端和后端都开启net.ipv4.tcp_timestamps=1

nginx with static libcurl

场景是这样子的: 这边有个nginx 模块 include curl/curl.h,而我的编译参数–with-openssl使用了最新的openssl 1.0.1g,编译出来的nginx直接segfault

去除这个模块或者去掉–with-openssl都能正常使用,推测是系统的libcurl(https)包含了libssl的依赖,与内嵌的openssl产生冲突

于是解决办法就是把libcurl也编译到nginx里边,绕开冲突和依赖

1. 静态编译libssl
cd openssl-1.0.1g
./config –prefix=/usr/src/redhat/BUILD/nginx-1.4.7/openssl-1.0.1g/.openssl no-shared no-threads
make
make install
make install LIBDIR=lib

2. 静态编译libcurl
cd curl-7.36.0
./configure –prefix=/usr/src/redhat/BUILD/nginx-1.4.7/curl-7.36.0/.curl –with-ssl=/usr/src/redhat/BUILD/nginx-1.4.7/openssl-1.0.1g/.openssl/lib/ –disable-ldap –disable-ldaps –without-libidn –enable-static=yes –enable-shared=no

#去除对librt.so的依赖,不介意可以不修改
sed -i /HAVE_CLOCK_GETTIME_MONOTONIC/d lib/curl_config.h

make
make install

3. 修改nginx的Makefile
#替换libcurl.so(lcurl)为静态编译的libcurl.a
sed -i ‘s#-lcurl#curl-7.36.0/.curl/lib/libcurl.a -Lopenssl-1.0.1g/.openssl/lib -lcrypto -lz#g’ objs/Makefile
make
make install

做完这步,就生成了包含libcurl和libssl的nginx了

nginx request line parsing vulnerability

CVE-2013-4547

nginx1.4.4和1.5.7版本之前有安全漏洞,会导致可能的绕开防护或者php解析攻击

比如
location ~ \.php$ {
fastcgi_pass …
}

by requesting a file as “/file /0.php”.

实地测试了一番,这个攻击在我们环境中比较难重现,需要以下条件同时成立
1.需要上传了一个带空格的文件(含攻击程序)
2.需要php设置了fix_pathinfo=1(默认为1)
3.需要php版本低于5.3.10,或者允许了所有的security.limit_extensions

因此,基于安全理由,建议升级并且升级php到5.3序列的最新版本

nginx 嵌套 error_page

nginx有时候希望嵌套的处理error_page
比如 location / {
error_page 404 @fetch
}

location @fetch {
error_page 404 @fetch2
proxy_pass http://backend1/one/;
}
location @fetch2 {
proxy_pass http://backend2/two/;
return 200 “xxx”;
}

这是个简单的例子,如果需要实现功能需要两个参数: proxy_intercept_errors on; recursive_error_pages on;
syntax: recursive_error_pages on | off;
default:
recursive_error_pages off;
context: http, server, location
Enables or disables doing several redirects using the error_page directive. The number of such redirects is limited.

syntax: proxy_intercept_errors on | off;
default:
proxy_intercept_errors off;
context: http, server, location
Determines whether proxied responses with codes greater than or equal to 300 should be passed to a client or be redirected to nginx for processing with the error_page directive.

近期协助排查一个故障发现有50x错误,开error日志发现有malloc或者crash work process的记录

[emerg] 5309#0: *288 malloc(808334101) failed (12: Cannot allocate memory) while sending to client
[notice] 4579#0: signal 18 (SIGCHLD) received
[notice] 4579#0: worker process 5211 exited with code 0

这其实是lua模块导致的问题,在0.7.5之前,nginx lua module存在 ngx.req.clear_header 导致的内存溢出问题,需要升级到最新版
that is all,完毕

介绍下igor职业打手Maxim Dounin写的一个gunzip模块

Gunzip module for nginx.
This module allows gunzipping responses returned with Content-Encoding: gzip
for clients that doesn’t support it. It may be usefull if you prefer to store
data compressed (to save space or disk/network IO) but do not want to penalize
clients without gzip support.

Note well: only responses with Content-Encoding set to gzip before this module
are handled (e.g. using “add_header Content-Encoding gzip;” isn’t enough as it
happens after). As of now only proxy and fastcgi are able to do so.

这个模块能针对不支持gzip编码的客户端,直接解压gzip格式的内容
好处:
1.跟源站直接请求压缩的内容,减少回源带宽,提高响应速度
2.只保留一份压缩的内容,减少缓存的大小,相同的cache能放更多的内容
配置格式比较简单:

Configuration directives:

gunzip (on|off)

Context: http, server, location
Default: off

Switches gunzip.

gunzip_buffers

Context: http, server, location
Default: 32 4k/16 8k

Specifies number and size of buffers available for decompression.

Usage:

location /storage/ {
gunzip on;

}

需要特别指出:
1.客户端不支持gzip编码,那么gunzip模块就返回解压的内容
2.客户端支持gzip编码,那么gunzip就自动不起作用,返回原始内容

某些特殊需求,比如addition_filter,我们知道这些filter在非压缩的内容才能正常工作,要结合这个模块就不是那么方便了
因此可以简单修改代码,让它不理会客户端的header,总是返回非压缩内容,注释掉这一整块即可:
ngx_http_gunzip_filter_module.c

141 #if (nginx_version >= 8025 || (nginx_version >= 7065 && nginx_version < 8000)) 142 143 r->gzip_vary = 1;
144
145 if (!r->gzip_tested) {
146 if (ngx_http_gzip_ok(r) == NGX_OK) {
147 return ngx_http_next_header_filter(r);
148 }
149
150 } else if (!r->gzip_ok) {
151 return ngx_http_next_header_filter(r);
152 }
153
154 #else
155
156 if (ngx_http_gzip_ok(r) == NGX_OK) {
157 return ngx_http_next_header_filter(r);
158 }
159
160 #endif

有人写了个gunzip_always的开关补丁,可以控制这个属性,patch暂时不放出来

模块地址: http://mdounin.ru/hg/ngx_http_gunzip_filter_module/

本文已经过时并且存在大量不安全,请参阅最新文档   https://www.4os.org/index.php/category/https/
nginx 默认编译就是支持https的,只需要开启ssl就好
配置如下:
                listen                  443 ;
                server_name             4os.org *.4os.org;

                ssl                     on;
                ssl_certificate         gz.crt;
                ssl_certificate_key     gz.key;
                ssl_session_timeout     5m;
                ssl_protocols           SSLv2 SSLv3 TLSv1;
                ssl_ciphers             ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
                ssl_prefer_server_ciphers       on;
....

其中:
gz.crt是证书颁发机构给的证书(免费ssl-证书/)
gz.key是解密后的私钥

上文中提到的私钥是加密的,可以在startssl的工具箱里边解密,也可以自己做:

openssl  rsa -in gz.pri -out gz.key,输入私钥生成时设置的密码,出来的就是不加密的私钥了,nginx启动也不会要你输入密码了

补充:

1. “SSL_CTX_use_PrivateKey_file fail”之类的错误,通常都是私钥和证书不匹配造成的,请确认你生成证书与私钥匹配

2.firefox证书需要根证书信息:

wget http://www.startssl.com/certs/sub.class1.server.ca.pem

cat sub.class1.server.ca.pem >> gz.crt

停止和重启apache与nginx有些许不同,彼此经验不能照搬

TERM:

两者相同,都是发指令给父进程,父进程立刻尝试杀死所有的子进程并退出

USR1:

nginx的文档说得很简单:reopen the logfile,实际上的操作是master重新打开日志文件,并改变日志文件权限,是worker进程有读写权限,然后发USR1给worker进程重新打开日志文件,这完全不涉及任何worker进程的重新启动

apache则不同,它的父进程会”建议”所有子进程完成当前请求后退出,父进程将重新读取配置文件和日志文件,每个子进程退出后父进程将生成新的子进程

HUP:

nginx收到这个信号会做3个事情:

1.重新读取配置文件

2.使用新的配置启动新的worker进程

3.旧的worker完成当前请求后退出

apache的做法不同,父进程接收到该信号后会跟TERM信号杀掉所有子进程,重新读取配置文件,重新打开日志文件,并声称新的子进程来服务.与TERM信号不同的是,父进程不退出,服务不会中止