服务端通过noa接口获取源ip示例程序

TCP-Java

tcp相关说明

只有裸金属后端,或者IPV6 LB才需要适配,如果是裸金属后端请先根据指引安装noa模块
以下示例,在jdk8,jdk11可以确保正常获取,其他版本的jdk如有问题请联系【网易人员】

客户端IP为IPv4场景

该示例仅支持获取客户端为IPv4的场景

Java代码

import java.io.*;
import java.net.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
​
public class Main {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000); // 使用8000端口while (true) {
            final Socket socket = serverSocket.accept();
​
            // 创建一个新线程来处理连接
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                        System.out.println("New connection from " + socket.getInetAddress().getHostAddress());
                        System.out.println("local address: " + socket.getLocalAddress().getHostAddress());

                        String line;
                        while ((line = reader.readLine()) != null) {
                            System.out.println("Received: " + line);
                            writer.println("Echo: " + line); // 发送回显
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}

客户端IP为IPv4或IPv6场景

该示例同时支持客户端为IPV4 或者 IPV6的场景

准备JIN-TCP

定义JNI返回模型-TCP
public class ToaSockopt {
    private int[] addr;  // 对应 ip 或 ip6
    private int port;    // 对应 port
    private byte family; // 对应 family
    private byte toaSet; // 对应 toa_set// getter and setter methods...
    public int[] getAddr() {
        return addr;
    }
​
    public void setAddr(int[] addr) {
        this.addr = addr;
    }
​
    public int getPort() {
        return port;
    }
​
    public void setPort(int port) {
        this.port = port;
    }
​
    public byte getFamily() {
        return family;
    }
​
    public void setFamily(byte family) {
        this.family = family;
    }
​
    public byte getToaSet() {
        return toaSet;
    }
​
    public void setToaSet(byte toaSet) {
        this.toaSet = toaSet;
    }
}
定义JNI-TCP
public class Toa {
  static {
    System.loadLibrary("Toa"); //加载动态链接库
  }
​
  // 本地方法声明
  private native ToaSockopt getsockoptNative(int sockfd);

  public ToaSockopt getsockopt(int sockfd) {
    return getsockoptNative(sockfd);
  }
}
编译Toa.java

Toa.java

javah -jni Toa
构建JIN头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Toa */
​
#ifndef _Included_Toa
#define _Included_Toa
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Toa
 * Method:    getsockoptNative
 * Signature: (I)LToaSockopt;
 */
JNIEXPORT jobject JNICALL Java_Toa_getsockoptNative
  (JNIEnv *, jobject, jint);
​
#ifdef __cplusplus
}
#endif
#endif
编写JNI实现-TCP
#include <jni.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <linux/types.h>
​
#ifdef __KERNEL__
#include <linux/in.h>
#include <linux/in6.h>
#else
#include <arpa/inet.h>
#endif
​
#define TOA_CTL (2049)    /* tcp sockopt option */
#define UOA_CTL (2048)    /* udp sockopt option */
​
struct toa_sockopt {
    union {
        __be32 ip[1];
        __be32 ip6[4];
    } addr;              /* 真实客户端 ip */
    __be16 port;         /* 真实客户端端口 */
    __u8 family;         /* 真实客户端 ip 类型:AF_INET / AF_INET6 */
    __u8 toa_set;        /* 1 表示设置了 toa */
};
​
struct toa_sockopt * toa_getsockopt(int sockfd) {
    int ret = 0;
    socklen_t socktoa_len;
    struct toa_sockopt *socktoa;

    socktoa_len = sizeof(struct toa_sockopt);
    socktoa = (struct toa_sockopt *) malloc(socktoa_len);
    if (socktoa == NULL) {
        return NULL;  // malloc failed
    }
​
    memset(socktoa, 0, sizeof(struct toa_sockopt));
​
    ret = getsockopt(sockfd, IPPROTO_IP, TOA_CTL, socktoa, &socktoa_len);
    if (ret == 0) {
        return socktoa;
    }
​
    free(socktoa);  // free memory if getsockopt failed
    return NULL;
}
​
JNIEXPORT jobject JNICALL Java_Toa_getsockoptNative(JNIEnv *env, jobject obj, jint sockfd) {
    int i;
    struct toa_sockopt * result = toa_getsockopt(sockfd);
    if (result == NULL) {
        return NULL;
    }
    // 找到Java类ToaSockopt的类引用
    jclass cls = (*env)->FindClass(env, "ToaSockopt");
    if(cls == NULL) {
        free(result);
        return NULL; // 类未找到
    }
    // 获取ToaSockopt类的构造函数
    jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "()V");
    if(constructor == NULL) {
        free(result);
        return NULL; // 构造函数未找到
    }
    // 创建一个新的ToaSockopt对象
    jobject objToaSockopt = (*env)->NewObject(env, cls, constructor);
    // 获取ToaSockopt类的各个字段
    jfieldID addrField = (*env)->GetFieldID(env, cls, "addr", "[I");
    jfieldID portField = (*env)->GetFieldID(env, cls, "port", "I");
    jfieldID familyField = (*env)->GetFieldID(env, cls, "family", "B");
    jfieldID toaSetField = (*env)->GetFieldID(env, cls, "toaSet", "B");
    jintArray addrArray = (*env)->NewIntArray(env, 4);
    if(addrArray == NULL) {
        // 无法创建数组
        free(result);
        return NULL;
    }
    // 给新创建的ToaSockopt对象的各个字段赋值
    jint addr[4];
    for (i = 0; i < 4; i++) {
        addr[i] = (jint)ntohl(result->addr.ip6[i]);
    }
    (*env)->SetIntArrayRegion(env, addrArray, 0, 4, addr);
    (*env)->SetObjectField(env, objToaSockopt, addrField, addrArray);
    (*env)->SetIntField(env, objToaSockopt, portField, (jint)ntohs(result->port));
    (*env)->SetByteField(env, objToaSockopt, familyField, (jbyte)result->family);
    (*env)->SetByteField(env, objToaSockopt, toaSetField, (jbyte)result->toa_set);
    free(result);
    return objToaSockopt;
}
编译JNI实现-TCP
gcc -shared -o libToa.so Toa.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -fPIC

使用JNI-TCP

使用以下类掉调用JNI获取客户端真实IP

import sun.nio.ch.SocketAdaptor;
import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;

public class RealIp {
    public static String getRealIp(Socket socket) {

        try {
            int fd = getFd(socket);

            System.out.println("channel File Descriptor: " + fd);

            Toa toa = new Toa();
            ToaSockopt result = toa.getsockopt(fd);
            if (result != null) {
                int length = 0;
                int[] addr = result.getAddr();
                if (result.getFamily() == 2) {
                    length = 4;
                } else if (result.getFamily() == 10) {
                    length = 16;
                }

                byte[] byteArray = new byte[length];
                for (int i = 0; i < length / 4; i++) {
                    byteArray[i * 4] = (byte) (addr[i] >> 24);
                    byteArray[i * 4 + 1] = (byte) (addr[i] >> 16);
                    byteArray[i * 4 + 2] = (byte) (addr[i] >> 8);
                    byteArray[i * 4 + 3] = (byte) (addr[i]);
                }
                InetAddress inetAddr = InetAddress.getByAddress(byteArray);
                return inetAddr.getHostAddress();
            } else {
                return socket.getInetAddress().getHostAddress();
            }
        } catch (Exception e) {
            return socket.getInetAddress().getHostAddress();
        }
    }


    private static int getFd(Socket socket) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        int cfd = -1;
        int sfd = -1;

        if (socket instanceof SocketAdaptor) {
            SocketChannel socketChannel = socket.getChannel();

            // 获取 SocketChannel 的实现类名称
            System.out.println("SocketChannel class: " + socketChannel.getClass().getName());

            // 假设我们知道 SocketChannelImpl 是具体实现类
            Class<?> socketChannelImplClass = Class.forName("sun.nio.ch.SocketChannelImpl");

            if (socketChannelImplClass.isInstance(socketChannel)) {
                // 使用反射获取 SocketChannelImpl 中的 fd 字段
                Field fdField = socketChannelImplClass.getDeclaredField("fd");
                fdField.setAccessible(true);

                // 从 fd 字段获取 FileDescriptor 对象
                FileDescriptor fds = (FileDescriptor) fdField.get(socketChannel);

                // 通过反射获取 FileDescriptor 中的 fd 整数字段
                Field fdIntField = FileDescriptor.class.getDeclaredField("fd");
                fdIntField.setAccessible(true);
                cfd = fdIntField.getInt(fds);

            } else {
                System.out.println("SocketChannel is not an instance of SocketChannelImpl.");
            }

            return cfd;

        } else {

            Method getImplMethod = Socket.class.getDeclaredMethod("getImpl");
            getImplMethod.setAccessible(true);
            Object socketImpl = getImplMethod.invoke(socket);
            Method getFileDescriptorMethod = java.net.SocketImpl.class.getDeclaredMethod("getFileDescriptor");
            getFileDescriptorMethod.setAccessible(true);
            Object fileDescriptor = getFileDescriptorMethod.invoke(socketImpl);
            Field fdField = fileDescriptor.getClass().getDeclaredField("fd");
            fdField.setAccessible(true);
            sfd = fdField.getInt(fileDescriptor);
            return sfd;
        }

    }
}

测试-TCP

NIO场景测试-TCP
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;


public class TestToaServer {
    public static void main(String[] args) {
        try {
            // Create a selector to handle channels
            Selector selector = Selector.open();

            // Open a ServerSocketChannel
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); // Configure it to be non-blocking
            serverSocketChannel.bind(new InetSocketAddress(9999)); // Bind to port 9999

            // Register the server socket channel to the selector for accepting connections
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server started on port 9999...");

            while (true) {
                // Wait for events
                selector.select();

                // Get the keys for which events are available
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectedKeys.iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();

                    if (key.isAcceptable()) {
                        // Accept a new client connection
                        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                        SocketChannel clientChannel = serverChannel.accept();
                        clientChannel.configureBlocking(false);

                        // Register the client channel for reading
                        clientChannel.register(selector, SelectionKey.OP_READ);

                        System.out.println(RealIp.getRealIp( clientChannel.socket()));
                        System.out.println("Accepted new connection from: " + clientChannel.getRemoteAddress());
                    } else if (key.isReadable()) {
                        // Read data from the client
                        SocketChannel clientChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(256);
                        int bytesRead = clientChannel.read(buffer);

                        if (bytesRead == -1) {
                            // Connection was closed by the client
                            clientChannel.close();
                            System.out.println("Closed connection from: " + clientChannel.getRemoteAddress());
                        } else {
                            // Process data
                            buffer.flip();
                            String receivedData = new String(buffer.array(), 0, bytesRead);
                            System.out.println("Received: " + receivedData);

                            // Echo the received data back to the client
                            clientChannel.write(ByteBuffer.wrap(("Echo: " + receivedData).getBytes()));
                        }
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
BIO场景测试-TCP
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class TestToaServerB {

    public static void main(String[] args) throws IOException {


        ServerSocket serverSocket = new ServerSocket(9999); // 使用9999端口
        System.out.println("Server started on port 9999...");
        while (true) {
            final Socket socket = serverSocket.accept();

            // 创建一个新线程来处理连接
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                        System.out.println("New connection from " + socket.getInetAddress().getHostAddress());
                        System.out.println("local address: " + socket.getLocalAddress().getHostAddress());
                        System.out.println(RealIp.getRealIp(socket));

                        String line;
                        while ((line = reader.readLine()) != null) {
                            System.out.println("Received: " + line);
                            writer.println("Echo: " + line); // 发送回显
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}

UDP-Java

udp相关说明

udp 只能通过调用 getsockopt 来获取真实源 ip,并且需要提供初始的源目信息。
只有裸金属后端,或者IPV6 LB才需要适配,如果是裸金属后端请先根据指引安装noa模块

注意事项

Java服务端启动监听时,需要指明服务端的真实IP,这个IP和LB的RS IP应该是一致的,不能直接使用0.0.0.0

示例

示例仅作为在UDP场景下获取客户端源IP的演示,实际生产请结合应用需求适配

实例环境:JDK11

准备JNI-UDP

定义JNI返回模型-UDP

UoaUtils.java

public class UoaUtils {
    public static class UoaResult {
        private String realSourceIp;
        private int realSourcePort;

        public UoaResult() {
            // 默认构造函数,JNI 代码中使用 NewObject 调用
        }

        public String getRealSourceIp() {
            return realSourceIp;
        }

        public void setRealSourceIp(String realSourceIp) {
            this.realSourceIp = realSourceIp;
        }

        public int getRealSourcePort() {
            return realSourcePort;
        }

        public void setRealSourcePort(int realSourcePort) {
            this.realSourcePort = realSourcePort;
        }
        public String toString() {
            return "realSourceIp:" + realSourceIp + ", realSourcePort:" + realSourcePort;
        }
    }
}
定义JNI-UDP

Uoa.java

import java.net.DatagramSocket;
public class Uoa {
  static {
    System.loadLibrary("Uoa"); //加载动态链接库
  }
/**
 * 本地方法声明,通过指定的四元组获取当前报文的真实客户端Ip和Port
 * @param fd socket fd
 * @param clientIp 从报文获取的初始源Ip
 * @param clientPort 从报文获取的初始源Port
 * @param serverIp 从报文获取的初始目的Ip
 * @param serverPort 从报文获取的初始目的Port
 * @return
 */
  public native UoaUtils.UoaResult getsockoptNative(int fd, String clientIp, int clientPort, String serverIp, int serverPort);
}
编译Uoa.java

Uoa.java

javac Uoa.java
使用指令构建JNI头文件
// 在不同的jdk版本,该指令有所不同,请根据jdk版本选择
javac -h . Uoa.java

上述指令执行后,会生成名为Uoa.h的头文件,类似如下内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Uoa */

#ifndef _Included_Uoa
#define _Included_Uoa
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Uoa
 * Method:    getsockoptNative
 * Signature: (ILjava/lang/String;ILjava/lang/String;I)LUoaUtils/UoaResult;
 */
JNIEXPORT jobject JNICALL Java_Uoa_getsockoptNative
  (JNIEnv *, jobject, jint, jstring, jint, jstring, jint);

#ifdef __cplusplus
}
#endif
#endif
编写JNI实现-UDP

Uoa.c

#include <jni.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/types.h>
#ifdef __KERNEL__
#include <linux/in.h>
#include <linux/in6.h>
#else
#include <arpa/inet.h>
#endif
#define TOA_CTL (2049)    /* tcp sockopt option */
#define UOA_CTL (2048)    /* udp sockopt option */
union inet_addr {
    struct in_addr      in;
    struct in6_addr     in6;
};

/*
 * uoa_param 结构是通过 getsockopt 接口获取 udp 连接真实源 ip 的入参 + 返回值,
 * 使用方式:getsockopt(sockfd, IPPROTO_IP, UOA_CTL, &param, &plen)
 * sockfd:连接的文件句柄,
 * IPPROTO_IP:协议,固定
 * UOA_CTL:option,固定
 * &param:struct uoa_param 类型的指针,用来传递入参及保存 getsockopt 的返回结果,输入是五元组,输出可以初始化为 0
 * &plen:param 变量的长度,通常设置为 sizeof(struct uoa_param)
 */
struct uoa_param {
    /* input */
    union inet_addr saddr;
    union inet_addr daddr;
    __be16 sport;
    __be16 dport;
    __u8 af;
    /* output */
    union inet_addr  real_saddr;
    __be16           real_sport;
    __u8 real_af;
} __attribute__((__packed__));

struct uoa_param * uoa_getsockopt(int sockfd, struct sockaddr_in *clientaddr, struct sockaddr_in *serveraddr) {
    int ret = 0;
    struct uoa_param *uparam = (struct uoa_param *)malloc(sizeof(struct uoa_param));
    if (uparam == NULL) {
     perror("malloc failed");
         return NULL;  // malloc failed
    }

    memset(uparam, 0, sizeof(struct uoa_param));

    // Assuming clientaddr and serveraddr are valid pointers passed to the function
    memcpy(&uparam->saddr.in, &clientaddr->sin_addr, sizeof(struct in_addr));
    memcpy(&uparam->daddr.in, &serveraddr->sin_addr, sizeof(struct in_addr));
    uparam->sport = clientaddr->sin_port;
    uparam->dport = serveraddr->sin_port;
    uparam->af = AF_INET;

    socklen_t up_len = sizeof(*uparam);
    ret = getsockopt(sockfd, IPPROTO_IP, UOA_CTL, uparam, &up_len);
    if (ret == 0) {
        return uparam;
    }

    free(uparam);  // free memory if getsockopt failed
    perror("get socketopt failed");
    return NULL;
}
JNIEXPORT jobject JNICALL Java_Uoa_getsockoptNative(JNIEnv *env, jobject obj, jint sockfd, jstring jClientIp, jint clientPort, jstring jServerIp, jint serverPort) {

    // Convert Java strings to C strings
    const char *clientIp = (*env)->GetStringUTFChars(env, jClientIp, 0);
    const char *serverIp = (*env)->GetStringUTFChars(env, jServerIp, 0);

    // Prepare socket address structures
    struct sockaddr_in clientaddr;
    struct sockaddr_in serveraddr;

    memset(&clientaddr, 0, sizeof(clientaddr));
    memset(&serveraddr, 0, sizeof(serveraddr));

    clientaddr.sin_family = AF_INET;
    serveraddr.sin_family = AF_INET;
    clientaddr.sin_port = htons(clientPort);
    serveraddr.sin_port = htons(serverPort);

    inet_pton(AF_INET, clientIp, &clientaddr.sin_addr);
    inet_pton(AF_INET, serverIp, &serveraddr.sin_addr);


    struct uoa_param *result = uoa_getsockopt(sockfd, &clientaddr, &serveraddr);

    // Release the Java strings
    (*env)->ReleaseStringUTFChars(env, jClientIp, clientIp);
    (*env)->ReleaseStringUTFChars(env, jServerIp, serverIp);

    if (result == NULL) {
     perror("result failed");
         return NULL; 
    }

    // Create a new UoaResult Java object
    jclass resultClass = (*env)->FindClass(env, "UoaUtils$UoaResult");
    jmethodID constructor = (*env)->GetMethodID(env, resultClass, "<init>", "()V");
    jobject uoaResult = (*env)->NewObject(env, resultClass, constructor);

    // Prepare real source IP string
    char realSourceIp[INET6_ADDRSTRLEN];
    printf("Real Address Family: %u\n", result->real_af);
    if (result->real_af == AF_INET) {
        // IPv4
        inet_ntop(AF_INET, &result->real_saddr.in, realSourceIp, INET_ADDRSTRLEN);
    } else if (result->real_af == AF_INET6) {
        // IPv6
        inet_ntop(AF_INET6, &result->real_saddr.in6, realSourceIp, INET6_ADDRSTRLEN);
    }    

    // Set the fields in the UoaResult object
    jmethodID setRealSourceIp = (*env)->GetMethodID(env, resultClass, "setRealSourceIp", "(Ljava/lang/String;)V");
    jmethodID setRealSourcePort = (*env)->GetMethodID(env, resultClass, "setRealSourcePort", "(I)V");

    jstring realSourceIpStr = (*env)->NewStringUTF(env, realSourceIp);
    (*env)->CallVoidMethod(env, uoaResult, setRealSourceIp, realSourceIpStr);
    (*env)->CallVoidMethod(env, uoaResult, setRealSourcePort, ntohs(result->real_sport));
    free(result);
    return uoaResult;
}
编译Uoa.c

编译Uoa.c
以下指令仅支持在linux系统上编译,请核对jdk路径是否正确

gcc -shared -o libUoa.so Uoa.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -fPIC

使用JNI-UDP

UdpRealIp.java

import java.io.FileDescriptor;
import java.lang.reflect.Field;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.nio.channels.DatagramChannel;

public class UdpRealIp {
    /*
    * BIO 模式下获取真实客户端IP
    */
    public static String getRealIp(DatagramSocket socket, String clientAddress, int clientPort) {

        try {
            // 获取服务器端信息
            InetAddress serverAddress = socket.getLocalAddress();
            int serverPort = socket.getLocalPort();

            // 获取 DatagramSocket 的私有 'impl' 字段,该字段是 DatagramSocketImpl 的一个实例
            Field implField = DatagramSocket.class.getDeclaredField("impl");
            implField.setAccessible(true);
            DatagramSocketImpl impl = (DatagramSocketImpl)implField.get(socket);
            // 打印实现类以确认实际使用的类
            System.out.println("Actual implementation class: " + impl.getClass());

            // 尝试从父类获取 'fd' 字段
            Field fdField = null;
            Class<?> clazz = impl.getClass();
            while (clazz != null) {
                try {
                    fdField = clazz.getDeclaredField("fd");
                    fdField.setAccessible(true);
                    break;
                } catch (NoSuchFieldException e) {
                    // 如果当前类没有该字段,则继续在父类中查找
                    clazz = clazz.getSuperclass();
                }
            }

            if (fdField != null) {
                FileDescriptor fdes = (FileDescriptor) fdField.get(impl);
                System.out.println("FileDescriptor: " + fdes);

                // 获取 FileDescriptor 的私有 'fd' 字段的值
                Field fdValueField = FileDescriptor.class.getDeclaredField("fd");
                fdValueField.setAccessible(true);
                int fd = fdValueField.getInt(fdes);

                Uoa uoa = new Uoa();
                UoaUtils.UoaResult result = uoa.getsockoptNative(fd,clientAddress,clientPort,serverAddress.getHostAddress(),serverPort);
                System.out.println("Received packet from IP: " + result);
                if (result != null) {
                    return result.getRealSourceIp();
                }
                return null;
            } else {
                throw new RuntimeException("Could not find 'fd' field in class hierarchy");
            }
        } catch (Exception e) {
            throw new RuntimeException("get RealIp failure", e);
        }
    }


    /**
    *
    * NIO 场景下获取真实客户端IP
    */
    public static String getRealIp(DatagramChannel channel, String clientAddress, int clientPort) {

        try {
            // 获取服务器端信息
            DatagramSocket socket = channel.socket();
            InetAddress serverAddress = socket.getLocalAddress();
            int serverPort = socket.getLocalPort();

            Field fdField = channel.getClass().getDeclaredField("fd");
            fdField.setAccessible(true);
            FileDescriptor fdes = (FileDescriptor) fdField.get(channel);

            // 通过反射获取 FileDescriptor 的私有 'fd' 字段
            Field fdValueField = FileDescriptor.class.getDeclaredField("fd");
            fdValueField.setAccessible(true);
            int fd = fdValueField.getInt(fdes);

            Uoa uoa = new Uoa();
            UoaUtils.UoaResult result = uoa.getsockoptNative(fd,clientAddress,clientPort,serverAddress.getHostAddress(),serverPort);
            System.out.println("Received packet from IP: " + result);
            if (result != null) {
                return result.getRealSourceIp();
            }
            return null;
        } catch (Exception e) {
            throw new RuntimeException("get RealIp failure", e);
        }
    }
}

测试-UDP

测试前,请先确认裸金属后端已经安装noa,并在实例的监听器上开启了NOA功能,检查方式如下:

(1) 是否安装noa,输出以下内容表示已安装
lsmod | grep noa
noa                    32768  0

(2) 是否开启NOA功能,1表示已开启
cat /proc/net/noa/toa_on
1

cat /proc/net/noa/uoa_on
1

如果noa模块相关检查异常,可联系【网易人员】

NIO场景测试-UDP
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.StandardCharsets;

public class TestUoaServer {

    public static void main(String[] args) {
        try {
            InetAddress localAddress = InetAddress.getByName("Server Ip");
            int port = 9876;
            InetSocketAddress socketAddress = new InetSocketAddress(localAddress, port);

            try (DatagramChannel channel = DatagramChannel.open()) {
                channel.bind(socketAddress);
                System.out.println("Listening on port 9876...");

                ByteBuffer buffer = ByteBuffer.allocate(1024);

                while (true) {
                    buffer.clear();
                    InetSocketAddress clientAddress = (InetSocketAddress) channel.receive(buffer);
                    buffer.flip();
                    String receivedMessage = StandardCharsets.UTF_8.decode(buffer).toString();
                    System.out.println("Message: " + receivedMessage);

                    int clientPort = clientAddress.getPort();
                    UdpRealIp.getRealIp(channel, clientAddress.getAddress().getHostAddress(), clientPort);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
BIO场景测试-UDP
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;

public class TestUoaServerB {

    public static void main(String[] args) {
        DatagramSocket socket = null;
        try {
            InetAddress localAddress = InetAddress.getByName("服务端IP");
            int port = 9876;
            InetSocketAddress socketAddress = new InetSocketAddress(localAddress, port);
            socket = new DatagramSocket(socketAddress);
            byte[] receiveData = new byte[1024];
            System.out.println("Listene 9876...");
            while (true) {
                DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                socket.receive(receivePacket);

                String receivedMessage = new String(receivePacket.getData(), 0, receivePacket.getLength());
                System.out.println("Message: " + receivedMessage);

                InetAddress clientAddress = receivePacket.getAddress();
                int clientPort = receivePacket.getPort();

                UdpRealIp.getRealIp(socket, clientAddress.getHostAddress(), clientPort);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (socket != null && !socket.isClosed()) {
                socket.close();
            }
        }
    }
}

分别编译上述实现类:例如

javac TestUoaServerB.java

执行

java -Djava.library.path=包含uoa.so的路径 -cp . TestUoaServerB

使用nc模拟客户端UDP报文

echo "Hello, UDP" | nc -u  evip port

预期内容

Message: Hello, UDP
Received packet from IP: realSourceIp:真实客户端IP, realSourcePort:真实客户端端口

results matching ""

    No results matching ""