代理模式下获取客户真实IP

我们经常会在我们后端服务前加一层代理去做负载均衡或认证,比较有名的就是apisix。但是,这样会出现一些问题,比如说后端服务无法获取到客户真实的ip,显示的都是代理的ip,对于业务展示会有问题。对于7层的后端服务还好,可以在载荷里将客户的出口ip带上来,但是四层和三层就不太容易了,目前主流的有下面两种解决方案:

1. 原理

1.1. L4层获取---TOA插件

下载toa代码,编译出新的linux内核模块,这样tcp/ip协议栈可以将客户真实IP插入tcp option中,具体位置如下图所示:

这样以来,业务四层端口接入后,节点和源站经过三次握手,在最后一个ACK数据包的TCP Option中插入了源端口号和源IP等信息,共占8个字节。

1.2. L3层获取---透明转发

主要依赖nginx透明代理+iptables mark+route

核心思想是apisix使用获取到的客户端ip来建立apisix与上游服务直接的tcp连接。

考虑1.2实现最简单,也最稳定,以1.2方案进行实验。

2. 测试环境搭建

本地环境如下:

scss 复制代码
tcp                                       tcp
client(192.168.134.34) <--------->  apisix(192.168.134.31:19001)  <---------> server(192.168.134.35:19000)

server 192.168.134.35:19000

采用python起一个tcp服务,监听19000,负责回显客户端发来的tcp载荷

python 复制代码
#coding:utf-8

from socket import *
from time import ctime

print("=====================时间戳TCP服务器=====================");

HOST = '0.0.0.0'  #主机号为空白表示可以使用任何可用的地址。
PORT = 19000  #端口号
BUFSIZ = 1024  #接收数据缓冲大小
ADDR = (HOST, PORT)

tcpSerSock = socket(AF_INET, SOCK_STREAM) #创建TCP服务器套接字
tcpSerSock.bind(ADDR)  #套接字与地址绑定
tcpSerSock.listen(5) #监听连接,同时连接请求的最大数目

while True:
    print('等待客户端的连接...')
    tcpCliSock, addr = tcpSerSock.accept()   #接收客户端连接请求
    print('取得连接:', addr)

    while True:
        data = tcpCliSock.recv(BUFSIZ)  #连续接收指定字节的数据,接收到的是字节数组
        print('rcv:', data)
        if not data:  #如果数据空白,则表示客户端退出,所以退出接收
           break
        if len(data) == 0:
           continue
        #tcpCliSock.send('[%s] %s' % (bytes(ctime(), 'utf-8'), data))

    tcpCliSock.close()  #关闭与客户端的连接
tcpSerSock.close()  #关闭服务器socket

client 192.168.134.34

采用python写一个tcp客户端,目的ip和端口写为apisix代理端口

ini 复制代码
#coding:utf-8

from socket import *

print("=====================TCP客户端=====================");

HOST = '192.168.134.31'  #代理ip地址
PORT = 19001  #代理通信端口号
BUFSIZ = 1024  #接收数据缓冲大小
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)  #创建客户端套接字
tcpCliSock.connect(ADDR)  #发起TCP连接

while True:
    data = input('> ')   #接收用户输入
    if not data:  #如果用户输入为空,直接回车就会发送"",""就是代表false
        break
    tcpCliSock.send(bytes(data, 'utf-8'))   #客户端发送消息,必须发送字节数组
    data = tcpCliSock.recv(BUFSIZ)   #接收回应消息,接收到的是字节数组
    if not data:   #如果接收服务器信息失败,或没有消息回应
        break
    print(data.decode('utf-8'))  #打印回应消息,或者str(data,"utf-8")

tcpCliSock.close() #关闭客户端socket

apisix 192.168.134.31

github上下载最新的apisix-docker-master,解压后修改example中docker-compose.yml内的apisix服务为network_mode: host,即使用主机网络栈。

docker-compose -p docker-apisix up -d

可以看到服务启动正常:

3. 实现

我们把问题拆分成以下两点:

  1. apisix怎么配置可以生成自定义的nginx配置?
  2. nginx怎么配置透明代理?

vi ./apisix_conf/config.yaml

一定要配置root,因为SOCKET的IP_TRANSPARENT模式改包需要root权限。

进到容器里可以看到已经生成了自定义的ngnix配置,cat ./conf/nginx.conf:

但这样还不行,因为apache/apisix:3.2.1-debian的dokcerfile是以自建用户apisix启动的,所以即使配置了user root也会被忽略掉:

我采用多年的套壳大法改一下dockerfile:

重新compose down + up,我们就可以登录客户主机试一下IP透明转发了:

不出意外的话,肯定是出意外了:

我们用抓包大法看看啥情况,代理主机+客户端+服务端同时抓:

原来是代理和客户端握手后,代理以客户端源ip发起了新的请求到了服务端,然后服务端收到了报文后,由于目的ip是客户端,所以报文直接转发到了客户端,客户端很懵逼,会话表里没有,直接对服务端无情RST。

好了,这也恰恰证明,我们的透明代理是配置是正确的,只不过需要控制一下报文流转,类似于下图,服务端的报文重新指回代理,然后还要控制代理收到的报文不要直接转发,而要自己处理:

如何控制报文轮转?

  1. server回代理:
sql 复制代码
iptables -t mangle -A OUTPUT -p tcp --src 192.168.134.35 --sport 19000:19005 -j MARK --set-xmark 0x1/0xffffffff
ip route add default via 192.168.134.31 table 100
ip rule add fwmark 1 lookup 100

对于从 IP 地址 192.168.134.35 发出的 TCP 流量,且源端口在 19000 到 19005 范围内的流量,进行标记。被标记的流量会走自定义100的路由表发给代理主机(192.168.134.31)

  1. 代理自己处理ngnix报文
sql 复制代码
iptables -t mangle -A PREROUTING -p tcp --src 192.168.134.35 --sport 19000:19005 -j MARK --set-xmark 0x1/0xffffffff
ip route add local 0.0.0.0/0 dev lo table 100
ip rule add fwmark 1 lookup 100

对于从 IP 地址 192.168.134.35 发过来的 TCP 流量转发进行标记,被标记的流量会走自定义100的路由表而不是本地路由表转发,它会直接路由回本地,由于本地有apisix和server的会话信息,因此协议栈会将报文转发给apisix。

这样server就获得了真实的客户ip:

之前我试过apisix的briage模式部署,但是发现代理发的报文并没有到达服务端,只发到网桥的虚拟网卡就丢掉了,没有抓发给eth0,怀疑与docker的iptablses策略有关,先不研究了,非要搞在搞。

Reference

www.duidaima.com/Group/Topic...

apisix.apache.org/zh/docs/api...

cloud.tencent.com/docum

相关推荐
无名之逆4 分钟前
Hyperlane:高性能 Rust HTTP 服务器框架评测
服务器·开发语言·windows·后端·http·rust
褚翾澜22 分钟前
Bash语言的社区交流
开发语言·后端·golang
Asthenia041230 分钟前
JUC:CompletableFuture 详细用法讲解
后端
freejackman1 小时前
MySQL 基础入门
数据库·后端·sql·mysql
圈圈编码1 小时前
WebSocket
java·网络·spring boot·websocket·网络协议·spring
Asthenia04121 小时前
一名实习生的复盘:技术与规划的经验教训
后端
Asthenia04121 小时前
从面试问题看端口连通性:Ping、TCP/UDP与业务实践
后端
程序员一诺1 小时前
【爬虫开发】爬虫开发从0到1全知识教程第14篇:scrapy爬虫框架,介绍【附代码文档】
后端·爬虫·python·数据
苏格拉没有底_coder1 小时前
【Easylive】视频在线人数统计系统实现详解 & WebSocket 及其在在线人数统计中的应用
websocket·网络协议
Asthenia04122 小时前
Spring事件机制:微服务架构下的子服务内部解耦合/多场景代码分析
后端