![如何反向代理原始 TCP 数据包](https://linux22.com/image/627416/%E5%A6%82%E4%BD%95%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E5%8E%9F%E5%A7%8B%20TCP%20%E6%95%B0%E6%8D%AE%E5%8C%85.png)
我正在设计一个系统,其中远程设备安全地将状态更新发送到中央日志服务器进行聚合。在服务器上,我使用 Redis + Logstash + Elasticsearch 解决方案。发送到服务器的数据很敏感,必须加密。我正在努力寻找一种高效且安全的方法将日志“LPUSH”到 Redis 列表中。
这些设备目前能够将以下 Redis 命令直接发送到端口 6379。
"*3\r\n$5\r\nLPUSH\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"
在服务器上的 Redis 中创建正确的键和列表条目。
下一步是将 redis 置于防火墙后面并加密数据包。我目前的尝试是使用 Apache 作为反向代理。设备将与 Apache 建立双向 SSL 连接,然后使用环回接口将解密的信息反向代理到端口 6379。双向 SSL 连接毫无问题地建立,并且消息被转发到 Redis。不幸的是,这不是设备发送的消息。tcpdump 告诉我以下内容...
tcpdump -nnXvv -i lo host localhost and port 6379
127.0.0.1.48916 > 127.0.0.1.6379: Flags [P.], cksum 0xfeab (incorrect -> 0x9415),
seq 1:132, ack 1, win 1025, options [nop,nop,TS val 299310518 ecr 299310518],
length 131
0x0000: 4500 00b7 12b7 4000 4006 2988 7f00 0001 E.....@.@.).....
0x0010: 7f00 0001 bf14 18eb ce9c 0f04 e920 abec ................
0x0020: 8018 0401 feab 0000 xxxx xxxx xxxx xxxx ................
0x0030: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx ....*3./.HTTP/1.
0x0040: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 1..Host:.localho
0x0050: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx st:6379..X-Forwa
0x0060: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx rded-For:.xx.xxx
0x0070: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx .xxx.xxx..X-Forw
0x0080: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx arded-Server:.xx
0x0090: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx .xxx.xxx.xx..Con
0x00a0: xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx nection:.Keep-Al
0x00b0: xxxx xxxx xxxx xx ive....
从 ASCII 翻译中可以看出,Apache 正在 *3 后的第一个 CRLF 处截断消息,并附加 HTTP 标头信息以进行转发,这是理所当然的。当然,Redis 会回复错误,因为该消息不再使用 Redis 序列化协议 (RESP) 进行格式化。
1)有没有办法配置 Apache 来盲目转发原始 TCP 数据包?
2)如果没有,那么有没有一个标准的开源解决方案可以解决这个问题?
感谢您的时间!
答案1
如果除了 Apache 对标头的附加内容之外所有功能均正常运行,则可以通过 mod_headers 关闭这些功能:http://httpd.apache.org/docs/2.2/mod/mod_headers.html
答案2
虽然我的直觉告诉我 Apache+PHP 不是最佳解决方案,但它是一个快速且迄今为止令人满意的解决方案。Apache 将 HTTP 消息发送到 PHP 脚本,然后使用环回接口提取帖子并将其转发到 redis。为了“后人”,我在此处粘贴了脚本的骨架版本。
<?php
$file = fopen("/some/logging.txt","a");
$entityBody = file_get_contents('php://input');
$redisSocket = fsockopen("localhost", 6379, $errno, $errstr, 30);
if (!$redisSocket) {
fwrite($file, "Error: $errstr ($errno)\n");
} else {
fwrite($redisSocket, "$entityBody");
$redisResponse = fgets($redisSocket);
if (!$redisResponse) {
fwrite($file, "No response from Redis");
} else {
fwrite($file, "Redis Response: $redisResponse");
}
}
?>
我尽量避免使用脚本将事物“粘合”在一起,但到目前为止,它提供了额外的好处,允许我检查输入数据的完整性并创建自定义日志和响应。