分类 技术 下的文章

remote_addr及X-Forwarded-For及用户真实IP及内网安全

先缕一下背景,计算机网络,连在网络中的机器都有一个(或多个)IP地址,提供Web服务的机器,常有一个便于人们记忆的域名,通过DNS解析到一个(或多个)IP地址。有的网站流量大,需要不止一台机器提供服务,但域名只有一个,只能指向一台机器,怎么办呢?

代理服务器负载均衡(简称LB),提升静态网页的访问速度,增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性

互联网产品做大了,再做它个开放平台,搞完用户,搞合作伙伴。对外提供内网服务,开放平台服务器(简称OP)实现认证,代理调用内网服务。
以下,OP、LB、Server均为Nginx

remote_addr_proxy.png

Web服务器,一些场景,根据来源IP地址,决定服务与否。若是内网接口,只服务内网机器,拒绝服务来自外网的请求。
遇到的问题是,当client c通过OP请求内网接口时,Server解析客户端IP为client c的IP,其为外网IP 遂拒绝了服务。此时,我们希望Server拿到的客户端IP为OP的内网IP,这样就能正常为OP代理的客户服务了。怎么做呢?

HTTP协议虽然定义了Client-Ip请求头,但不是安全的,服务器的做法是取TCP连接的对端IP作为client ip,在Nginx中,解析为变量$remote_addr,不架设代理、负载均衡时,直接拿来用即可。但当在Server前代理时,应利用Nginx的ngx_http_realip_module中的real_ip_header指令,指定用哪个请求header的值来替换默认的$remote_addr。

假设
client a IP:12.34.56.78
OP IP: 34.56.78.90 及内网IP 10.22.33.44
LB IP: 10.23.34.45
这时,我们考察 X-Forwarded-For 的定义及格式,对于上图Server接收来自client c的请求时,X-Forwarded-For首先由OP设置为client c的IP,即

proxy_set_header X-Forwarded-For $remote_addr;

LB接收此header时,应再次设置

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

此时,到达Server的信息应该是:
$remote_addr: 10.23.34.45
X-Forwarded-For: 12.34.56.78,10.22.33.44

注意,Server知道Load Balance的存在,所以不关心$remote_addr的默认值,关心的是真正的请求IP,此时可设置
real_ip_header

set_real_ip_from  10.0.0.0/8;
real_ip_header    X-Forwarded-For;
real_ip_recursive off;

关于real_ip_recursive指令,官方文档:

If recursive search is disabled, the original client address that
matches one of the trusted addresses is replaced by the last address
sent in the request header field defined by the real_ip_header
directive. If recursive search is enabled, the original client address
that matches one of the trusted addresses is replaced by the last
non-trusted address sent in the request header field.

设置off时,Server得到的$remote_addr为10.22.33.44 即开放平台的IP,正常服务!

注:RFC 7239 定义了一个新的 Forwarded header,更完备地组织这些信息。

位操作

位操作高效解决问题

其中,
Sum of Two Integers
Use ^ and & to add two integers

int getSum(int a, int b) {
    return b==0? a:getSum(a^b, (a&b)<<1); //be careful about the terminating condition;
}

用^及& 实现 加法
看了好一会儿,才看懂
两个位串相加,结果可由两部分组成:不同的位 与 相同的位
不同的位,用^取得
相同的位,相加并进位,先&运算,再左移1位
此两部分相加,即得结果,递归下去即可。

注意边界情况,整型通常4字节32位,不断左移终使b为0,结束递归。
对于python/php等脚本语言,实现了非常大的整型范围,这样写法则不成了,需用mask限制32位

class Solution(object):
    def getSum(self, a, b):
        """
        :type a: int
        :type b: int
        :rtype: int
        """
        # 32 bits integer max
        MAX = 0x7FFFFFFF
        # 32 bits interger min
        MIN = 0x80000000
        # mask to get last 32 bits
        mask = 0xFFFFFFFF
        while b != 0:
            # ^ get different bits and & gets double 1s, << moves carry
            a, b = (a ^ b) & mask, ((a & b) << 1) & mask
        # if a is negative, get a's 32 bits complement positive first
        # then get 32-bit positive's Python complement negative
        return a if a <= MAX else ~(a ^ mask)

如上,Python3整型是无界的(无限大),想限定整型上限,如何能让其如C/Java一样表示补码?
为了理解最后一行代码的含义,我们可以假设场景,然后确定目的是什么。
假设C语言int占4位,python语言int占8位,mask为0xF
举例,我们得到的a若为 0b0101,它被预期正常解释,算法结束;若a为0b1011,根据此算法的隐含条件,我们想让它被解释成负数,就得让它符号扩展到 0b11111011

~(a ^ mask)

0b1011 => 0b0100 => 0b11111011

应用程序语言(PHP)导出csv文件乱码问题及解决

给运营同学提供数据支持,需求是导出数据文件,通常就是Excel能打开的、通用的CSV逗号分隔符文件。

首先导出 无BOM头UTF-8编码的标准csv格式文件,但用Excel打开此csv乱码。
此时考虑,能否手动指定Excel打开文件的编码:
文件-导入-CSV,指定UTF8编码、 ,分隔符 及 " qualifier,不乱码了,但出现 字段内容的换行符以外起作用,也就是CSV的qualifier解析失效。

没能解决,遂换个思路,分析Excel本身导出的utf8编码的csv文件
csv-utf8-bom.png
赫然写着With BOM,丫生成的utf8文件带BOM头!(业界对于UTF8文件,不带BOM头的做法更为标准,微软仍在依赖BOM)
PHP生成csv时,先输出BOM头,导出的csv文件,用Excel直接打开,完美解决乱码问题!

具体代码(Stackoverflow,maybe需翻墙):
PHP导出csv
PHP输出BOM头:

echo "\xEF\xBB\xBF";

题外话:
解决这个问题时,遇到另一个小问题,PHP的指令结束标志 ?>,在include及require等文件引入时,产生额外的空白符见:Note,对一些场景导致bug。
比如此文,在输出BOM头前 输出了空白字符,将导致多数软件无法正常识别带BOM头的文件。

科普科学,便捷上网

鉴于很多朋友不大清楚科学上网的原理,这里简单介绍一下。
大陆墙是为了祖国社会稳定,使大众免受外来不良思想的影响而建立的。
墙外有一些网站,我们没办法访问,尽管他们的产品很棒。
kexueshangwang1.png
计算机网络中,有一种架构叫做代理。
你直接访问不了的,可以交给能访问得了的人(软件)帮你访问,再转达给你,问题解决!
上图的Shadowsocks(以下简称ss)便是你的帮手,它可以代理你的网络请求,使你能够Google一下。

下面说一下技术逻辑:

  1. ss提供网络代理服务,同时配置系统网络代理。
  2. 当我们用浏览器打开一个网址A时,浏览器会读取 系统的网络配置 或 浏览器自身的网络配置,从而得知,网址A应该直接访问 还是 通过代理来访问。
  3. 用已确认出的方式访问吧
    kexueshangwang2.png

完。

对PHP连接MySQL和其他数据库方式的理解

PHP/MySQL是主流的应用开发搭配方式,印象中PHP支持使用很多种数据库,而且MySQL就有mysql、mysqli、pdo三种API可用。本文旨在从PHP数据库访问设计的思路为轴,简单的捋清这些关系。

PHP手册中《数据库扩展》一章有两部分:数据库抽象层、针对各数据库系统对应的扩展,已经是足够的说明了,要更深刻的理解可去研读。

首先,直接的想法是,PHP应该为不同的数据库实现不同的数据库抽象层,调用客户端库API来使用DBMS。如下图:
1.png

然后,考虑这个场景:当PHP应用写好后,环境数据库不同时,就要用对应的数据库抽象层重写项目所有数据库相关代码。所以能否把访问抽象出来,用不同数据库时,只要切换不同实现就ok了。如下图:
2.png

最后,主流选用MySQL当然是既可以使用mysql、mysqli两种数据库抽象层,又可以使用PDO数据访问抽象层(须安装PDO_MYSQL扩展实现PDO接口)。如下图:
3.png

当然任何一个数据库抽象层,都要在PHP编译时都要指定数据库的客户端库以使用数据库服务器。以MySQL为例,推荐用mysqlnd库,配置项:--with-mysqli=mysqlnd --with-pdo-mysql=mysqlnd --with-mysql=mysqlnd

现在是不是清晰些了呢?