什麼場合需要代理?
假設您有一台實體伺服器部署在家裡,你需要在函數計算中通路這台實體伺服器。最簡單的辦法是直接暴露這個實體伺服器到公網環境。那麼問題來了,如果直接暴露到公網不設定防火牆,那麼任何人都可以直接通路你的機器,這樣會有很大風險。由于函數計算的 IP 是動态變化的,是以您也無法做到指定某個範圍的 IP 做防火牆限制。
我們換個思路,将函數計算的的出口通路請求全部經過一台或多台 ECS 出口代理,然後再向外送出請求。ECS和函數計算之間使用加密,那麼上面的問題就可以解決了。
如何将函數計算的出口請求全部使用網絡代理?
方案一:改源碼
将所有相關請求的 client 加上代理伺服器位址。
這個方案最大的問題是需要改變原有邏輯,成本非常高,對于既有 binary 代碼無法做出修改。
很顯然,這并不是一個好方案。
方案二:使用 http_proxy 環境變量
使用
http_proxy
,
all_proxy
https_proxy
no_proxy
等環境變量。
我們知道函數計算支援使用者自定義環境變量,我們可以将代理伺服器的位址寫入環境變量,那麼這個函數中所有的 HTTP 請求都會被轉發到這個代理伺服器做跳轉請求。我們可以通過
no_proxy
來控制部分host 不走代理。
例如:
http_proxy=http://username:[email protected]: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
我們在現有函數計算代碼包中,增加 proxy 目錄,将
libproxychains.so
proxychains.conf
放置到該目錄,修改 proxychains.conf 中代理伺服器的位址和相關使用者密碼,如果有不需要代理的 HOST 也可以在該檔案中配置。
注意到,這裡我們可以使用
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!\n
替換成
hello world!\nhello FC!\n
#include <stdio.h>
int main(int argc, char *argv[]) {
puts("hello world!\n");
}
我們把這個編譯好
gcc -o a.out main.c
執行得到
hello world!\n
實作 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!\n");
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代理伺服器的搭建
推薦
3proxy- 支援帶驗證的 socks4/socks5/HTTP 代理
- 支援多賬号
- 支援賬号流量控制
- 支援 linux/mac/windows
- 支援 DNS 代理查詢
- 支援 IPv6
- 配置簡單