10
25
2012
24

使用 TLS/SSL 加密你的 HTTP 代理

HTTP 代理是明文的,这导致实际访问的 URL 可以被他人监测到。即使使用 HTTPS 协议,经过 HTTP 代理时会发送CONNECT请求,告诉代理要连续到远程主机的指定端口。于是,访问的目标域名暴露了。

有没有办法将传输内容加密呢?比如像 HTTPS 那样,使用 TLS 协议连接到代理服务器,然后再进行 HTTP 请求。很遗憾的是,我在 ziproxy 的配置里没有发现这样的选项。在 shlug 邮件列表里询问后,Shell Xu 提到了 stunnel 这个工具。以前我试过用它把 HTTP 的网站转成 HTTPS 的,但是网站后端程序无法知晓用户实际上使用的是 HTTPS,有些郁闷,就没管它了。

这次再次请出 stunnel,在代理服务器上执行如下命令:

sudo stunnel -d 0.0.0.0:8081 -r localhost:8080 -p /etc/stunnel/stunnel.pem

这样,所有到服务器的 8081 端口的请求,都会经过 TLS 解密后传递给 8080 端口。同时响应的数据也会被加密后再返回请求方。

接下来的问题是,浏览器无法直接使用这种代理。实际上除了拿 openssl 命令手动连接外,我不知道任何程序能够使用这种代理。那好,本地弄个反过来加密/解密的服务好了。还是使用 stunnel。不过出了点意外:Arch Linux 的 stunnel 是第四版,不再用命令行参数,转而使用配置文件了。于是参考这篇 Upgrading to stunnel 4,写了份 stunnel4 的配置文件:

compression = zlib
foreground = yes
output = /dev/stdout
client = yes
pid = /tmp/stunnel.pid
# or will output to syslog :-(
output = /tmp/stunnel.log

[name]
accept = 8082
connect = server.com:8081

这样在本地 8082 端口监听,把所有请求加密后转发到 server.com 的 8081 端口。同时响应的数据会被解密后再返回。

现在,所有与代理服务器传输的数据都被加密了,不怕被偷窥啦。

后记:

后来,我发现其实代理服务器和我本机都装了两个版本的 stunnel,只是名字中不带版本号的一个是第三版而另一个是第四版而已……

再后来,我猛然想起神器 socat——这家伙是支持 OpenSSL 的!比如客户端这边像下边这样子就可以了:

$ socat tcp-listen:8082,fork openssl:server.com:8081,verify=0

socat 真是神器啊,cat、netcat、rinetd、stunnel 的功能都覆盖了!

这样使用的时候,每次来新请求时,socat 会 fork 一个新进程来处理。有点浪费资源。不过略微查看了下,stunnel 似乎也一样。

8
28
2012
10

UDP打洞实验

两台没有外网 IP、在 NAT 后边的主机如何直连?UDP打洞通常可行,但是需要第三方服务器。方法如下:

在服务器 S 上监听一个 UDP 端口,在收到 UDP 数据包后把源地址发回去。代码如下(github):

import sys
import time
import socket

def main(port):
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  s.bind(('', port))
  try:
    while True:
      data, addr = s.recvfrom(4096)
      back = 'Your address is %r\n' % (addr,)
      s.sendto(back.encode(), addr)
      print(time.strftime('%Y-%m-%d %H:%M:%S'), addr, 'just sent us a message:', data.decode('utf-8', 'replace'), end='')
  except KeyboardInterrupt:
    print()

if __name__ == '__main__':
  try:
    main(int(sys.argv[1]))
  except (ValueError, IndexError):
    sys.exit('which port to listen?')

主机 A 发送数据包:

$ socat readline udp:xmpp.vim-cn.com:2727,sourceport=4567
my addr?
Your address is ('a.b.c.d', 40060)

输入任意消息并回车,一个 UDP 就从本地的 4567 发送出去了。从上述示例我们可以看到,NAT 设备转发时是从 40060 端口发送出去的。为了让服务器返回的数据能够到达内网主机,在一段时间内,NAT 设备会记住外网来自 40060 端口的 UDP 数据包要发送给主机 a.b.c.d 的 4567 端口。完全圆锥型NAT不会在意外部数据包是从什么地方发回来的。受限圆锥型NAT会忽略掉其它主机的数据包,上例中只认可来自 xmpp.vim-cn.com 的数据包。端口受限圆锥型NAT更进一步地要求源端口(上例中是 2727)必须跟之前发出的数据包的目的端口一致。当然,「之前发出的数据包」不必是最后一个。所以,除了最后一种——对称NAT——之外,其它类型的NAT都是有可能成功穿透的。参见维基百科条目网络地址转换STUN

后来通过 pystun 程序,我得知我所处的 NAT 是完全圆锥型的。

在知道 A 的发送地址后,主机 B 就可以向这个地址发送数据了。接下来的操作使用 socat 命令就是:

# host A
$ socat readline udp-listen:4567
# host B
$ socat readline udp:A:4567

然后 B 先发送数据让 A 知道 B 的地址(socat 会 connect 到这个地址),双方就可以相互通信了。当然,因为是 UDP 协议,所以通信是不可靠的,丢包啊乱序啊都有可能。

2013年10月13日更新:想要连接到 NAT 后边的 mosh 请看这里

Category: 网络 | Tags: python 网络 socat UDP
7
27
2012
8

fcitx-remote 接口通过 socat 跨主机使用

在使用 Mac OS X 时,我十分想念 fcitx.vim 插件在使用 Vim 时能智能切换输入法的激活状态。所以我换回 Arch Linux 了。关于 Mac OS X 与我的「不兼容」还是留到下次再说,这次解决的问题是,当我 ssh 到另一主机上使用 Vim 时,如何让 fcitx.vim 能够控制本机的输入法状态?

fcitx-remote 接口使用的是 UNIX 套接字文件,因此天生是不能跨主机通信的(因此不用担心局域网里其它人捣乱)。现在,为了进行跨主机通信,当然要使用网络套接字了。既然都是套接字,转发下就可以了嘛。于是想到 socat。

在远程机器监听一个套接字文件,转发到本地机器的 8989 端口:

socat UNIX-LISTEN:/tmp/fcitx-remote.sock,fork TCP:192.168.2.142:8989

在本地监听网络 8989 端口,转发到本地 fcitx 的套接字:

socat tcp-listen:8989,fork UNIX-CONNECT:/tmp/fcitx-socket-\\:0

fcitx.vim 使用更新后的 1.2 版,然后告诉它你要使用的套接字文件地址:

export FCITX_SOCKET=/tmp/fcitx-remote.sock                                                                                                                                               

然后就可以啦~

最后,贴一张测试过程中抓到的 htop 的图片,2 万多进程哦,htop 已经卡了,实际的 load 请看右下角的红色数字。我执行killall socat命令后等了几分钟,终于因为内存耗尽系统开始重新缓慢工作了。数次 killall 后终于恢复正常……再次测试前果断先ulimit -u 1000 :-)

Category: Linux | Tags: fcitx vim 网络 socat

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com