记录一个通过Nginx转发UDP后无法获取真实IP问题

2,697 阅读5分钟

前言

大家好,我是MonSterCoder,此篇文章记录在一次线上环境中,我们利用Nginx作为服务端的负载均衡,过后我们发现无论哪个客户端往服务端发送UDP(SNMP)请求,获取到的IP地址都是来自Nginx服务器的IP地址,导致相关业务无法处理。下面就带大家复现整个过程。

环境准备

2台测试虚拟机,一台部署Nginx,另一台部署UDP(SNMP)服务端

IP以及端口信息

  • Nginx - 10.211.55.31:6705
  • UDP(SNMP)服务 - 10.211.55.32:6705
  • 本地客户端 - 10.211.55.2

部署UDP(SNMP)服务

新建一个SpringBoot项目

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.snmp4j</groupId>
    <artifactId>snmp4j</artifactId>
    <version>2.6.3</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

代码示例

参考了网上示例

@Slf4j
@Component
public class SnmpReceiver implements CommandResponder {

    private Snmp snmp;
    /**
     * 初始化snmp
     */
    private void init() throws Exception {
        ThreadPool threadPool = ThreadPool.create("SNMP_THREAD", 5);

        // 初始化多线程消息转发类
        MultiThreadedMessageDispatcher dispatcher = new MultiThreadedMessageDispatcher(threadPool, new MessageDispatcherImpl());
        Address listenAddress = GenericAddress.parse("10.211.55.32/6705"); // 本地IP与监听端口

        TransportMapping<?> transport = null;
        // 创建transportMapping,对TCP与UDP协议进行处理
        if (listenAddress instanceof UdpAddress) {
            transport = new DefaultUdpTransportMapping((UdpAddress) listenAddress);
        } else {
            transport = new DefaultTcpTransportMapping((TcpAddress) listenAddress);
        }

        // 创建snmp
        snmp = new Snmp(dispatcher, transport);
        // 其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv1());
        snmp.getMessageDispatcher().addMessageProcessingModel(new MPv2c());
        snmp.listen();
    }

    /**
     * 启动进程
     */
    public void run() {
        try {
            init();
            snmp.addCommandResponder(this);
            log.info("开始监听SNMP-Trap信息");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 当监听到消息时,会自动调用该方法
     */
    @Override
    public void processPdu(CommandResponderEvent respEvent) {
        if (respEvent == null || respEvent.getPDU() == null) {
            log.warn("未监听到消息!");
        } else if (respEvent.getPDU().getType() != PDU.TRAP) {
            log.warn("仅支持Trap消息!");
        } else {
            Vector<? extends VariableBinding> recVBs = respEvent.getPDU().getVariableBindings();
            log.info(String.format("Type TRAP   : %s", respEvent.getPDU().getType() == PDU.TRAP));
            log.info(String.format("Address     : %s", respEvent.getPeerAddress()));
            log.info(String.format("Community   : %s", new String(respEvent.getSecurityName())));
            log.info(String.format("Variable    : %s", recVBs));
        }

    }

}
@SpringBootApplication
public class UdpDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(UdpDemoApplication.class, args);
    }

    @Autowired
    private SnmpReceiver snmpReceiver;

    @Bean
    public ApplicationRunner startSnmpReceiver() {
        return args -> snmpReceiver.run();
    }
}

打包项目

mvn package -Dmaven.test.skip=true

部署项目

将打包好的UDPDemo-0.0.1-SNAPSHOT.jar 上传到虚拟机,执行java -jar UDPDemo-0.0.1-SNAPSHOT.jar启动项目

[root@centos-linux ~]# java -jar UDPDemo-0.0.1-SNAPSHOT.jar

. ____ _ __ _ _

/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

\\/ ___)| |_)| | | | | || (_| | ) ) ) )

' |____| .__|_| |_|_| |_\__, | / / / /

=========|_|==============|___/=/_/_/_/

:: Spring Boot :: (v2.7.0)

2022-06-09 15:39:12.432 INFO 7078 --- [ main] com.example.udpdemo.UdpDemoApplication : Starting UdpDemoApplication v0.0.1-SNAPSHOT using Java 1.8.0_2

42 on centos-linux.shared with PID 7078 (/root/UDPDemo-0.0.1-SNAPSHOT.jar started by root in /root)

2022-06-09 15:39:12.435 INFO 7078 --- [ main] com.example.udpdemo.UdpDemoApplication : No active profile set, falling back to 1 default profile: "def

ault"

2022-06-09 15:39:13.507 INFO 7078 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)

2022-06-09 15:39:13.523 INFO 7078 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]

2022-06-09 15:39:13.523 INFO 7078 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.63]

2022-06-09 15:39:13.603 INFO 7078 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext

2022-06-09 15:39:13.603 INFO 7078 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1090 m

s

2022-06-09 15:39:14.066 INFO 7078 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''

2022-06-09 15:39:14.076 INFO 7078 --- [ main] com.example.udpdemo.UdpDemoApplication : Started UdpDemoApplication in 1.995 seconds (JVM running for 2

.332)

2022-06-09 15:39:14.114 INFO 7078 --- [ main] com.example.udpdemo.SnmpReceiver : 开 始 监 听 SNMP-Trap信 息

关闭防火墙

[root@centos-linux ~]# systemctl stop firewalld

Nginx安装

本文以1.22.0版本为例

下载地址:nginx.org/en/download…

安装依赖

yum -y install gcc gcc-c++ pcre pcre-devel gd-devel openssl openssl-devel zlib zlib-devel

下载解压

[root@Nginx ~]# cd /opt
[root@Nginx opt]# wget http://nginx.org/download/nginx-1.22.0.tar.gz
[root@Nginx opt]# tar -xf nginx-1.22.0.tar.gz -C nginx

配置

[root@Nginx opt]# cd nginx
[root@Nginx nginx]# ./configure --with-stream

编译安装

[root@Nginx nginx]# make && make install

修改配置

[root@Nginx nginx]# cd /usr/local/nginx/conf
[root@Nginx conf]# vi nginx.conf

// 加入以下配置
stream {

    server {

        listen 6705 udp;

        proxy_pass 10.211.55.32:6704;

        proxy_responses 0;

    }

}

启动Nginx

[root@Nginx conf]# cd /usr/local/nginx/sbin
[root@Nginx sbin]# ./nginx -c /usr/local/nginx/conf/nginx.conf

关闭防火墙

[root@Nginx conf]# systemctl stop firewalld

本地10.211.55.2测试UDP报文

代码示例

public static void main(String[] args) throws IOException {
    Address targetAddress = GenericAddress.parse("udp:10.211.55.31/6705"); // Nginx地址
    TransportMapping<UdpAddress> transport = new DefaultUdpTransportMapping();
    Snmp snmp = new Snmp(transport);
    PDU pdu = new PDU();
    VariableBinding v = new VariableBinding();
    v.setOid(new OID("1.21.1.2.2.3.45.5.57567.3"));//随便写的oid
    v.setVariable(new OctetString("Snmp Trap V2 Test 123"));
    v.setOid(new OID("1.21.1.2.2.3.45.5.57567.4"));//随便写的oid
    v.setVariable(new OctetString("你好 Snmp Trap V2 Test 123"));
    pdu.add(v);
    pdu.setType(PDU.TRAP);

    // set target
    CommunityTarget target = new CommunityTarget();
    target.setCommunity(new OctetString("public"));
    target.setAddress(targetAddress);

    // retry times when commuication error
    target.setRetries(2);
    target.setTimeout(1500);
    target.setVersion(SnmpConstants.version2c);
    // send pdu, return response
    snmp.send(pdu, target);
}

返回结果

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Type TRAP : true

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Address : 10.211.55.31/45265

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Community : public

2022-06-08 21:52:52.574 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Variable : [1.21.1.2.2.3.45.5.57567.4 = e4:bd:a0:e5:a5:bd:

20:53:6e:6d:70:20:54:72:61:70:20:56:32:20:54:65:73:74:20:31:32:33]

我们发现Address输出的IP地址为Nginx服务器的IP地址,并不是我们客户端的IP地址,为此,我们需要更改Nginx一处配置

更改Nginx配置文件

[root@Nginx sbin]# cd /usr/local/nginx/conf
[root@Nginx conf]# vi nginx.conf

// 加入以下配置
stream {

    server {

        listen 6705 udp; // Nginx监听端口

        proxy_pass 10.211.55.31:6704; 后端UDP代理地址

        proxy_responses 0;

        proxy_bind $remote_addr transparent; // 加入此配置, 后端服务器会接受到来自客户端IP的请求

    }

}

重启Nginx

[root@Nginx conf]# cd /usr/local/nginx/sbin
[root@Nginx sbin]# ./nginx -s reload -c /usr/local/nginx/conf/nginx.conf

重新发起测试

返回结果

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Type TRAP : true

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Address : 10.211.55.2/60439

2022-06-08 21:52:52.573 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Community : public

2022-06-08 21:52:52.574 INFO 70411 --- [ SNMP_THREAD.0] com.example.udpdemo.SnmpReceiver : Variable : [1.21.1.2.2.3.45.5.57567.4 = e4:bd:a0:e5:a5:bd:

20:53:6e:6d:70:20:54:72:61:70:20:56:32:20:54:65:73:74:20:31:32:33]

至此我们发现已经能够正常的获取到客户端的IP,通过Nginx转发UDP后无法获取真实IP问题也完美解决。