分类目录归档:技术

谈谈 XOPEN_SOURCE

最近维护一个古老代码的时候,在AS7里边编译的时候出现了不少错误,比如:

warning: comparison between pointer and integer, 这是代码 strptime(current_time, “%a%n%b%n%d%n%H:%M:%S%n%Y”, &tm) == NULL) 产生的

还有, warning: incompatible implicit declaration of built-in function ‘snprintf’, 这是类似 snprintf(path, sizeof(path), “%s/arc_%s”, archdir, com->cat) 的代码产生的

查了下,strptime的异常返回值确实是NULL, http://man7.org/linux/man-pages/man3/strptime.3.html

If strptime() fails to match all of the format string and therefore an error occurred, the function returns NULL.

一般来说, 类似的snprintf错误 添加 #include<stdlib.h>就好,但是这个古老代码里边已经有这个了

在代码的开头发现了这个东西

define _XOPEN_SOURCE

       _XOPEN_SOURCE
              Defining this macro causes header files to expose definitions as
              follows:

              o  Defining  with  any  value  exposes definitions conforming to POSIX.1, POSIX.2, and XPG4.

              o  The value 500 or greater additionally exposes definitions for SUSv2 (UNIX 98).

              o  (Since  glibc  2.2)  The  value  600  or greater additionally exposes  definitions  for   SUSv3   (UNIX   03;   i.e.,   the POSIX.1-2001  base  specification plus the XSI extension) and C99 definitions.

              o  (Since glibc 2.10) The  value  700  or  greater  additionally exposes  definitions  for  SUSv4 (i.e., the POSIX.1-2008 base specification plus the XSI extension).

https://linux.cn/man7/feature_test_macros.7.html, 初略的说,大概是不同的值会影响某些函数和库的一些标准行为,而默认的_xopen_soure 不带参数会被认为是<500

#define _XOPEN_SOURCE        /* or any value < 500 */

所以需要修正这个模式,添加一个标准即可,由于代码比较古老,修改为500和600都可以编译通过

https 证书比较 geotrust globalsign verisign digicert

目前收费的证书有几个品牌: 来自于digicert旗下的 Geotrust 和 Digicert, 来自日本的globalsign

digicert: 其实是之前的verisign的替换,由于chrome拒绝掉了symantec的安全认证导致这个品牌的彻底消亡被digicert收购并重新签发证书

优点: 兼容性好,大品牌, RSA ECC双认证, 支持Windows XP SP3 +, iOS5 +, Firefox 2+, Android 1.5+

缺点: 贵, 如果要实现WinXP全兼容,需要Baltimore cross 认证

使用公司: weixin.qq.com www.sohu.com www.zhihu.com等

GeoTurs: 低端品牌, 由于被digicert收购后用新根签发,兼容性大增

优点:  便宜 兼容性好

缺点: 只支持RSA认证, 品牌认知度偏低

Globalsign: 来自日本的证书品牌, 2016年曾出现过中间证书异常吊销的事故,目前占据了越来越多的市场,目前百度 阿里 腾讯 京东 爱奇艺 搜狗都使用这个品牌

优点: 相对便宜,只有之前verisign的一半价格, 兼容性好

缺点:  2016年的事故让人有疑虑,签发过程会比digicert略微繁琐些

整体看: 目前几家商业证书的兼容性基本都在同一水平上, 只剩下品牌认知度的影响

globalsign其实是个非常合适的性价比之选

当然,对于非常计较成本的用户,当然是用免费证书

https://knowledge.digicert.com/generalinformation/digicert-root-compatibility.html

nginx 与 TLS1.3

TLS1.3支持了更优秀的SSL 新特性,可以有效降低https的协商时间,建议升级

本文使用了最新的nginx 1.14.1 (该版本修正了 1.14 H2 cpu/mem 攻击漏洞: low)

#wget https://www.openssl.org/source/openssl-1.1.1.tar.gz
#wget http://nginx.org/download/nginx-1.14.1.tar.gz

#spdy兼容补丁
#wget https://raw.githubusercontent.com/favortel/nginx_patch/master/nginx-1.14.0_spdy_h2.patch

#OKHTTP H2 头部动态压缩兼容补丁
#wget https://raw.githubusercontent.com/favortel/nginx_patch/master/fssnginx_1.14.0_dynamic_table_size.patch

#PCRE ZLIB等
#wget https://ftp.pcre.org/pub/pcre/pcre-8.42.tar.gz
#wget https://zlib.net/zlib-1.2.11.tar.gz

#tar -zxvf openssl-1.1.1.tar.gz
#tar -zxvf pcre-8.42.tar.gz
#tar -zxvf zlib-1.2.11.tar.gz
#tar -zxvf nginx-1.14.1.tar.gz

#cd nginx-1.14.1
#patch -p1 < ../fssnginx_1.14.0_dynamic_table_size.patch
#patch -p1 < ../nginx-1.14.0_spdy_h2.patch

./configure --prefix=/opt/itc/nginx --with-http_stub_status_module --with-http_realip_module --with-http_ssl_module --with-openssl=../openssl-1.1.1 --with-pcre=../pcre-8.42 --with-pcre-jit --with-zlib=../zlib-1.2.11 --with-http_v2_module --with-http_spdy_module
#make && make install

配置比较简单,就是ssl_protocols 增加TLSv1.3就好, ssl_ciphers 不用特意改动:

...
ssl_protocols               TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers                 ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers   on;
ssl_ecdh_curve              secp384r1;
...

比如本站,如果您是用chrome访问的是https协议,打开开发者工具-security,就能看到本站使用了TLS1.3了

apache 配置指令执行顺序

需要特别留意下,apache的配置是有顺序关系的,以下为先后:

  1. <Directory> (except regular expressions) and .htaccess done simultaneously (with .htaccess, if allowed, overriding <Directory>)
  2. <DirectoryMatch> (and <Directory ~>)
  3. <Files> and <FilesMatch> done simultaneously
  4. <Location> and <LocationMatch> done simultaneously

举个例子,执行顺序是 A > B > C > D > E

<Location />
E
</Location>

<Files f.html>
D
</Files>

<VirtualHost *>
<Directory /a/b>
B
</Directory>
</VirtualHost>

<DirectoryMatch “^.*b/”>
C
</DirectoryMatch>

<Directory /a/b>
A
</Directory>

 

一个危险的示例,在这个例子里边,其实deny 不生效,因为被后生效的 location 配置覆盖了

<Location />
Order deny,allow
Allow from all
</Location>

# Woops! This <Directory> section will have no effect
<Directory />
Order allow,deny
Allow from all
Deny from badguy.example.com
</Directory>

参考文档: http://www.who.int/manual/sections.html#mergin

proxy_cache behavier difference of nginx 1.6 and 1.7 above

nginx 1.6 cache objects depend on proxy_cache_key,such as

nginx 1.6的缓存对象取决于proxy_cache_key的设置,比如

proxy_cache_key $uri$is_args$args;

nginx 1.7 cache objects depend on proxy_cache_key and VARY Header

nginx 1.7的缓存对象取决于proxy_cache_key 和 服务器返回的VARY头的内容
Changes with nginx 1.7.7 28 Oct 2014


*) Change: now nginx takes into account the "Vary" header line in a
backend response while caching.

If the upstream server reponsed this header: “Vary: Accept-Encoding”, cache object of the same url may save many copys depend on the different request headers: “Accept-Encoding”, in nginx 1.7 or above versions.

假如,你的upstream服务器返回了这样的header: “Vary: Accept-Encoding”,

相同一个url的缓存对象会基于请求header里边的”Accept-Encoding”的不同值 保存为许多份不同的副本,这个特性起于nginx1.7 版本

In this case, purge cache may failed, cause you don’t how many cache objects with “Accept-Encoding” headers

在这种情况下,清理缓存通常会失败,因为你不知道有多少的缓存对象有着哪些对应的”Accept-Encoding” headers

You have 2 choices to fix this problem:

你有两种选择来对付这个问题:

1. Rewrite the reuqest header “Accept-Encoding” in the front proxy serve

在最前端的代理服务器 重写请求的header: “Accept-Encoding”

set $compress "none";
if ($http_accept_encoding ~* "gzip|deflate|compress") {set $compress "gzip,deflate";}
proxy_set_header Accept-Encoding $compress;

2. Ignore the response header “Vary” in the middle cache server

或者在中间层的cache服务器 忽略掉服务器返回的header “Vary”
proxy_ignore_headers  Vary;

在这里, “Vary: Accept-Encoding”, 只是一个非常常见的举例,实际上你的后端源站可能会返回特异的Vary Header.

nginx 1.14 与 okhttp H2不兼容的情况

新上线 nginx 1.14的时候,发现 许多使用okhttp的APP通讯异常了, 听包能发现服务器返回的很多小包被认为是非法数据忽略了

查了下nginx 官方资料,发现这是一个H2 动态压缩导致的不兼容情况, 需要使用补丁去绕开这个问题

问题的关键看起来是OKHTTP 库无法支持H2协议里边头部压缩的一个标准特性:  ​Dynamic Table Size 更新,导致了客户端和服务端协商失败

测试证明nginx 1.13.6 引入了这个特性并会导致okhttp3.4.1版本(我测试的版本)H2通讯异常, 可以通过以下链接获取到这些信息,应用这个补丁可以解决这个问题

https://trac.nginx.org/nginx/changeset/fbb683496705f91db4dad32b3ec2ec4ed75115c0/nginx

https://trac.nginx.org/nginx/ticket/1397

另外,在1.15.3版本,修正了这个问题,但是1.14序列没有同步跟进

nginx的正则 *

nginx 里边在server_name 或者 if的时候会用到一些简单的正则,实际上跟普通的正则会有些不同,简单举2个例子

1. server_name *.4os.org
它其实能匹配 test.www.4os.org, 它能匹配多级,这个需要注意下

2. 普通正则里边的 \s* 这里的* 表示 匹配前面的子表达式 零次 或 多次
而nginx 里边的* 一般都是表示存在的东西,这个是概念上的不同

nginx spdy patch for 1.14.0 1.13.12

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

postfix 内网发信

一般来说我们的linux服务器有内网发信的权限就可以了,所以需要对postfix进行简单的配置
修改 vim /etc/postfix/main.cf

myhostname = lookgod.sohu.com
mynetworks = 127.0.0.0/8
relayhost = transport_server_ip

顺便修改下 inet_protocols = ipv4 以免ipv6报错

就可以了

nginx add_header的一个陷阱

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: