SNIProxy Bind Outgoing Interface

这篇纯技术,没兴趣的别看。

关于需求

最早试图用路由器自带的功能去实现sni检测+自动路由,结果发现edgerouter的dpi没开放太多资料。
再又试图用squid去实现正向代理,绑定上行网络地址或者设备,虽然edgerouter自带squid以实现transparent proxy,但是自带版本SNI检测不支持(新版不知),绑定upstream的地址或者设备也没看到有人说支持。
又考虑过haproxy,但是这个必须自己写backend(似乎可以haproxy + squid,没去实践过,至少haproxy的acl要写好长;而且这个组合里haproxy都可以直接被跳过,反正有DNAT也有DNS劫持)。

从上边的方案思考过程,我觉得应该能看懂我想要什么。

关于实现

选用sniproxy,也算偶然。虽然之前vps上实验sni检测+代理的时候,大概看了眼(但是最后还是选了haproxy)。不过最后选这个的重要原因就是好改 XD
官方github在https://github.com/dlundquist/sniproxy。

先考虑的方案是,通过socket上联时绑定客户端IP。
本身支持上行设置请求的源地址,大致实现代码在 connection.c:

但是经过试验,配置source address后,数据包还是会根据路由上的路由表跳到域名解析IP该走的那个网络。

所以,这个方案是不满足我的要求的。

如果绑定IP不行,那就试试绑定设备。
绑定IP的方案的可行性,很早以前跟同学Windows下讨论一个客户端设计的时候验证过;但是绑定设备的,完全没搞过。
现查。

然后查到了 SO_BINDTODEVICE

SO_BINDTODEVICE
Bind this socket to a particular device like “eth0”, as specified in the passed interface name. If the name is an empty string or the option length is zero, the socket device binding is removed. The passed option is a variable-length null-terminated interface name string with the maximum size of IFNAMSIZ. If a socket is bound to an interface, only packets received from that particular interface are processed by the socket. Note that this only works for some socket types, particularly AF_INET sockets. It is not supported for packet sockets (use normal bind(2) there).
Before Linux 3.8, this socket option could be set, but could not retrieved with getsockopt(2). Since Linux 3.8, it is readable. The optlen argument should contain the buffer size available to receive the device name and is recommended to be IFNAMSZ bytes. The real device name length is reported back in the optlen argument.

不过比较麻烦的是,这个只支持IPV4,而且必须使用root。

具体实践在 https://github.com/sskaje/sniproxy
mips(64)/mipsel的deb包已经放在 http://dl.sskaje.me/debian/,供 edgerouter lite/edgerouter x使用,未签名。

具体代码:

还需要说明的是,这个也只能在Linux上用。

关于应用

应用方案,单独写,跟这篇关系没那么大。

SNIProxy Bind Outgoing Interface by @sskaje: https://sskaje.me/2016/11/sniproxy-bind-outgoing-interface/