在使用 libwebsockets 开发服务端时,若客户端通过代理(如 Nginx、HAProxy 等)连接,直接获取的通常是代理服务器的 IP。要获取客户端真实 IP,需通过代理服务器传递的特定 HTTP 头信息(如 X-Forwarded-For
或 X-Real-IP
)解析。以下是具体实现方法:
核心原理
代理服务器会在转发请求时,将客户端真实 IP 写入 HTTP 头字段(需代理提前配置),常见字段:
X-Forwarded-For
:格式为客户端真实IP, 代理1IP, 代理2IP
(最左侧为真实IP)。X-Real-IP
:直接记录客户端真实IP(通常由一级代理设置)。
libwebsockets 服务端可在握手阶段解析这些头字段,提取真实 IP。
实现步骤
1. 代理服务器配置(以 Nginx 为例)
确保代理服务器正确添加转发头,示例 Nginx 配置:
location /ws {
proxy_pass http://127.0.0.1:8080; # 指向libwebsockets服务端
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
# 关键:添加真实IP头
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
2. libwebsockets 服务端获取真实 IP
在 libwebsockets 回调函数中,通过 lws_hdr_copy
接口获取代理头字段,解析出真实 IP。
#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#define MAX_IP_LEN 64
// 解析X-Forwarded-For获取真实IP(取第一个IP)
static const char* get_real_ip_from_forwarded(const char* forwarded) {
if (!forwarded) return NULL;
static char real_ip[MAX_IP_LEN];
// X-Forwarded-For格式:"client_ip, proxy1_ip, proxy2_ip"
const char* comma = strchr(forwarded, ',');
if (comma) {
size_t len = comma - forwarded;
if (len < MAX_IP_LEN) {
memcpy(real_ip, forwarded, len);
real_ip[len] = '\0';
return real_ip;
}
}
// 若没有逗号,整个字段即为真实IP
return forwarded;
}
// 回调函数:处理WebSocket事件
static int callback_server(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: {
// 连接建立时获取IP
char client_ip[MAX_IP_LEN] = {0};
char proxy_ip[MAX_IP_LEN] = {0};
const char* real_ip = NULL;
// 1. 获取代理头X-Forwarded-For
if (lws_hdr_copy(wsi, proxy_ip, sizeof(proxy_ip), WSI_TOKEN_X_FORWARDED_FOR) > 0) {
real_ip = get_real_ip_from_forwarded(proxy_ip);
}
// 2. 若X-Forwarded-For不存在,尝试X-Real-IP
if (!real_ip && lws_hdr_copy(wsi, proxy_ip, sizeof(proxy_ip), WSI_TOKEN_X_REAL_IP) > 0) {
real_ip = proxy_ip;
}
// 3. 若均不存在,使用默认的客户端IP(可能是代理IP)
if (!real_ip) {
lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_ip, sizeof(client_ip), NULL, 0);
real_ip = client_ip;
}
printf("客户端真实IP: %s\n", real_ip);
break;
}
// 其他事件处理(略)
default:
break;
}
return 0;
}
// 协议配置
static const struct lws_protocols protocols[] = {
{
"default",
callback_server,
0, // 不使用用户数据
4096, // 接收缓冲区大小
},
{ NULL, NULL, 0, 0 } // 协议结束标记
};
int main() {
struct lws_context_creation_info info;
struct lws_context *context;
memset(&info, 0, sizeof(info));
info.port = 8080;
info.protocols = protocols;
info.options = LWS_SERVER_OPTION_HTTP_HEADERS_SECURITY_BEST_PRACTICES_ENFORCE;
// 创建WebSocket上下文
context = lws_create_context(&info);
if (!context) {
fprintf(stderr, "创建上下文失败\n");
return 1;
}
printf("服务端启动,端口: %d\n", info.port);
// 事件循环
while (lws_service(context, 50) >= 0);
// 清理资源
lws_context_destroy(context);
return 0;
}
关键代码解析
获取代理头字段:
通过lws_hdr_copy
函数提取X-Forwarded-For
和X-Real-IP
头,对应的令牌分别为:WSI_TOKEN_X_FORWARDED_FOR
:对应X-Forwarded-For
头WSI_TOKEN_X_REAL_IP
:对应X-Real-IP
头
解析
X-Forwarded-For
:
该字段可能包含多个 IP(逗号分隔),取第一个即为客户端真实 IP(如client_ip, proxy1, proxy2
中提取client_ip
)。降级策略:
若代理头不存在,通过lws_get_peer_addresses
获取原始连接 IP(通常是代理服务器 IP)。
注意事项
代理信任问题:
仅信任已知代理服务器的X-Forwarded-For
或X-Real-IP
头,防止客户端伪造这些头字段。可在服务端限制仅接收特定代理 IP 的请求。libwebsockets 版本兼容:
不同版本的 libwebsockets 头字段令牌可能不同(如旧版本可能需要直接使用字符串X-Forwarded-For
而非枚举值),需根据实际版本调整。IPv6 支持:
若需支持 IPv6,需调整 IP 缓冲区大小(MAX_IP_LEN
),并处理 IPv6 格式(如::1
)。
通过以上方法,libwebsockets 服务端可正确获取经过代理的客户端真实 IP,适用于反向代理、负载均衡等场景。