0x01 先查个问题
测试环境微信支付通道提示网络环境未能通过安全验证, 请稍后再试, 出现这种情况一般首要 想到可能是双方网络交互中微信方验参与我们出现不一致, 翻了下手册确定是这类问题开始排查环节
可能获取真实 IP 方式错误
- getenv('HTTP_CLIENT_IP')
- getenv('HTTP_X_FORWARDED_FOR')
- getenv('REMOTE_ADDR')
- filter_var($remote_ip, FILTER_VALIDATE_IP)
已经依次获取并过滤
固程序没有任何问题, 往上发散
是否反向代理
经过反向代理后, 由于在客户端和 web 服务器之间增加了中间层, 因此 Web 服务器无法直接拿到客户端的 ip, 只能通过 $remote_addr 变量拿到的将是反向代理服务器的 ip 地址, 检查不存在此类问题, 再往上, 擅长网络通信工程的同学表示绝不认输
可能 NAT 分配出口 IP, 或负载均衡服务分发出现异常
先拿到我本地内网外网 IP 方便之后问题排查
- # 本机 IP
- ifconfig | grep -A 1 "en" | grep broadcast | cut -d " " -f 2
- # 外网 IP
- curl --silent http://icanhazip.com
检查与 80 端口建立连接目标都有谁
netstat -tn|grep 80|akw '{print $5}'|awk -F '{print $1}' | grep [本地 IP]
这里出现问题, 竟然没有我的 IP, 再以 nginx $remote_addr 拿到的 IP 作为参考, 这是 nginx 最后一次握手的 IP,$remote_addr = 10.168.0.0/16 段
在 nginx 处打印 $remote_addr, 并在 server_name 添加当前机器 ip, 分别以负载均衡 IP 与本地 IP 做测试, 最终确定问题出现在负载均衡服务器出现异常
0x02 LNMP 栈拿真实 IP
LNMP 栈内 PHP 所有获得到的 TCP 操作信息都是由前面 Nginx 通过 fastcgi 传递给它的, 就比如 $_SERVER['REMOTE_ADDR']由 include fastcgi.conf; 引进, 其等于 nginx 的 $remote_addr
Nginx 中的几个变量:
$remote_addr
代表客户端的 IP, 但它的值不是由客户端提供的, 而是服务端根据客户端的 ip 指定的, icanhazip 的原理也是这样, 当你的浏览器访问某个网站时, 假设中间没有任何代理, 那么网站的 Web 服务器就会把 remote_addr 设为你在公网暴露的 IP, 如果你用了某个代理, 那么你的浏览器会先访问这个代理, 然后再由这个代理转发到网站, 这样 Web 服务器就会把 remote_addr 设为这台代理机器的 IP, 除非代理将你的 IP 附在请求 header 中一起转交给 Web 服务器.
$proxy_add_x_forwarded_for
$proxy_add_x_forwarded_for 变量包含客户端请求头中的 "X-Forwarded-For", 与 $remote_addr 两部分, 他们之间用逗号分开. X-Forwarded-For(简称 XFF),X-Forwarded-For 是一个 HTTP 扩展头部. RFC 2616 https://tools.ietf.org/html/rfc2616 协议并没有对它的定义, 它最开始是由 Squid 这个缓存代理软件引入, 用来表示 HTTP 请求端真实 IP. 如今它已经成为事实上的标准, 被各大 HTTP 代理, 负载均衡等转发服务广泛使用, 并被写入 RFC 7239 https://tools.ietf.org/html/rfc7239 (Forwarded HTTP Extension` 标准之中.
$proxy_set_header
已在排查问题中说明, 可设置代理后 header
- proxy_set_header Host $http_host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
X-Real-IP
一般比如 X-Real-IP 这一个自定义头部字段, 通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP, 这个设备可能是其他代理, 也可能是真正的请求端, 这个要看经过代理的层级次数或是是否始终将真实 IP 一路传下来.(牢记: 任何客户端传上来的东西都是不可信的)
当多层代理或使用 CDN 时, 如果代理服务器不把用户的真实 IP 传递下去, 那么服务器将永远不可能获取到用户的真实 IP.
0x03 用户的真实 IP 从何而来
宽带供应商提供独立 IP 比如家里电信宽带上网, 电信给分配了公网 ip, 那么一个请求经过的 ip 路径如下:
这种情况下, 119.147.19.234 会把得到的 116.1.2.3 附加到头信息中传给 10.168.0.0/32, 因此这种情况下, 我们取得的用户 ip 则为: 116.1.2.3. 如果 119.110.0.0/16 没有把 116.1.2.3 附加到头信息中传给业务服务器, 业务服务器就只能取上上一级 ip 地址
宽带供应商不能提供独立 IP
宽带提供商没有足够的公网 ip, 分配的是个内网 ip, 比如长宽等小的 isp. 请求路径则可能如下:
这种情况下得到的用户 ip, 就是 211.162.78.1. 这种情况下, 就可能出现一个 ip 对应有数十上百个用户的情况了
手机 2g 上网
网络提供商没法直接提供 ip 给单个用户终端, 以中国移动 cmwap 上网为例, 因此请求路径可能为:
这种情况下得到的用户 ip, 就是 202.96.75.1.2008 年的时候整个广东联通就三个手机上网的公网 ip, 因此这种情况下, 同一 ip 出现数十万用户也是正常的.
有几万或数十万员工的公司
这种也会出现来自同一 ip 的超多用户, 可能达到几万人, 但出口 IP 可能就那么几个.
0x04 NAT [Network Address Translation]
中文意思是网络地址转换, 它允许一个整体机构以一个公用 IP 地址出现在 Internet 上.
NAT 在 OSI 参考模型的网络层 (第 3 层), 它是一种把内部私有网络地址 (IP 地址) 翻译成合法网络 IP 地址的技术. NAT 可以让那些使用私有地址的内部网络连接到 Internet 或其它 IP 网络上. NAT 路由器在将内部网络的数据包发送到公用网络时, 在 IP 包的报头把私有地址转换成合法的 IP 地址.
RFC1918 https://tools.ietf.org/html/rfc1918 规定了三块专有的地址, 作为私有的内部组网使用
A 类: 10.0.0.0 - 10.255.255.255 10.0.0.0/8
B 类: 172.16.0.0 - 172.31.255.255 172.16.0.0/12
C 类: 192.168.0.0 - 192.168.255.255 192.168.0.0/16
这三块私有地址本身是可路由的, 只是公网上的路由器不会转发这三块私有地址的流量; 当一个公司内部配置了这些私有地址后, 内部的计算机在和外网通信时, 公司的边界路由会通过 NAT 或者 PAT 技术, 将内部的私有地址转换成外网 IP, 外部看到的源地址是公司边界路由转换过的公网 IP 地址, 这在某种意义上也增加了内部网络的安全性
这个过程是通过 NAT 中的本地址与全局地址映射条目来实现的, 所以事先要在 NAT 路由器上配置这样的映射条目.
通过这种方式一个公网 IP 底下可以发私有的 IP 地址.
0x05 IPV6 来了?
写这篇文章的时候看到有个推送, 表示阿里全面应用 IPV6, 这件事的意义还挺重大的
我们知道, 一段 IPv4 标准的 IP 地址, 一共由 4 X 8 = 32 位二进制数字组成, 理论上存在 2^32 个 IP 地址. 等于 4,294,967,296 , 42 亿多个 IPv4 的地址.
参考世界互联网用户统计 https://www.internetworldstats.com/stats.htm 报告, 全球现在大概有 4,208,571,287 人在上网, 也就是说已经快到 ipv4 地址设计的最大 IP 数了
不过不用担心, 前面提到的 NAT, 让 IPv4 公网 IP 哪怕用完了也能凑合过.
到了 IPv6 , 相比 IPv4 最大的提升, 就是位数大大增加, 变成了 8 个 4 位的十六进制数字. 也就是说有 2^128 个 IPv6 地址. 地球上的每粒沙子都分一个也管够
存储 2^128 字节理论上什么概念呢, 在当今的量子水平下, 假设计算设备能够操作在原子一级, 每公斤质量可存储大约 10 的 25 次方 bits, 存储 2 的 128 次方的字节大约需要 272 trillion = 2720000 亿公斤.
最后, 周末愉快, 北京联通已经支持 ipv6 了, 我在望京测试, 可以拿到 ipv6 地址
来源: https://juejin.im/post/5c0b91a6e51d451d8d69c54c