Python

TCP-Python

  1. accept获取TCP源IP accept获取TCP源IP仅支持真实客户源IP为IPv4的场景,示例程序如下所示,具体细节可见:
    noa/examples/toa/tcp_server.py
    while True:
     # Wait for a connection
     print('Waiting for a connection')
     connection, client_address = sock.accept()
    ​
     try:
         print('Connection from', client_address)
    
    程序调用sock.accept函数之后,返回的client_address地址便是真实客户端IP,如果真实客户端IP为IPv6,那么返回的便是网关fullnat之后的源IP。
  2. 真实客户端IPv6或者IPv4/6混杂场景 - toa_getsockopt方式 如果想要支持真实客户端IP为IPv6的场景,可以通过下述方式,具体细节可见
    noa/examples/toa/tcp_server_nat64.py
    编译动态链接库
    文件路径: noa/examples/libnoa/Makefile
    执行指令:
    make
    
    加载动态链接库
    # 加载动态链接库   
    libnoa = ctypes.cdll.LoadLibrary('../libnoa/libnoa.so') 
    libnoa.toa_getsockopt.argtypes = [ctypes.c_int]
    libnoa.toa_getsockopt.restype = ctypes.POINTER(ToaSockopt)
    toa_getsockopt = libnoa.toa_getsockopt
    
    定义toa_sockopt
    # 定义 toa_sockopt 结构体
    class Addr(ctypes.Union):
     # 禁用对齐,严格按照定义的顺序和大小进行排列
     _pack_ = 1
     _fields_ = [('ip', ctypes.c_uint32 * 1),
                 ('ip6', ctypes.c_uint32 * 4)]
    ​
    class ToaSockopt(ctypes.Structure):
     # 禁用对齐,严格按照定义的顺序和大小进行排列
     _pack_ = 1
     _fields_ = [('addr', Addr),
                 ('port', ctypes.c_uint16),
                 ('family', ctypes.c_uint8),
                 ('toa_set', ctypes.c_uint8)]
    
    获取连接的文件描述符
    data = connection.recv(16)
         print('Received {!r}'.format(data))
    ​
         fd = connection.fileno()
    
    调用toa_getsockopt函数
         ... 
         toasockopt = toa_getsockopt(fd)
         if toasockopt:
             port = socket.ntohs(toasockopt.contents.port)
             if toasockopt.contents.family == socket.AF_INET:
                 ip = socket.inet_ntop(toasockopt.contents.family, toasockopt.contents.addr.ip)
             else :
                 ip = socket.inet_ntop(toasockopt.contents.family, toasockopt.contents.addr.ip6)
             print(f'TOA: {ip}:{port}')
             libnoa.free(toasockopt)
    

注意: 需要手动释放toasockopt,代码libnoa.free(toasockopt)

UDP-Python

UDP场景下仅支持getsockopt方式来获取真实的客户端源IP。

  1. uoa_getsockopt方式 操作步骤如下所示,具体代码可见:
    noa/examples/uoa/udp_server.py
    编译动态链接库(动态链接库代码见下面)
    文件路径: noa/examples/libnoa/Makefile
    执行指令:
    make
    
    加载动态链接库
    # Load the dynamic library
    libnoa = ctypes.cdll.LoadLibrary("../libnoa/libnoa.so")
    ​
    # Define the function signature
    uoa_getsockopt = libnoa.uoa_getsockopt
    uoa_getsockopt.restype = POINTER(UOAParamStruct)
    uoa_getsockopt.argtypes = [c_int, c_char_p, c_char_p, c_int, c_int]
    

定义UOAParamStruct

class Addr(ctypes.Union):
    # 禁用对齐,严格按照定义的顺序和大小进行排列
    _pack_ = 1
    _fields_ = [('ip', ctypes.c_uint32 * 1),
                ('ip6', ctypes.c_uint32 * 4)]

# The uoa_param structure
class UOAParamStruct(Structure):
    # 禁用对齐,严格按照定义的顺序和大小进行排列
    _pack_ = 1
    _fields_ = [("saddr", Addr), 
                ("daddr", Addr),
                ("sport", c_uint16),
                ("dport", c_uint16),
                ("af", c_uint8),
                ("real_saddr", Addr),
                ("real_sport", c_uint16),
                ("real_af", c_uint8)]

调用uoa_getsockopt接口
uoa_getsockopt接口详细定义:

struct uoa_param * uoa_getsockopt(int sockfd, const char *saddr, const char *daddr, int sport, int dport);
  • sockfd: 连接文件描述符
  • saddr: fullnat之后的源IP,可以通过data, address = sock.recvfrom(4096)得到
  • daddr: 服务器监听地址,不支持0.0.0.0
  • sport: fullnat之后的源端口
  • dport: 服务器监听端口
    具体代码如下所示:
    # Create a UDP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    ​
    # Bind the socket to a port
    server_address = ('10.191.73.38', 7400)
    print('Starting up on {} port {}'.format(*server_address))
    sock.bind(server_address)
    ​
    while True:
      print('Waiting to receive message\n')
      data, address = sock.recvfrom(4096)
    ​
      print('Received {} bytes from {}\n'.format(len(data), address))
      print(data)
    ​
      uparam = uoa_getsockopt(sock.fileno(), address[0].encode(), server_address[0].encode(), address[1], server_address[1])
      if uparam:
          port = socket.ntohs(uparam.contents.real_sport)
          if uparam.contents.real_af == socket.AF_INET:
              ip = socket.inet_ntop(uparam.contents.real_af, uparam.contents.real_saddr.ip)
          else :
              ip = socket.inet_ntop(uparam.contents.real_af, uparam.contents.real_saddr.ip6)
          print(f'UOA: {ip}:{port}\n')
          # 释放掉uparam内存空间
          libnoa.free(uparam)
    ...
    

注意: 在Python中,字符串是Unicode编码的,而C语言中的字符串是以字符形式表示的。因此,在调用C语言函数时,需要将Python字符串转换为C语言字符串。可以使用Python的encode方法将Python字符串编码成指定编码的字节流,然后将字节流传递给C语言函数。

动态链接库代码

libnoa.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include "../../uapi/unoa.h"
​
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;
}
​
struct uoa_param * uoa_getsockopt(int sockfd, const char *saddr, const char *daddr, int sport, int dport) {
    int ret = 0;
    socklen_t up_len;
    struct uoa_param *uparam;

    up_len = sizeof(struct uoa_param);
    uparam = (struct uoa_param *) malloc(up_len);
    if (uparam == NULL) {
        return NULL;
    }

    memset(uparam, 0, sizeof(struct uoa_param));
    if (inet_pton(AF_INET, saddr, &uparam->saddr.in) <= 0 || inet_pton(AF_INET, daddr, &uparam->daddr.in) <= 0) {
        free(uparam);
        return NULL;  // inet_pton failed
    }
​
    uparam->sport = htons(sport);
    uparam->dport = htons(dport);
    uparam->af = AF_INET;
​
    ret = getsockopt(sockfd, IPPROTO_IP, UOA_CTL, uparam, &up_len);
    if (ret == 0) {
        return uparam;
    }
​
    free(uparam);
    return NULL;
}

unoa.h

#ifndef _UNOA_H_
#define _UNOA_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 */
​
/*
 * toa_sockopt 结构是通过 getsockopt 接口获取 tcp 连接真实源 ip 的返回值,
 * 使用方式:getsockopt(sockfd, IPPROTO_IP, TOA_CTL, &socktoa, &socktoa_len),其中
 * sockfd:连接的文件句柄,
 * IPPROTO_IP:协议,固定
 * TOA_CTL:option,固定
 * &socktoa:struct toa_sockopt 类型的指针,用来保存 getsockopt 的返回结果,可以初始化为 0
 * &socktoa_len:socktoa 变量的长度,通常设置为 sizeof(struct toa_sockopt)
 */
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 */
};
​
union inet_addr {
    struct in_addr      in;
    struct in6_addr     in6;
};
​
/*
 * uoa_param 结构是通过 getsockopt 接口获取 udp 连接真实源 ip 的入参 + 返回值,
 * 使用方式:getsockopt(sockfd, IPPROTO_IP, UOA_SO_GET_LOOKUP, &param, &plen)
 * sockfd:连接的文件句柄,
 * IPPROTO_IP:协议,固定
 * UOA_SO_GET_LOOKUP: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__));
​
#endif

makefile

CC = gcc
CFLAGS = -Wall -fPIC
INCLUDES = -I../../uapi
LIB_NAME = libnoa
LIB_SRC = $(LIB_NAME).c
LIB_HEADER = ../../uapi/unoa.h
LIB_OBJ = $(LIB_NAME).o
LIB_TARGET = $(LIB_NAME).so
​
all: $(LIB_TARGET)
​
$(LIB_OBJ): $(LIB_SRC) $(LIB_HEADER)
  $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
​
$(LIB_TARGET): $(LIB_OBJ)
  $(CC) -shared -o $@ $^
​
clean:
  rm -f $(LIB_OBJ) $(LIB_TARGET)
​
.PHONY: all clean

results matching ""

    No results matching ""