如何在函数计算中实现无入侵全局网络代理
Posted 悦码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在函数计算中实现无入侵全局网络代理相关的知识,希望对你有一定的参考价值。
什么场合需要代理?
假设您有一台物理服务器部署在家里,你需要在函数计算中访问这台物理服务器。最简单的办法是直接暴露这个物理服务器到公网环境。那么问题来了,如果直接暴露到公网不设置防火墙,那么任何人都可以直接访问你的机器,这样会有很大风险。由于函数计算的 IP 是动态变化的,所以您也无法做到指定某个范围的 IP 做防火墙限制。
我们换个思路,将函数计算的的出口访问请求全部经过一台或多台 ECS 出口代理,然后再向外发出请求。ECS和函数计算之间使用加密,那么上面的问题就可以解决了。
如何将函数计算的出口请求全部使用网络代理?
方案一:改源码
这个方案最大的问题是需要改变原有逻辑,成本非常高,对于既有 binary 代码无法做出修改。
很显然,这并不是一个好方案。
方案二:使用 http_proxy 环境变量
使用 http_proxy, all_proxy, https_proxy, no_proxy 等环境变量。
例如:
http_proxy=http://username:passwd@123.100.10.123:3128
no_proxy=.aliyun.com,.taobao.com
优点:
无需改动任何一行代码,增加一项环境变量即可。甚至对于既有的 binary 文件,只要遵循 http_proxy 代理协议无需做任何改动即可以正常执行。
缺点:
虽然现有的大部分 HTTP client 遵循这个规范,但还是有一些实现并不遵从,更重要的是,某些网络请求根本不是 HTTP 协议,例如 mysql client 可能用的是 TCP 连接。
方案三:非入侵式动态替换 glibc 的 connect 函数
如何使用 proxychains
我们先编译这个项目
git clone https://github.com/haad/proxychains
./configure && make
找到 libproxychains.so 和 proxychains.conf
注意到,这里我们可以使用 http / https / socks4 / socks5 等多种代理协议。
创建函数后,我们在函数计算的控制台上为这个函数增加两个环境变量:
PROXYCHAINS_CONF_FILE=/code/proxy/proxychains.conf
LD_PRELOAD=/code/proxy/libproxychains4.so
我们可以使用下面的代码做测试:
# -*- coding: utf-8 -*-
import os
def handler(event, context):
os.system('curl -v ipinfo.io')
return 'hello world'
if __name__ == '__main__':
handler(1, 1)
通过上述函数,我们在日志中可以得到当前函数访问出口 IP。
优点:
原生程序无需关系代理协议细节
所有的 TCP 请求都可以无缝地使用代理,逻辑代码可以无感知,无侵入;
支持自定义 DNS;
缺点:
需要为原始工程增加 proxy 目录,增加两个文件;
不支持 UDP 协议代理;
对于大部分项目来说,使用 UDP 的地方相当少,而只是增加两个文件即可以做到全局代理,这些缺点可以忽略了
实现原理
我们需要在 client 发起 connect 的时候把实际要连接的服务器重定向到指定代理服务器, write 对应 socket fd 的时候将原始数据做相关代理封包,写给代理服务器,read 的时候尝试把代理数据包解开写回给应用逻辑层。
在理解上述实现之前,我们先来看一个示例,如何替换编译好的 C 语言可执行程序中的函数 printf,将下面的 hello world! 替换成 hello world! hello FC!
#include <stdio.h>
int main(int argc, char *argv[]) {
puts("hello world!
");
}
我们把这个编译好
gcc -o a.out main.c
执行得到 hello world!
实现 hook.c
#define _GNU_SOURCE
#include <dlfcn.h>
typedef int (*origin_puts_t)(const char *msg);
int puts(const char *msg) {
int n = 0;
origin_puts_t origin_puts;
/* find the origin puts function */
origin_puts = (origin_puts_t)dlsym(RTLD_NEXT, "puts");
/* use origin puts to print message */
n += origin_puts(msg);
n += origin_puts("hello FC!
");
return n;
}
编译动态链接库 hook.so :
gcc -shared -fPIC hook.c -o hook.so -ldl
接下来我们设置环境变量,并执行原来的 a.out :
LD_PRELOAD=$PWD/hook.so ./a.out
输出得到:
hello world!
hello FC!
也就是说,我们将 a.out 的 puts 函数替换成了 hook.so 中的 puts!
了解这个原理后,我们回到原来的问题,如何实现无入侵的网络代理?
参考 libproxychains.so 核心实现:
connect(https://github.com/rofl0r/proxychains-ng/blob/master/src/libproxychains.c?spm=a2c4e.11153940.blogcont645991.12.20e036188NGsx6#L442)
代理服务器的搭建
推荐 3proxy(https://github.com/z3APA3A/3proxy?spm=a2c4e.11153940.blogcont645991.13.20e036188NGsx6)
支持带验证的 socks4/socks5/HTTP 代理
支持多账号
支持账号流量控制
支持 linux/mac/windows
支持 DNS 代理查询
支持 IPv6
配置简单
以上是关于如何在函数计算中实现无入侵全局网络代理的主要内容,如果未能解决你的问题,请参考以下文章