Python
TCP-Python
- accept获取TCP源IP
accept获取TCP源IP仅支持真实客户源IP为IPv4的场景,示例程序如下所示,具体细节可见:
noa/examples/toa/tcp_server.py
程序调用sock.accept函数之后,返回的client_address地址便是真实客户端IP,如果真实客户端IP为IPv6,那么返回的便是网关fullnat之后的源IP。while True: # Wait for a connection print('Waiting for a connection') connection, client_address = sock.accept() try: print('Connection from', client_address)
- 真实客户端IPv6或者IPv4/6混杂场景 - toa_getsockopt方式
如果想要支持真实客户端IP为IPv6的场景,可以通过下述方式,具体细节可见
noa/examples/toa/tcp_server_nat64.py
编译动态链接库
文件路径: noa/examples/libnoa/Makefile
执行指令:
加载动态链接库make
定义toa_sockopt# 加载动态链接库 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 结构体 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)]
调用toa_getsockopt函数data = connection.recv(16) print('Received {!r}'.format(data)) fd = connection.fileno()
... 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。
- 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, ¶m, &plen)
* sockfd:连接的文件句柄,
* IPPROTO_IP:协议,固定
* UOA_SO_GET_LOOKUP:option,固定
* ¶m: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