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

相关推荐
刘大辉在路上3 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
Aileen_0v04 小时前
【AI驱动的数据结构:包装类的艺术与科学】
linux·数据结构·人工智能·笔记·网络协议·tcp/ip·whisper
花鱼白羊5 小时前
TCP Vegas拥塞控制算法——baseRtt 和 minRtt的区别
服务器·网络协议·tcp/ip
追逐时光者5 小时前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~5 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581366 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳6 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾6 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭7 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc