Netty获取客户端真实IP(使用Nginx代理TCP)

原文:nginx代理 netty tcp服务端获取客户端真实IP_樱木花道VS康的博客-CSDN博客_nginx代理netty


场景描述:

netty tcp 服务端与客户端进行交互,之前采用服务器直连的方式,即:

客户端 -----------> 服务端(tcp)

此时服务端可以通过 ctx.channel().remoteAddress() 获取客户端IP进行相关的业务使用。

但是后续随着客户端增加,服务端就上来了,此时使用nginx代理的方式增加netty服务端数量一次来处理更多的连接数。

客户端 -----------> nginx  --------------->服务端(tcp,多台部署)

这样就导致了nginx代理后 ctx.channel().remoteAddress()获取的是nginx代理服务器的地址,无法获取客户端真实IP。

解决办法:

1、nginx

先说nginx,nginx代理tcp需要安装stream模块,此时涉及到一个代理协议proxy protocol的使用。

推荐博文: https://www.jianshu.com/p/cc8d592582c9 大家也可以找资料更深入的理解。

简而言之就是:nginx代理tcp(四层tcp代理)时增加一个头信息,其中包含了客户端IP的信息,后端则想办法从此头报文中获取IP。

代理协议proxy protocol的使用场景(都是为了获取客户端信息):

4层tcp代理 转 7层http代理

4层tcp代理 转 4层tcp代理 (多层tcp代理)

这样的场景下需要使用代理协议进行客户端信息的传递。

我现在要解决的就是简单的一层nginx(tcp代理)的场景。

配置示例:

stream {
    log_format proxy '$remote_addr [$time_local] '
                 '$protocol $status $bytes_sent $bytes_received '
                 '$session_time "$upstream_addr" '
                 '"$upstream_bytes_sent" "$upstream_bytes_received" "$upstream_connect_time"';        
         
    access_log logs/tcp.log proxy;
    
    upstream netty-tcp {
        server 0.0.0.0:8899 weight=5;
    }
    
    server {
    
        listen 18899 ssl;
        
        ssl_certificate /root/nginx/cert/3565893_xxx.pem;
        ssl_certificate_key /root/nginx/cert/3565893_xxx.key;
        
        ssl_protocols  TLSv1 TLSv1.1 TLSv1.2;
        
        ssl_ciphers    HIGH:!aNULL:!MD5;

        proxy_pass netty-tcp;

        proxy_protocol on;        #仅此一句重点
    }
}

重点是开启代理:

proxy_protocol on;  

更多配置请自行查看 Nginx stream模块简述_微学苑

2、netty服务端

netty服务端就是要拿到这段代理头报文进行IP获取。

备注:此段报文只会在首次建立连接时发送一次,建立连接后不会发送,断开连接再次连接时仍会发送。

通过博文:https://www.jianshu.com/p/cc8d592582c9 我们大概得知这段报文的格式:

PROXY TCP4 101.106.236.66 192.168.0.150 12646 5683\r\n

思路:以tcp为例,您的编码、解码方式,客户端、服务端肯定是保持一致的,而我要做的是自定义一个解码器(仅服务端),然后对这段PROXY报文进行处理,对于没有包含这段报文的tcp流不做处理放行即可。

代码如下:

自定义解码器:

/**
 * @Description nginx代理netty tcp服务端负载均衡,nginx stream要打开 proxy_protocol on; 配置
 */
public class DecodeProxy extends ByteToMessageDecoder {
 
    private Logger logger = LoggerFactory.getLogger(DecodeProxy.class);
 
    /**
     * 保存客户端IP
     */
    public static AttributeKey<String> key = AttributeKey.valueOf("IP");
 
    /**
     * decode() 会根据接收的数据,被调用多次,直到确定没有新的元素添加到list,
     * 或者是 ByteBuf 没有更多的可读字节为止。
     * 如果 list 不为空,就会将 list 的内容传递给下一个 handler
     * @param ctx 上下文对象
     * @param byteBuf 入站后的 ByteBuf
     * @param out 将解码后的数据传递给下一个 handler
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
 
        /*消息打印--------------------------*/
        byte[] bytes = printSz(byteBuf);
        String message = new String(bytes, Charset.forName("UTF-8"));
        //logger.info("从客户端收到的字符串:" + message);
        /*消息打印--------------------------*/
 
        if(bytes.length > 0){
 
            //判断是否有代理
            if(message.indexOf("PROXY") != -1){
                //PROXY TCP4 101.106.236.66 192.168.0.150 12646 5683\r\n
                logger.info("PROXY MSG: " + message.substring(0,message.length()-2));
                if(message.indexOf("\n") != -1){
                    String[] str =  message.split("\n")[0].split(" ");
                    logger.info("Real Client IP: " + str[2]);
                    Attribute<String> channelAttr = ctx.channel().attr(key);
                    //基于channel的属性
                    if(null == channelAttr.get()){
                        channelAttr.set(str[2]);
                    }
                }
 
                //清空数据,重要不能省略
                byteBuf.clear();
            }
 
            if(byteBuf.readableBytes() > 0){
                //logger.info("out add!!!");
                out.add(byteBuf.readBytes(byteBuf.readableBytes()));
            }
        }
    }
 
 
    /**
     * 打印byte数组
     * @param newBuf
     */
    public byte[] printSz(ByteBuf newBuf){
        ByteBuf copy = newBuf.copy();
        byte[] bytes = new byte[copy.readableBytes()];
        copy.readBytes(bytes);
        //logger.info("字节数组打印:" + Arrays.toString(bytes));
        return bytes;
    }
}

添加自定义解码器:

  pipeline.addLast("decoder",new DecodeProxy());//增加这个自定义的解码器
  pipeline.addLast("frameDecoder",new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, 0, 2));
  pipeline.addLast("frameEncoder", new LengthFieldPrepender(2, false));

说明:这个自定义的解码器是nginx代理后,nginx与服务端建立连接时使用代理协议发送的一段包含客户端信息的报文,即上面的:PROXY TCP4 101.106.236.66 192.168.0.150 12646 5683\r\n,而自定义解码器就是为了解析到这个报文进行IP的获取,其他的报文放行不做任何处理。

知识点:AttributeKey 推荐博客:netty之AttributeKey的应用_zcf9916的博客-CSDN博客

简单说就是:类似于tomcat服务器的session一样,保存数据绑定自己的上下文,多客户端连接数据隔离。(我理解不深)

这样就可以通过:

Attribute<String> channelAttr = ctx.channel().attr(DecodeProxy.key);
//基于channel的属性
if(null != channelAttr.get()){
   logger.info("IP地址--------------- :" + channelAttr.get());
   clientIp = channelAttr.get();
}

获取到客户端IP地址了。

3、总结:

1、nginx 代理协议proxy protocol

2、netty AttributeKey对象的使用

写在最后,感谢两个大神的博客:

https://www.jianshu.com/p/cc8d592582c9

netty之AttributeKey的应用_zcf9916的博客-CSDN博客

使用Netty获取客户端真实IP地址时,需要考虑到一些因素。由于Netty是基于TCP/IP协议栈开发的,无法直接从协议层获取客户端真实IP地址。 但是,我们可以借助一些辅助工具来获取客户端真实IP地址。以下是一种常见的方法: 一、使用HttpHeaders解析请求头 1. 获取Netty中的请求对象HttpRequest对象。 2. 通过HttpRequest对象的headers()方法获取到请求头的所有参数。 3. 根据请求头中的参数名获取真实IP地址的值,一般为"X-Forwarded-For"或"X-Real-IP"。 4. 如果上述参数不为空,则说明已经有经过代理服务器处理的真实IP地址。 二、使用ChannelHandlerContext获取远程地址 1. 在Netty的Handler中,通过重写channelRead(ChannelHandlerContext ctx, Object msg)方法,获取到ChannelHandlerContext对象。 2. 通过ctx对象的channel()方法获取到channel对象。 3. 通过channel对象的remoteAddress()方法获取到远程连接的地址。 4. remoteAddress()方法返回的是InetSocketAddress对象,通过调用其getAddress()方法获取真实IP地址。 请注意,以上方法仅为通用实现方式,在实际应用中还需根据具体的网络环境和业务需求进行适配和修改。 总结起来,通过Netty获取真实IP地址主要是通过解析请求头或通过ChannelHandlerContext对象获取到远程连接的地址来实现的。由于具体应用场景和网络环境的差异,可能需要根据实际情况做出相应调整和适配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值