天天看點

如何在函數計算中實作無入侵全局網絡代理

什麼場合需要代理?

假設您有一台實體伺服器部署在家裡,你需要在函數計算中通路這台實體伺服器。最簡單的辦法是直接暴露這個實體伺服器到公網環境。那麼問題來了,如果直接暴露到公網不設定防火牆,那麼任何人都可以直接通路你的機器,這樣會有很大風險。由于函數計算的 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
  • 配置簡單

繼續閱讀