代理模式下获取客户真实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

相关推荐
枫叶_v15 分钟前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
杜杜的man1 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
java小吕布1 小时前
Java中Properties的使用详解
java·开发语言·后端
hzyyyyyyyu1 小时前
隧道技术-tcp封装icmp出网
网络·网络协议·tcp/ip
2401_857610032 小时前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
杨哥带你写代码4 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_4 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
清尘沐歌5 小时前
有什么好用的 WebSocket 测试工具吗?
websocket·网络协议·测试工具
背水5 小时前
初识Spring
java·后端·spring
清尘沐歌5 小时前
有什么好用的 WebSocket 调试工具吗?
网络·websocket·网络协议