├── .github
└── workflows
│ └── erlang.yml
├── .gitignore
├── .travis.yml
├── Emakefile
├── LICENSE
├── README.md
├── README.zh.md
├── ebin
├── .gitignore
└── mimicsocks.app
├── include
└── mimicsocks.hrl
├── priv
├── .gitignore
└── mimicsocks.cfg
├── src
├── mimicsocks_app.erl
├── mimicsocks_cfg.erl
├── mimicsocks_crypt.erl
├── mimicsocks_inband_recv.erl
├── mimicsocks_inband_send.erl
├── mimicsocks_local.erl
├── mimicsocks_local_agg.erl
├── mimicsocks_mimic.erl
├── mimicsocks_remote.erl
├── mimicsocks_remote_agg.erl
├── mimicsocks_remote_ho.erl
├── mimicsocks_remote_http.erl
├── mimicsocks_remote_relay.erl
├── mimicsocks_remote_socks.erl
├── mimicsocks_sup.erl
├── mimicsocks_tcp_listener.erl
├── mimicsocks_wormhole_local.erl
└── mimicsocks_wormhole_remote.erl
└── test
└── mimicsocks_test.erl
/.github/workflows/erlang.yml:
--------------------------------------------------------------------------------
1 | name: Erlang CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 |
14 | build:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | container:
19 | image: erlang:22.0.7
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 | - name: Compile
24 | run: rebar3 compile
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.eunit
2 | /doc
3 | *.bak
4 | *.bak~
5 | *.swp
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: erlang
2 | otp_release:
3 | - 20.0
4 | script: erl -make
--------------------------------------------------------------------------------
/Emakefile:
--------------------------------------------------------------------------------
1 | {"./src/*", [{i, "./include"}, {outdir, "./ebin"}]}.
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2019 foldl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mimicsocks: just another TCP proxy
2 |
3 | [](https://travis-ci.org/foldl/mimicsocks)
4 |
5 | Mimicsocks is a reversable TCP forwarder, relay, tunnel or proxy, inspired by Shadowsocks and stimulated by [1].
6 |
7 | 查看[简体中文版](README.zh.md).
8 |
9 | ## Table of Contents
10 |
11 | * [Overview](#overview)
12 | * [Features](#features)
13 | * [Scenario 1](#scenario-1)
14 | * [Scenario 2](#scenario-2)
15 | * [Get Stated](#get-started)
16 | * [Configuration Exampels](#configuration-examples)
17 | * [Scenario 1](#scenario-1-1)
18 | * [Scenario 2](#scenario-2-1)
19 | * [Inside the Wormhole](#inside-the-wormhole)
20 |
21 |
22 | ## Overview
23 |
24 | Mimicsocks is a wormhole with two ends called local & remote end respectively.
25 | Both ends are specified by an IP address and port.
26 |
27 | Data put into the local end will be transmitted to the remote end
28 | secretly and magically. Once data arrives at the remote end, it is served by
29 | a data handler. Mimicsocks has three handlers:
30 |
31 | * A simple socks4/4a/5 proxy
32 |
33 | With it, one can use mimicsocks just like Shadowsocks.
34 |
35 | Thanks to the modularity, this handler can be used as a standalone socks4/4a/5 proxy:
36 |
37 | `mimicsocks_tcp_listener:start_link([Ip, Port, mimicsocks_remote_socks, [undefined]]).`
38 |
39 | * A simple http proxy
40 |
41 | This simple proxy supports http/https and http tunnel.
42 |
43 | This handler can also be used as a standalone http proxy:
44 |
45 | `mimicsocks_tcp_listener:start_link([Ip, Port, mimicsocks_remote_http, [undefined]]).`
46 |
47 | * A relay
48 |
49 | This relay forwards data to somewhere else specified by an IP address and port.
50 | Data can be forwarded to another mimicsocks to create a chain of proxies. Data can
51 | also be forwarded to your own socks5 or http proxy.
52 |
53 | ### Features
54 |
55 | * Chainable
56 |
57 | Mimicsocks can be connected in series to create a multi-hop proxy or a likely-onion router.
58 |
59 | * Mimic
60 |
61 | Mimicsocks manipluates packages size and delay, and makes baton handover randomly.
62 |
63 | * Simple
64 |
65 | Mimicsocks is written in Erlang/OPT, no third-party dependencies.
66 |
67 | Mimicsocks can be used for different purposes.
68 |
69 | ### Scenario 1
70 |
71 | User programs connect to the local end to access services provided by different handlers.
72 |
73 | When users want extra privacy, or to access contents that are blocked by firewall, this scenario provides a solution.
74 |
75 | ```
76 | handlers
77 |
78 | Users +--------+
79 | + +----> http <-->
80 | | | +--------+
81 | | |
82 | +-----v-----+ wormhole +------------+ | +--------+
83 | | local <+ + + + + + + + > remote <--------> socks <-->
84 | +-----------+ +------------+ | +--------+
85 | |
86 | | +--------+
87 | +----> relay <-->
88 | +--------+
89 | ```
90 |
91 | ### Scenario 2
92 |
93 | User programs connect to the remote end to access services provided by different handlers.
94 |
95 | When users want to access contents located in a intranet, this scenario provides a solution.
96 |
97 | WARNING: It should be noted that in this scenario, intranet services are exposed to the
98 | outside without protection. Handlers in the intranet should have proper authentication
99 | mechanism, or chain with another mimicsocks.
100 |
101 | ```
102 | +---------------------------------------+
103 | | intranet |
104 | | handlers |
105 | | |
106 | | +--------+ | Users
107 | | <--> http <----+ | +
108 | | +--------+ | | |
109 | | | | |
110 | | +--------+ | +-----------+ | wormhole +-----v------+
111 | | <--> socks <----------> local <--+ + + + + + + > remote |
112 | | +--------+ | +-----------+ | +------------+
113 | | | |
114 | | +--------+ | |
115 | | <--> relay <----+ |
116 | | +--------+ |
117 | +---------------------------------------+
118 | ```
119 |
120 | ## Get Started
121 |
122 | Take Windows as an example.
123 |
124 | 1. Install Erlang/OTP 20.0 or newer (seriously).
125 |
126 | Suppose it's installed in `C:\Program files\erl9.0`
127 |
128 | 1. Download this package Erlang's lib directory: `C:\Program files\erl9.0\lib`.
129 |
130 | 1. Start werl.exe, and build mimicsocks:
131 |
132 | ```shell
133 | Eshell V9.0 (abort with ^G)
134 | 1> cd("../lib/mimicsocks").
135 | C:/Program files/erl9.0/lib/mimicsocks
136 | ok
137 | 2> make:all().
138 | ......
139 | up_to_date
140 | ```
141 |
142 | 1. Config mimicsocks.
143 |
144 | See below for exmples. Note that both ends share the same config file.
145 |
146 | Open `C:\Program files\erl9.0\lib\mimicsocks\priv\mimicsocks.cfg` and edit it:
147 |
148 | ```erlang
149 | {default, [ % name of this wormhole
150 | ...
151 | {handler, socks}, % socks, http, or relay (see below)
152 | ...
153 | ]
154 | }.
155 | ```
156 |
157 | To use the relay handler, one can define another wormhole, then use it:
158 | ```erlang
159 | {default, [ % name of this wormhole
160 | ...
161 | {handler, {relay, another}},
162 | ...
163 | ]
164 | }.
165 | {another, [ % name of another wormhole
166 | ...
167 | ]
168 | }.
169 | ```
170 |
171 | Or just relay to another address:
172 | ```erlang
173 | {default, [ % name of this wormhole
174 | ...
175 | {remote_handler, {relay, {Ip, Port}},
176 | ...
177 | ]
178 | }.
179 | ```
180 |
181 | 1. Launch mimicsocks:
182 |
183 | On remote & local machine:
184 | ```shell
185 | erl -eval "application:ensure_all_started(mimicsocks)" -noshell -detached
186 | ```
187 |
188 | For aggregated ones, remote end should be started ahead of local end.
189 |
190 | It's recommended to use Erlang heart (see Issue #):
191 | ```shell
192 | export HEART_COMMAND="erl -eval 'application:ensure_all_started(mimicsocks)' -noshell -detached -heart"
193 | `$HEART_COMMAND`
194 | ```
195 |
196 | ## Configuration Examples
197 |
198 | ### Scenario 1
199 |
200 | There is a server with IP address S0.S1.S2.S3, we want to use it as a socks proxy.
201 |
202 | We are in a intranet with IP address A0.A1.A2.A3, we want to use port 8888 as the entry point.
203 |
204 | ```erlang
205 | {default, [ % name of this wormhole
206 | {server, {{A0,A1,A2,A3}, 8888}}, % local end address
207 | {wormhole_remote, {{S0,S1,S2,S3}, 9999}}, % remote end address
208 | {wormhole, aggregated}, % can be aggregated (RECOMMENDED) or distributed
209 | {handler, socks}, % socks, http, or relay
210 | {wormhole_extra_ports, [9998]}, % extra ports on remote end for handover
211 | {key, <<...>>} % possible key length: 128, 192, or 256 bits
212 | % use following code to generate a new key:
213 | % io:format("~p~n",[crypto:strong_rand_bytes(256 div 8)]).
214 | ]
215 | }.
216 | ```
217 |
218 | After mimicsocks is successfully started, set programs' socks proxy to A0.A1.A2.A3:8888.
219 |
220 | Note: port number can be chosen randomly.
221 |
222 | ### Scenario 2
223 |
224 | We have a server with IP address S0.S1.S2.S3 and another Windows box in intranet with address A0.A1.A2.A3.
225 | We need to access Windows remote desktop from outside of this intranet.
226 |
227 | ```erlang
228 | {default, [ % name of this wormhole
229 | {reverse, true}, % reverse proxy
230 | {server, {{S0,S1,S2,S3}, 8888}}, % local end address
231 | {wormhole_remote, {{S0,S1,S2,S3}, 9999}}, % remote end address
232 | {wormhole, aggregated}, % must be aggregated
233 | {handler, {relay, {{A0,A1,A2,A3}, 3389}}}, % relay to remote desktop
234 | {wormhole_extra_ports, [9998]}, % extra ports on remote end for handover
235 | {key, <<...>>} % possible key length: 128, 192, or 256 bits
236 | ]
237 | }.
238 | ```
239 |
240 | After mimicsocks is successfully started, connect to S0.S1.S2.S3:8888 to access the remote desktop.
241 |
242 | ## Inside the Wormhole
243 |
244 | In each end of this wormhole (a.k.a mimicsocks), there is a list of nodes.
245 | Each node receives data in message format `{recv, From, Data}`, and pass
246 | the processed data to the next node by sending `{recv, From, NewData}` to it.
247 | Generally, if there is a node A in one end,
248 | there will be a node A-1 in the other end to cancel out A's effects.
249 |
250 | There are two types of wormhole, aggregated and distributed.
251 | For a wormhole in distributed mode, when a new socket (call it local socket)
252 | is established on the local end, another socket (call it remote socket) between
253 | local & remote ends is also created.
254 | For a wormhole in aggregated mode, there is only one socket between local & remote ends,
255 | and all traffic are aggregated into this single socket.
256 |
257 | Mimicsocks has following nodes.
258 |
259 | 1. AES encryption/decryption
260 |
261 | 1. inband transceiving
262 |
263 | To support handover, mimicsocks uses these nodes for inband communication between
264 | local & remote ends.
265 |
266 | For a wormhole in distributed mode, handover means during the lifetime of local socket, new socket are
267 | dynamically created to take over the job from elder remote socket. In a tiny time
268 | frame in traffic from local to remote and traffic from remote to local occur in
269 | two separate sockets, so yes, it is baton handover.
270 |
271 | For a wormhole in aggregated mode, handover means during the lifetime of this single socket,
272 | a new socket is dynamically created to take over the job from elder one, and then
273 | the elder one is closed.
274 |
275 | 1. mimic
276 |
277 | This node learns the statistical characteristics of the ongoing traffic, and
278 | then manipluates packages size and delay to make them follow a randomly-choosen
279 | distribution.
280 |
281 | Package size may follow one of these distributions: constant, uniform or Gaussian.
282 | Package delay may follow one of these distributions: constant, uniform, Gaussian or exponential.
283 |
284 | This node does not need a A-1 in the other end.
285 |
286 | ----
287 | [1] [The Random Forest Based Detection of Shadowsock's Traffic](http://ieeexplore.ieee.org/document/8048116/)
288 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # Mimicsocks: 又一个 TCP 代理
2 |
3 | [](https://travis-ci.org/foldl/mimicsocks)
4 |
5 | Mimicsocks 是 TCP 转发器、中继、隧道、代理,在 Shadowsocks 的启发和文献 [1] 的激励下诞生。
6 |
7 | View [English version](README.md).
8 |
9 | ```
10 | 处理模块
11 |
12 | +--------+
13 | +----> http <-->
14 | | +--------+
15 | |
16 | +-----------+ 虫 洞 +------------+ | +--------+
17 | | 本端 <- - - - - - - - > 远端 <---+----> socks <-->
18 | +-----------+ +------------+ | +--------+
19 | |
20 | | +--------+
21 | +----> 中继 <-->
22 | +--------+
23 | ```
24 |
25 | ## 特性
26 |
27 | * 可串联
28 |
29 | 多个 Mimicsocks 可以串联在一起构成多跳代理或者类似洋葱路由的玩意儿.
30 |
31 | * 拟态
32 |
33 | Mimicsocks 会调整 TCP 数据包的大小、时延,工作过程中还会随机切换 Socket。
34 |
35 | * 简单
36 |
37 | Mimicsocks 纯用 Erlang/OPT 实现,不依赖其它库。
38 |
39 | ## 总体
40 |
41 | Mimicsocks 就是一个有两个端点(本端和远端)的虫洞。端点由 IP 地址和端口号指定。
42 |
43 | 进入本端的数据会被秘密地传输到远端。数据到达远端后交由数据处理模块处理。目前,Mimicsocks
44 | 有三种处理模块。
45 |
46 | * 简单的 socks4/4a/5 代理服务器
47 |
48 | 用这个东西,Mimicsocks 可以实现跟 Shadowsocks 类似的功能。
49 |
50 | 由于使用了模块化设计,这个 socks4/4a/5 代理服务器可以单独使用:
51 |
52 | `mimicsocks_tcp_listener:start_link([Ip, Port, mimicsocks_remote_socks, [undefined]).`
53 |
54 |
55 | * 简单的 http 代理服务器
56 |
57 | 支持 http/https,支持 http 隧道。
58 |
59 | 这个http 代理服务器也可以单独使用:
60 |
61 | `mimicsocks_tcp_listener:start_link([Ip, Port, mimicsocks_remote_http, [undefined]]).`
62 |
63 | * 中继
64 |
65 | 本中继可以把数据转发到指点的 IP 地址和端口。转发到另一个 Mimicsocks 的本端即可以搭建多级
66 | Mimicsocks。当然,也可用该中继将数据转发到自己的 sock5 或者 HTTP 代理服务器。
67 |
68 | ## 内部实现
69 |
70 | 在虫洞的两端各有一系列处理节点。每个节点通过 `{recv, From, Data}` 消息接收来自
71 | 前级节点的数据,处理后再通过 `{recv, From, NewData}` 消息把数据传递给后级节点。
72 | 一般而言,如果在一端有一个节点 A,那么在另一端就有一个对应的节点 A-1
73 | 来抵消 A 的作用,将数据还原。
74 |
75 | 有两种虫洞,聚合式和分布式。分布式虫洞会为每一个本端出现的连接请求建立一个单独
76 | 的到远端的连接,而聚合式虫洞在两端之间维持一个 TCP 连接,所有的数据
77 | 都聚合到该连接上传输。
78 |
79 | 目前 Mimicsocks 有以下处理节点。
80 |
81 | 1. AES 加解密
82 |
83 | 1. 带内传输
84 |
85 | 为实现切换功能,mimicsocks 通过带内传输实现本端和远端之间的控制信息传递。
86 |
87 | 1. 拟态
88 |
89 | 主动调整虫洞内传输的数据包大小、延迟,改变其统计特性。
90 |
91 | 本节点在对端不需要相应的 A-1 节点.
92 |
93 | ## Get Started
94 |
95 | Take Windows as an example.
96 |
97 | 1. Install Erlang/OTP 20.0 or newer (seriously).
98 |
99 | Suppose it's installed in `C:\Program files\erl9.0`
100 |
101 | 1. Download this package Erlang's lib directory: `C:\Program files\erl9.0\lib`.
102 |
103 | 1. Start werl.exe, and build mimicsocks:
104 |
105 | ```shell
106 | Eshell V9.0 (abort with ^G)
107 | 1> cd("../lib/mimicsocks").
108 | C:/Program files/erl9.0/lib/mimicsocks
109 | ok
110 | 2> make:all().
111 | ......
112 | up_to_date
113 | ```
114 |
115 | 1. Config mimicsocks. Note that both ends share the same config file.
116 |
117 | Open `C:\Program files\erl9.0\lib\mimicsocks\priv\mimicsocks.cfg` and edit it:
118 |
119 | ```erlang
120 | {default, [ % name of this wormhole
121 | {local, {{127,0,0,1}, 8888}}, % local end address
122 | {remote, {{127,0,0,1}, 9999}}, % remote end address
123 | {wormhole, aggregated}, % can be aggregated (RECOMMENDED) or distributed
124 | {remote_handler, socks}, % socks, http, or relay (see below)
125 | {remote_extra_ports, [9998]}, % extra ports on remote end for handover
126 | {key, <<41,186,113,221,126,106,146,106,246,112,85,183,56,79,159,
127 | 111,44,174,51,120, 240,217,55,13,205,149,176,82,120,6,61,131>>}
128 | % possible key length: 128, 192, or 256 bits
129 | % use following code to generate a new key:
130 | % io:format("~p~n",[crypto:strong_rand_bytes(256 div 8)]).
131 | ]
132 | }.
133 | ```
134 |
135 | To use the relay handler, one can define another wormhole, then use it:
136 | ```erlang
137 | {default, [ % name of this wormhole
138 | ...
139 | {remote_handler, {relay, another}},
140 | ...
141 | ]
142 | }.
143 | {another, [ % name of another wormhole
144 | ...
145 | ]
146 | }.
147 | ```
148 |
149 | Or just relay to another address:
150 | ```erlang
151 | {default, [ % name of this wormhole
152 | ...
153 | {remote_handler, {relay, {Ip, Port}},
154 | ...
155 | ]
156 | }.
157 | ```
158 |
159 | 1. Launch mimicsocks:
160 |
161 | On remote & local machine:
162 | ```shell
163 | erl -eval "application:ensure_all_started(mimicsocks)" -noshell -detached
164 | ```
165 |
166 | For aggregated ones, remote end should be started ahead of local end.
167 |
168 | ----
169 | [1] [The Random Forest Based Detection of Shadowsock's Traffic](http://ieeexplore.ieee.org/document/8048116/)
170 |
--------------------------------------------------------------------------------
/ebin/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 | !mimicsocks.app
--------------------------------------------------------------------------------
/ebin/mimicsocks.app:
--------------------------------------------------------------------------------
1 | {application, mimicsocks,
2 | [{description, "just another TCP proxy"},
3 | {vsn, "1.0.0"},
4 | {modules, [mimicsocks_app, mimicsocks_cfg, mimicsocks_crypt, mimicsocks_inband_recv,
5 | mimicsocks_inband_send, mimicsocks_local, mimicsocks_local_agg,
6 | mimicsocks_mimic, mimicsocks_remote, mimicsocks_remote_agg, mimicsocks_remote_ho,
7 | mimicsocks_remote_relay, mimicsocks_remote_socks, mimicsocks_sup,
8 | mimicsocks_tcp_listener]},
9 | {registered, [mimicsocks_cfg]},
10 | {applications, [kernel, stdlib, crypto]},
11 | {mod, {mimicsocks_app,[]}},
12 | {env, [{log, []}]}
13 | ]}.
--------------------------------------------------------------------------------
/include/mimicsocks.hrl:
--------------------------------------------------------------------------------
1 | %% logging
2 | -define(INFO(Format, L), error_logger:info_msg("~p: " ++ Format, [?MODULE | L])).
3 | -define(WARNING(Format, L), error_logger:warning_msg("~p: " ++ Format, [?MODULE | L])).
4 | -define(ERROR(Format, L), error_logger:error_msg("~p: " ++ Format, [?MODULE | L])).
5 |
6 | -define(MIMICSOCKS_HELLO_SIZE, 16). % IVEC
7 |
8 | % all TCP payload are protected by 256 AES stream cipher, including in-band
9 | % transmission, excluding IVEC
10 |
11 | -define(MIMICSOCKS_INBAND_PAYLOAD, 8).
12 | -define(MIMICSOCKS_INBAND_HMAC, 8).
13 | -define(MIMICSOCKS_INBAND_SIZE, (?MIMICSOCKS_INBAND_HMAC + ?MIMICSOCKS_INBAND_PAYLOAD)).
14 | -define(MIMICSOCKS_INBAND_NOP, 0).
15 | -define(MIMICSOCKS_INBAND_HO_L2R, 1).
16 | -define(MIMICSOCKS_INBAND_HO_R2L, 2).
17 | -define(MIMICSOCKS_INBAND_HO_COMPLETE_R2L, 3).
18 | -define(MIMICSOCKS_INBAND_HO_COMPLETE_L2R, 4).
19 |
20 | % mimicsocks use in-band packages for client-server communication:
21 | % in band packge ::= hmac(sha, key, commands, 8) | command0, ...
22 | % where, command0, command1, ... are 0-padded to 32bytes
23 | %
24 | % Command definitions:
25 | % +--------------------------+
26 | % | 0 | variable length |
27 | % +-----+--------------------+
28 | % | cmd | parameters |
29 | % +--------------------------+
30 |
31 | % command 1: hand-over (local -> remote)
32 | % params ::= target port 16-bit-int/big-endian%
33 | %
34 | % command 2: hand-over (remote -> local)
35 | % command 3: hand-over complete (remote -> local)
36 | % command 4: hand-over complete (local -> remote)
37 | % params ::= Null
38 |
39 | %
40 | % a baton hand-over machanism for socket. it can be initiated by local at anytime.
41 | %
42 | % step 1: LOCAL:
43 | % connect to another port on remote (rsock2), and use ID(n) to identify itself;
44 | % start tapping rsock
45 | %
46 | % Generation of ID(n): n is the No. of handover, while No. 0 is the initial connection
47 | % Note: ID(0) is sent to remote immediately after IVec used for authentication
48 | %
49 | % ID(0) = hmac(IVec, 16)
50 | % ID(n) = hmac(ID(n-1), 16), n >= 1
51 | %
52 | % step 2: REMOTE:
53 | % after rsock2 is accepted, remote starts tapping rsock,
54 | % send HO-R2L on rsock & R2L traffic is switched to rsock2
55 | %
56 | % step 3: LOCAL:
57 | % once HO-R2L is received on rsock, switch R2L traffic to rsock2, and,
58 | % send HO-COMPLETE-L2R on sock & L2R traffic is switched to rsock2
59 | %
60 | % step 4: REMOTE:
61 | % received HO-COMPLETE-L2R is received, switch L2R traffic to rsock2 and
62 | % close rsock, HO completed.
63 | %
64 |
65 | %% defintions for aggregated transmission
66 | %doc NOP command
67 | % <>
68 | -define(AGG_CMD_NOP, 0).
69 | %doc New socket
70 | % <>
71 | -define(AGG_CMD_NEW_SOCKET, 1).
72 | %doc Close socket
73 | % <>
74 | -define(AGG_CMD_CLOSE_SOCKET, 2).
75 | %doc DATA
76 | % <>
77 | -define(AGG_CMD_DATA, 3).
78 | %doc small DATA
79 | % <>
80 | -define(AGG_CMD_SMALL_DATA, 4).
81 |
82 |
--------------------------------------------------------------------------------
/priv/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore everything in this directory
2 | *
3 | # Except this file
4 | !.gitignore
5 | !mimicsocks.cfg
--------------------------------------------------------------------------------
/priv/mimicsocks.cfg:
--------------------------------------------------------------------------------
1 | {default, % name of this wormhole
2 | [
3 | {server, {{127,0,0,1}, 8888}}, % local end address
4 | {wormhole_remote, {{127,0,0,1}, 9999}}, % remote end address
5 | {wormhole, aggregated}, % can be aggregated or distributed
6 | {handler, socks5}, % handler
7 | {wormhole_extra_ports, [9998]}, % extra ports on remote end for handover
8 | % possible key length: 128, 192, or 256 bits
9 | % use following code to generate a new key:
10 | % io:format("~p~n",[crypto:strong_rand_bytes(256 div 8)]).
11 | {key, <<1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1>>}
12 | ]
13 | }.
14 |
--------------------------------------------------------------------------------
/src/mimicsocks_app.erl:
--------------------------------------------------------------------------------
1 | %@doc a application
2 | %@author foldl
3 | -module(mimicsocks_app).
4 |
5 | -behaviour(application).
6 |
7 | -export([start/2, stop/1]).
8 |
9 | start(_Type, _Args) ->
10 | mimicsocks_sup:start_link().
11 |
12 | stop(_State) ->
13 | ok.
--------------------------------------------------------------------------------
/src/mimicsocks_cfg.erl:
--------------------------------------------------------------------------------
1 | %@doc config server
2 | %@author foldl
3 | -module(mimicsocks_cfg).
4 |
5 | -include("mimicsocks.hrl").
6 |
7 | -behaviour(gen_server).
8 |
9 | -export([start_link/1, stop/0, get_value/2, get_server/1, list_servers/0, get_all/0]).
10 | -export([register_remote/2, get_remote/1, deregister_remote/1]).
11 |
12 | % gen-server callback functions
13 | -export([init/1, terminate/2,
14 | handle_call/3, handle_cast/2, handle_info/2, code_change/3]).
15 |
16 | -record(state,
17 | {
18 | cfg,
19 | table
20 | }).
21 |
22 | start_link(Config) when is_atom(Config) ->
23 | CfgFile = filename:join(code:priv_dir(mimicsocks), atom_to_list(Config) ++ ".cfg"),
24 | gen_server:start_link({local, ?MODULE}, ?MODULE, [CfgFile], []).
25 |
26 | stop() -> gen_server:stop(?MODULE).
27 |
28 | % api
29 | get_all() -> gen_server:call(?MODULE, get_all).
30 | list_servers() -> gen_server:call(?MODULE, list_servers).
31 | get_server(Server) -> gen_server:call(?MODULE, {get_server, Server}).
32 | get_value(Server, Key) -> gen_server:call(?MODULE, {get_value, Server, Key}).
33 | register_remote(ID, Pid) -> gen_server:call(?MODULE, {register_remote, ID, Pid}).
34 | get_remote(ID) -> gen_server:call(?MODULE, {get_remote, ID}).
35 | deregister_remote(ID) -> gen_server:call(?MODULE, {deregister_remote, ID}).
36 |
37 | % callback functions
38 | init([CfgFile]) ->
39 | {ok, Cfg} = file:consult(CfgFile),
40 | Tid = ets:new(table, []),
41 | {ok, #state{cfg = Cfg, table = Tid}}.
42 |
43 | handle_call(get_all, _From, #state{cfg = Cfg} = State) ->
44 | {reply, Cfg, State};
45 | handle_call(list_servers, _From, #state{cfg = Cfg} = State) ->
46 | S = [element(1, X) || X <- Cfg],
47 | {reply, S, State};
48 | handle_call({get_server, Server}, _From, #state{cfg = Cfg} = State) ->
49 | S = proplists:get_value(Server, Cfg),
50 | {reply, S, State};
51 | handle_call({get_value, Server, Key}, _From, #state{cfg = Cfg} = State) ->
52 | R = case proplists:get_value(Server, Cfg) of
53 | L when is_list(L) -> proplists:get_value(Key, L);
54 | undefined -> undefined
55 | end,
56 | {reply, R, State};
57 | handle_call({register_remote, ID, Pid}, _From, #state{table = Tid} = State) ->
58 | ets:insert(Tid, {ID, Pid}),
59 | {reply, ok, State};
60 | handle_call({deregister_remote, ID}, _From, #state{table = Tid} = State) ->
61 | ets:match_delete(Tid, {ID, '_'}),
62 | {reply, ok, State};
63 | handle_call({get_remote, ID}, _From, #state{table = Tid} = State) ->
64 | case ets:lookup(Tid, ID) of
65 | [{ID, Pid}] -> {reply, {ok, Pid}, State};
66 | _ -> {reply, undefined, State}
67 | end.
68 |
69 | handle_cast(Request, State) ->
70 | ?ERROR("unexpected cast msg: ~p", [Request]),
71 | {noreply, State}.
72 |
73 | handle_info(Info, State) ->
74 | ?ERROR("unexpected info msg: ~p", [Info]),
75 | {noreply, State}.
76 |
77 | terminate(_Reason, #state{table = Tid} = _State) ->
78 | ets:delete(Tid),
79 | normal.
80 |
81 | code_change(_OldVsn, State, _Extra) -> {ok, State}.
--------------------------------------------------------------------------------
/src/mimicsocks_crypt.erl:
--------------------------------------------------------------------------------
1 | %doc en/decrypt module
2 | %author foldl
3 | -module(mimicsocks_crypt).
4 |
5 | -export([start_link/2, stop/1, recv/2, hmac_sha/3, init_aes_ctr_enc/2, init_aes_ctr_dec/2]).
6 |
7 | -include("mimicsocks.hrl").
8 |
9 | -record(state, {
10 | output,
11 | cipher
12 | }).
13 |
14 | start_link(encrypt, [Output, CipherState]) ->
15 | spawn_link(fun() -> loop_encrypt(#state{output = Output, cipher = CipherState}) end);
16 | start_link(decrypt, [Output, CipherState]) ->
17 | spawn_link(fun() -> loop_decrypt(#state{output = Output, cipher = CipherState}) end).
18 |
19 | stop(Pid) -> Pid ! stop.
20 |
21 | recv(Pid, Data) -> Pid ! {recv, self(), Data}.
22 |
23 | loop_encrypt(#state{output = Output, cipher = CipherState} = State) ->
24 | receive
25 | {recv, _From, Data} ->
26 | {NewCipherState, CipherText} = stream_encrypt(CipherState, Data),
27 | Output ! {recv, self(), CipherText},
28 | loop_encrypt(State#state{cipher = NewCipherState});
29 | Msg ->
30 | handle_msg(fun loop_encrypt/1, Msg, State)
31 | end.
32 |
33 | loop_decrypt(#state{output = Output, cipher = CipherState} = State) ->
34 | receive
35 | {recv, _From, Data} ->
36 | {NewCipherState, CipherText} = stream_decrypt(CipherState, Data),
37 | Output ! {recv, self(), CipherText},
38 | loop_decrypt(State#state{cipher = NewCipherState});
39 | Msg ->
40 | handle_msg(fun loop_decrypt/1, Msg, State)
41 | end.
42 |
43 | handle_msg(Loop, {flush, Ref, _From}, #state{output = Output} = State) ->
44 | Output ! {flush, Ref, self()},
45 | Loop(State);
46 | handle_msg(_Loop, stop, #state{output = Output} = _State) -> Output ! stop;
47 | handle_msg(Loop, Msg, State) ->
48 | ?WARNING("unexpected msg: ~p", [Msg]),
49 | Loop(State).
50 |
51 | -ifndef(OTP_RELEASE).
52 | -define(OTP_RELEASE, 20).
53 | -endif.
54 |
55 | -if(?OTP_RELEASE >= 24).
56 | init_aes_ctr_enc(Key, IVec) -> crypto:crypto_init(aes_256_ctr, Key, IVec, true).
57 | init_aes_ctr_dec(Key, IVec) -> crypto:crypto_init(aes_256_ctr, Key, IVec, false).
58 |
59 | stream_encrypt(CipherState, Data) -> {CipherState, crypto:crypto_update(CipherState, Data)}.
60 | stream_decrypt(CipherState, Data) -> {CipherState, crypto:crypto_update(CipherState, Data)}.
61 |
62 | hmac_sha(Key, Data, MacLength) -> crypto:macN(hmac, sha, Key, Data, MacLength).
63 | -else.
64 | init_aes_ctr_enc(Key, IVec) -> crypto:stream_init(aes_ctr, Key, IVec).
65 | init_aes_ctr_dec(Key, IVec) -> crypto:stream_init(aes_ctr, Key, IVec).
66 |
67 | stream_encrypt(CipherState, Data) -> crypto:stream_encrypt(CipherState, Data).
68 | stream_decrypt(CipherState, Data) -> crypto:stream_decrypt(CipherState, Data).
69 |
70 | hmac_sha(Key, Data, MacLength) -> crypto:hmac(sha, Key, Data, MacLength).
71 | -endif.
72 |
--------------------------------------------------------------------------------
/src/mimicsocks_inband_recv.erl:
--------------------------------------------------------------------------------
1 | %@doc inband transmission module
2 | %@author foldl
3 | -module(mimicsocks_inband_recv).
4 |
5 | -export([start_link/1, stop/1, recv/2, set_key/2, tapping/2]).
6 | -export([subtract/2]).
7 |
8 | -include("mimicsocks.hrl").
9 |
10 | -record(state, {
11 | output,
12 | cmd_handler,
13 | key,
14 | tapping = false,
15 | buff = <<>>
16 | }).
17 |
18 | start_link([Output, Handler]) ->
19 | spawn_link(fun() -> loop(#state{output = Output, cmd_handler = Handler}) end).
20 |
21 | stop(Pid) -> Pid ! stop.
22 |
23 | recv(Pid, Data) -> Pid ! {recv, self(), Data}.
24 | set_key(Pid, Key) -> Pid ! {set_key, Key}.
25 | tapping(Pid, Flag) -> Pid ! {tapping, Flag}.
26 |
27 | search_cmds(Start, All, #state{output = Output, cmd_handler = Handler,
28 | key = Key} = State) ->
29 | case All of
30 | <> ->
32 | case mimicsocks_crypt:hmac_sha(Key, Cmds, ?MIMICSOCKS_INBAND_HMAC) of
33 | HMAC ->
34 | Output ! {recv, self(), Head},
35 | Handler ! {inband_cmd, self(), Cmds},
36 | search_cmds(0, Rem, State);
37 | _ ->
38 | search_cmds(Start + 1, All, State)
39 | end;
40 | _ ->
41 | NextBuff = case Start > 0 of
42 | true ->
43 | <> = All,
44 | Output ! {recv, self(), Msg},
45 | Rem10;
46 | _ -> All
47 | end,
48 | State#state{buff = NextBuff}
49 | end.
50 |
51 | loop_data(Data, State) when is_list(Data) ->
52 | lists:foldl(fun (Bin, AState) -> loop_data(AState, Bin) end, State, Data);
53 | loop_data(Data, #state{buff = Buff, tapping = true} = State) ->
54 | search_cmds(0, <>, State);
55 | loop_data(Data, #state{output = Output, tapping = false} = State) ->
56 | Output ! {recv, self(), Data},
57 | State.
58 |
59 | loop(State) ->
60 | receive
61 | {tapping, true} ->
62 | loop(State#state{tapping = true});
63 | {tapping, false} ->
64 | State#state.output ! {recv, self(), State#state.buff},
65 | loop(State#state{tapping = false, buff = <<>>});
66 | {recv, _From, Data} ->
67 | loop(loop_data(Data, State));
68 | {set_key, Key} ->
69 | loop(State#state{key = Key});
70 | {flush, Ref, _From} ->
71 | State#state.output ! {flush, Ref, self()},
72 | loop(State);
73 | stop -> State#state.output ! stop;
74 | X ->
75 | ?WARNING("unexpected msg: ~p", [X]),
76 | loop(State)
77 | end.
78 |
79 | subtract(X, Y) when is_integer(X) -> X - Y;
80 | subtract(_X, _Y) -> infinity.
81 |
--------------------------------------------------------------------------------
/src/mimicsocks_inband_send.erl:
--------------------------------------------------------------------------------
1 | %@doc inband transmission module
2 | %@author foldl
3 | -module(mimicsocks_inband_send).
4 |
5 | -export([start_link/1, stop/1, recv/2, set_key/2,
6 | recv_cmd/2, recv_cmd/3, get_buff/1, continue/1]).
7 |
8 | -include("mimicsocks.hrl").
9 |
10 | -import(mimicsocks_inband_recv, [subtract/2]).
11 |
12 | -record(state, {
13 | output,
14 | cmd_handler,
15 | key,
16 | after_cmd, % continue or hold_and_flush
17 | counter = 0,
18 | buff
19 | }).
20 |
21 | start_link([Output, Handler]) ->
22 | spawn_link(fun() -> loop(#state{output = Output, cmd_handler = Handler}) end).
23 |
24 | stop(Pid) -> Pid ! stop.
25 |
26 | recv(Pid, Data) -> Pid ! {recv, self(), Data}.
27 |
28 | set_key(Pid, Key) -> Pid ! {set_key, Key}.
29 | recv_cmd(Pid, Cmd) -> recv_cmd(Pid, Cmd, continue).
30 | continue(Pid) -> Pid ! continue.
31 |
32 | get_buff(Pid) ->
33 | This = self(),
34 | Ref = make_ref(),
35 | Pid ! {get_buff, This, Ref},
36 | receive
37 | {get_buff, Ref, Data} -> Data
38 | end.
39 |
40 | recv_cmd(Pid, Cmd, AfterCmd) ->
41 | if size(Cmd) =< ?MIMICSOCKS_INBAND_PAYLOAD -> ok;
42 | true -> throw({size_too_large, Cmd})
43 | end,
44 | Padding = list_to_binary(lists:duplicate(?MIMICSOCKS_INBAND_PAYLOAD - size(Cmd), 0)),
45 | Ref = make_ref(),
46 | Pid ! {send_cmd, Ref, <>, AfterCmd},
47 | Ref.
48 |
49 | loop_data(State, Data) when is_list(Data) ->
50 | lists:foldl(fun (Bin, AState) -> loop_data(AState, Bin) end, State, Data);
51 | loop_data(#state{buff = L} = State, Data) when is_list(L) ->
52 | State#state{buff = [Data | L]};
53 | loop_data(#state{output = Output} = State, Data) ->
54 | Output ! {recv, self(), Data},
55 | State.
56 |
57 | loop(#state{output = Output} = State) ->
58 | receive
59 | continue ->
60 | case State#state.buff of
61 | L when is_list(L) ->
62 | loop_data(State, lists:reverse(L));
63 | _ -> ok
64 | end,
65 | loop(State#state{buff = undefined});
66 | {recv, _From, Data} ->
67 | loop(loop_data(State, Data));
68 | {set_key, Key} ->
69 | loop(State#state{key = Key});
70 | {send_cmd, Ref, Cmd, After} ->
71 | Key = State#state.key,
72 | HMAC = mimicsocks_crypt:hmac_sha(Key, Cmd, ?MIMICSOCKS_INBAND_HMAC),
73 | Output ! {recv, self(), <>},
74 | NewState = case After of
75 | continue -> State;
76 | hold -> State#state{buff = []}
77 | end,
78 | State#state.cmd_handler ! {cmd_sent, Ref},
79 | loop(NewState);
80 | {get_buff, Pid, Ref} ->
81 | L10 = case State#state.buff of
82 | L when is_list(L) -> lists:reverse(L);
83 | _ -> []
84 | end,
85 | Pid ! {get_buff, Ref, L10},
86 | loop(State);
87 | {flush, Ref, _From} ->
88 | State#state.output ! {flush, Ref, self()},
89 | loop(State);
90 | stop -> State#state.output ! stop;
91 | X ->
92 | ?WARNING("unexpected msg: ~p", [X]),
93 | loop(State)
94 | end.
95 |
--------------------------------------------------------------------------------
/src/mimicsocks_local.erl:
--------------------------------------------------------------------------------
1 | %@doc local socket forwarding node
2 | % local traffic is forwarded and handled by remote node
3 | %@author foldl
4 | -module(mimicsocks_local).
5 |
6 | -include("mimicsocks.hrl").
7 |
8 | -behaviour(gen_statem).
9 |
10 | % api
11 | -export([start_link/1, socket_ready/2]).
12 |
13 | % callbacks
14 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
15 |
16 | % utils
17 | -import(mimicsocks_mimic, [choice/1]).
18 |
19 | % FSM States
20 | -export([
21 | init/3,
22 | forward/3
23 | ]).
24 |
25 | -record(state,
26 | {
27 | channel,
28 | lsock
29 | }
30 | ).
31 |
32 | -define(REMOTE_TCP_OPTS, [{active, true}, {packet, raw}, binary, {reuseaddr, true}]).
33 |
34 | start_link(Args) ->
35 | gen_statem:start_link(?MODULE, Args, []).
36 |
37 | socket_ready(Pid, LSock) when is_pid(Pid), is_port(LSock) ->
38 | gen_tcp:controlling_process(LSock, Pid),
39 | gen_statem:cast(Pid, {socket_ready, LSock}).
40 |
41 | %%%===================================================================
42 | %%% gen_fsm callbacks
43 | %%%===================================================================
44 | init(Args) ->
45 | case mimicsocks_wormhole_local:start_link([self() | Args]) of
46 | {ok, Channel} ->
47 | {ok, init, #state{channel = Channel}};
48 | {error, Reason} -> {stop, Reason}
49 | end.
50 |
51 | callback_mode() ->
52 | state_functions.
53 |
54 | init(cast, {socket_ready, Socket}, StateData) when is_port(Socket) ->
55 | ok = inet:setopts(Socket, [{active, true}, {packet, raw}, binary]),
56 | {next_state, forward, StateData#state{lsock = Socket}};
57 | init(info, Msg, Data) -> handle_info(Msg, init, Data).
58 |
59 | forward(info, Msg, Data) -> handle_info(Msg, forward, Data).
60 |
61 | handle_info({recv, Channel, Data}, _StateName, #state{lsock = Socket, channel = Channel} = StateData) ->
62 | gen_tcp:send(Socket, Data),
63 | {keep_state, StateData};
64 | handle_info({tcp, Socket, Bin}, _StateName, #state{lsock = Socket, channel = Channel} = StateData) ->
65 | mimicsocks_wormhole_local:recv(Channel, Bin),
66 | {keep_state, StateData};
67 | handle_info({tcp_closed, Socket}, _StateName, #state{lsock = Socket} = StateData) ->
68 | {stop, normal, StateData};
69 | handle_info(Info, StateName, StateData) ->
70 | ?ERROR("unexpected ~p in state ~p", [Info, StateName]),
71 | {keep_state, StateData}.
72 |
73 | terminate(_Reason, _StateName, #state{lsock=LSocket,
74 | channel = Channel} = _StateData) ->
75 | (catch gen_tcp:close(LSocket)),
76 | (catch mimicsocks_wormhole_local:stop(Channel)),
77 | ok.
78 |
79 | code_change(_OldVsn, StateName, State, _Extra) ->
80 | {ok, StateName, State}.
--------------------------------------------------------------------------------
/src/mimicsocks_local_agg.erl:
--------------------------------------------------------------------------------
1 | %doc aggregated communication
2 | %author foldl
3 | -module(mimicsocks_local_agg).
4 |
5 | -include("mimicsocks.hrl").
6 |
7 | -behaviour(gen_statem).
8 |
9 | %% API
10 | -export([start_link/1, stop/1, accept/2, accept2/2, socket_ready/2]).
11 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
12 |
13 | -import(mimicsocks_wormhole_local, [show_sock/1]).
14 |
15 | % FSM States
16 | -export([
17 | init/3,
18 | forward/3
19 | ]).
20 |
21 | % utils
22 | -export([parse_full/2, send_data/3]).
23 |
24 | start_link(Args) ->
25 | gen_statem:start_link(?MODULE, Args, []).
26 |
27 | accept(Pid, Socket) ->
28 | ok = gen_tcp:controlling_process(Socket, Pid),
29 | Pid ! {accept, Socket}.
30 |
31 | accept2(Pid, Socket) ->
32 | ok = gen_tcp:controlling_process(Socket, Pid),
33 | Pid ! {accept2, Socket}.
34 |
35 | socket_ready(Pid, LSock) when is_pid(Pid), is_port(LSock) ->
36 | gen_tcp:controlling_process(LSock, Pid),
37 | gen_statem:cast(Pid, {socket_ready, LSock}).
38 |
39 | stop(Pid) -> gen_statem:stop(Pid).
40 |
41 | callback_mode() ->
42 | state_functions.
43 |
44 | -record(state,
45 | {
46 | channel,
47 | wormhole,
48 | t_s2i,
49 | t_i2s,
50 | last_id = -1,
51 | key,
52 | last_timer = undefined,
53 |
54 | buf = <<>>
55 | }
56 | ).
57 |
58 | %% callback funcitons
59 | init([Key]) ->
60 | case init_wormhole_remote(Key) of
61 | {ok, Channel} ->
62 | {ok, init, #state{wormhole = mimicsocks_wormhole_remote,
63 | t_i2s = ets:new(tablei2s, []),
64 | t_s2i = ets:new(tables2i, []),
65 | key = Key,
66 | channel = Channel}};
67 | {error, Reason} -> {stop, Reason}
68 | end;
69 | init(Args) ->
70 | case mimicsocks_wormhole_local:start_link([self() | Args]) of
71 | {ok, Channel} ->
72 | {ok, init, #state{channel = Channel,
73 | wormhole = mimicsocks_wormhole_local,
74 | t_i2s = ets:new(tablei2s, []),
75 | t_s2i = ets:new(tables2i, [])
76 | }};
77 | {error, Reason} -> {stop, Reason}
78 | end.
79 |
80 | init(info, {accept2, Socket}, #state{channel = Channel} = State) ->
81 | mimicsocks_wormhole_remote:socket_ready(Channel, Socket),
82 | {next_state, forward, State};
83 | init(info, Msg, StateData) -> handle_info(Msg, init, StateData).
84 |
85 | forward(info, Info, State) -> handle_info(Info, forward, State).
86 |
87 | % here, we use monitor but not fail-restart, because on some VPS,
88 | % gen_tcp:listen fails during restart.
89 | handle_info({'DOWN', _Ref, process, Channel, _Reason}, _StateName,
90 | #state{channel = Channel, key = Key} = State) ->
91 | % clean-up
92 | clean_up(State),
93 |
94 | % re-initialize
95 | case init_wormhole_remote(Key) of
96 | {ok, NewChannel} ->
97 | {next_state, init, State#state{channel = NewChannel, buf = <<>>}};
98 | {error, Reason} -> {stop, Reason}
99 | end;
100 | handle_info({accept2, Socket}, _StateName, #state{key = Key,
101 | wormhole = mimicsocks_wormhole_remote} = State) ->
102 | % clean-up
103 | clean_up(State),
104 |
105 | % re-initialize
106 | case init_wormhole_remote(Key) of
107 | {ok, NewChannel} ->
108 | mimicsocks_wormhole_remote:socket_ready(NewChannel, Socket),
109 | {next_state, forward, State#state{channel = NewChannel, buf = <<>>}};
110 | {error, Reason} -> {stop, Reason}
111 | end;
112 | handle_info({accept, Socket}, _StateName, #state{t_i2s = Ti2s, t_s2i = Ts2i,
113 | channel = Channel, wormhole = W,
114 | last_id = N,
115 | last_timer = LastTimer} = State) ->
116 | State20 = case inet:setopts(Socket, [{active, true}]) of
117 | ok ->
118 | Port = make_id(Ti2s, N, N),
119 | ets:insert(Ti2s, {Port, Socket}),
120 | ets:insert(Ts2i, {Socket, Port}),
121 | W:suspend_mimic(Channel, 5000),
122 | W:recv(Channel, <>),
123 | State#state{last_id = Port, last_timer = update_ho_timer(LastTimer)};
124 | _Error ->
125 | gen_tcp:close(Socket),
126 | State
127 | end,
128 | {keep_state, State20};
129 | handle_info({tcp, Socket, Bin}, _StateName, #state{t_s2i = Ts2i, channel = Channel} = State) ->
130 | case ets:lookup(Ts2i, Socket) of
131 | [{Socket, Id}] ->
132 | send_data(Channel, Id, Bin);
133 | _ -> ?ERROR("socket (~p) not in db", [show_sock(Socket)])
134 | end,
135 | {keep_state, State};
136 | handle_info({tcp_closed, Socket}, _StateName, #state{t_s2i = Ts2i, t_i2s = Ti2s, channel = Channel} = State) ->
137 | case ets:lookup(Ts2i, Socket) of
138 | [{Socket, ID}] ->
139 | Channel ! {recv, self(), <>},
140 | ets:match_delete(Ts2i, {Socket, '_'}),
141 | ets:match_delete(Ti2s, {ID, '_'});
142 | _ -> ok
143 | end,
144 | {keep_state, State};
145 | handle_info({recv, Channel, Bin}, _StateName, #state{channel = Channel, buf = Buf} = State) ->
146 | All = <>,
147 | {ok, Frames, Rem} = parse_full(All, []),
148 | State10 = lists:foldl(fun handle_cmd/2, State, Frames),
149 | NewState = State10#state{buf = Rem},
150 | {keep_state, NewState};
151 | handle_info({tcp_error, Socket, _Reason}, _StateName, #state{t_s2i = Ts2i, t_i2s = Ti2s, channel = Channel} = State) ->
152 | case ets:lookup(Ts2i, Socket) of
153 | [{Socket, ID}] ->
154 | Channel ! {recv, self(), <>},
155 | ets:match_delete(Ts2i, {Socket, '_'}),
156 | ets:match_delete(Ti2s, {ID, '_'});
157 | _ -> ok
158 | end,
159 | {keep_state, State};
160 | handle_info(ho_timer, _StateName, #state{channel = Channel, wormhole = W} = State) ->
161 | W:handover_now(Channel),
162 | {keep_state, State#state{last_timer = undefined}};
163 | handle_info(stop, _StateName, State) ->
164 | {stop, normal, State};
165 | handle_info(Info, _StateName, State) ->
166 | ?WARNING("unexpected msg: ~p", [Info]),
167 | {keep_state, State}.
168 |
169 | terminate(_Reason, _StateName, #state{channel = Channel, t_i2s = T1, t_s2i = T2, wormhole = W} =
170 | _State) ->
171 | (catch W:stop(Channel)),
172 | (catch ets:delete(T1)),
173 | (catch ets:delete(T2)),
174 | normal.
175 |
176 | code_change(_OldVsn, OldStateName, OldStateData, _Extra) ->
177 | {ok, OldStateName, OldStateData}.
178 |
179 | %helpers
180 | clean_up(#state{t_i2s = Ti2s, t_s2i = Ts2i,
181 | channel = Channel,
182 | wormhole = mimicsocks_wormhole_remote} = _State) ->
183 | (catch mimicsocks_wormhole_remote:stop(Channel)),
184 | (catch ets:delete_all_objects(Ti2s)),
185 | (catch ets:delete_all_objects(Ts2i)).
186 |
187 | init_wormhole_remote(Key) ->
188 | case mimicsocks_wormhole_remote:start_link([self(), Key]) of
189 | {ok, Channel} ->
190 | unlink(Channel),
191 | erlang:monitor(process, Channel),
192 | {ok, Channel};
193 | {error, Reason} -> {stop, Reason}
194 | end.
195 |
196 | send_data(Send, Id, List) when is_list(List) ->
197 | lists:foreach(fun (X) -> send_data(Send, Id, X) end, List);
198 | send_data(_Send, _Id, <<>>) -> ok;
199 | send_data(Send, Id, Bin) when size(Bin) < 256 ->
200 | Len = size(Bin),
201 | Send ! {recv, self(), <>};
202 | send_data(Send, Id, Bin) when size(Bin) < 65536 ->
203 | Len = size(Bin),
204 | Send ! {recv, self(), <>};
205 | send_data(Send, Id, Bin) ->
206 | <> = Bin,
207 | Send ! {recv, self(), <>},
208 | send_data(Send, Id, Rem).
209 |
210 | parse_data(<<>>) -> incomplete;
211 | parse_data(<>) ->
212 | case Rem of
213 | <<_Reserved:16/big, Len, _Dummy:Len/binary, Rem10/binary>> ->
214 | {?AGG_CMD_NOP, Rem10};
215 | _ -> incomplete
216 | end;
217 | parse_data(<>) ->
218 | case Rem of
219 | <> ->
220 | {{?AGG_CMD_NEW_SOCKET, Id}, Rem10};
221 | _ -> incomplete
222 | end;
223 | parse_data(<>) ->
224 | case Rem of
225 | <> ->
226 | {{?AGG_CMD_CLOSE_SOCKET, Id}, Rem10};
227 | _ -> incomplete
228 | end;
229 | parse_data(<>) ->
230 | case Rem of
231 | <> ->
232 | {{?AGG_CMD_DATA, Id, Data}, Rem10};
233 | _ -> incomplete
234 | end;
235 | parse_data(<>) ->
236 | case Rem of
237 | <> ->
238 | {{?AGG_CMD_DATA, Id, Data}, Rem10};
239 | _ -> incomplete
240 | end;
241 | parse_data(<>) ->
242 | {bad_command, Cmd}.
243 |
244 | parse_full(Data, Acc) ->
245 | case parse_data(Data) of
246 | incomplete -> {ok, lists:reverse(Acc), Data};
247 | {bad_command, Value} -> {{bad_command, Value}, Acc, Data};
248 | {Frame, Rem} -> parse_full(Rem, [Frame | Acc])
249 | end.
250 |
251 | handle_cmd(?AGG_CMD_NOP, State) -> State;
252 | handle_cmd({?AGG_CMD_NEW_SOCKET, _Id}, State) ->
253 | ?ERROR("unexpected ?AGG_CMD_NEW_SOCKET", []),
254 | State;
255 | handle_cmd({?AGG_CMD_CLOSE_SOCKET, Id}, #state{t_s2i = Ts2i, t_i2s = Ti2s} = State) ->
256 | case ets:lookup(Ti2s, Id) of
257 | [{ID, Socket}] ->
258 | ets:match_delete(Ts2i, {Socket, '_'}),
259 | ets:match_delete(Ti2s, {ID, '_'});
260 | _ -> ok
261 | end,
262 | State;
263 | handle_cmd({?AGG_CMD_DATA, Id, Data}, #state{t_i2s = Ti2s, last_timer = LastTimer} = State) ->
264 | case ets:lookup(Ti2s, Id) of
265 | [{Id, Socket}] ->
266 | gen_tcp:send(Socket, Data);
267 | _ -> ok
268 | end,
269 | State#state{last_timer = cancel_timer(LastTimer)}.
270 |
271 | make_id(Ti2s, N1, N0) ->
272 | N2 = (N1 + 1) rem 65536,
273 | true = (N2 =/= N0),
274 | case ets:lookup(Ti2s, N2) of
275 | [{_ID, _Socket}] ->
276 | make_id(Ti2s, N2, N0);
277 | _ ->
278 | N2
279 | end.
280 |
281 | update_ho_timer(undefined) ->
282 | {ok, TRef} = timer:send_after((rand:uniform(10) + 10) * 1000, ho_timer),
283 | TRef;
284 | update_ho_timer(Old) ->
285 | cancel_timer(Old),
286 | update_ho_timer(undefined).
287 |
288 | cancel_timer(undefined) -> undefined;
289 | cancel_timer(Old) ->
290 | timer:cancel(Old),
291 | undefined.
--------------------------------------------------------------------------------
/src/mimicsocks_mimic.erl:
--------------------------------------------------------------------------------
1 | %@doc change the stat. characters of tcp packages
2 | %@author foldl
3 | -module(mimicsocks_mimic).
4 |
5 | -behaviour(gen_server).
6 |
7 | %% API
8 | -export([start_link/1, stop/1, recv/2, flush/1, change/1, change/2, suspend/2]).
9 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
10 |
11 | %helpers
12 | -export([choice/1]).
13 |
14 | -include("mimicsocks.hrl").
15 |
16 | -define(EPSILON, 1/1000000.0).
17 |
18 | -record(state,
19 | {
20 | output,
21 | size_dist, % identity, constant, uniform, gaussian
22 | delay_dist, % identity, constant, uniform, gaussian, poission
23 | queue = queue:new(),
24 | total,
25 | create_t,
26 | last_recv_t,
27 | last_send_t,
28 | size_esti, % welford or iir
29 | delay_esti,
30 | suspended = false
31 | }).
32 |
33 | %@doc online mean & variance estimator
34 | -record(param_esti,
35 | {
36 | algo,
37 | state
38 | }).
39 |
40 | stop(Pid) -> gen_server:call(Pid, stop).
41 |
42 | flush(Pid) -> Pid ! flush.
43 |
44 | suspend(Pid, MilliSec) -> Pid ! {suspend, MilliSec}.
45 |
46 | start_link(Args) -> gen_server:start_link(?MODULE, Args, []).
47 |
48 | recv(Pid, Data) when is_binary(Data) ->
49 | Pid ! {recv, self(), Data};
50 | recv(Pid, Data) when is_list(Data) ->
51 | [recv(Pid, X) || X <- Data].
52 |
53 | change(Pid) -> change(Pid, {rand_size_model(), rand_delay_model()}).
54 |
55 | change(Pid, {SizeModel, DelayModel}) -> Pid ! {change, SizeModel, DelayModel}.
56 |
57 | %% callback funcitons
58 | init([Output]) ->
59 | init([Output, rand_size_model(), rand_delay_model(), iir]);
60 | init([Output, SizeModel, DelayModel, Estimator]) ->
61 | T = cur_tick(),
62 | {ok, #state{
63 | output = Output,
64 | size_dist = SizeModel,
65 | delay_dist = DelayModel,
66 | queue = queue:new(),
67 | total = 0,
68 | create_t = T,
69 | last_recv_t = T,
70 | last_send_t = T,
71 | size_esti = esti_init(Estimator),
72 | delay_esti = esti_init(Estimator),
73 | suspended = false
74 | }}.
75 |
76 | handle_call(stop, _From, State) ->
77 | {stop, normal, stopped, State};
78 | handle_call(_Request, _From, State) ->
79 | {reply, ok, State}.
80 |
81 | handle_cast(_Msg, State) ->
82 | {noreply, State}.
83 |
84 | handle_info({flush, Ref, _From}, #state{output = Output} = State) ->
85 | R = handle_info0(flush, State),
86 | Output ! {flush, Ref, self()},
87 | R;
88 | handle_info(resume, State) ->
89 | {noreply, State#state{suspended = false}};
90 | handle_info({suspend, MilliSec}, State) ->
91 | timer:send_after(MilliSec, resume),
92 | handle_info0(flush, State#state{suspended = true});
93 | handle_info({change, SizeModel, DelayModel}, State) ->
94 | {noreply, State#state{size_dist = SizeModel, delay_dist = DelayModel}};
95 | handle_info(stop, State) ->
96 | {stop, normal, State};
97 | handle_info(Info, #state{suspended = Suspended} = State) ->
98 | {noreply, NewState} = handle_info0(Info, State),
99 | case Suspended of
100 | true ->
101 | handle_info0(flush, NewState);
102 | _ ->
103 | case queue:is_empty(NewState#state.queue) of
104 | false -> schedule(NewState);
105 | _ -> ok
106 | end,
107 | {noreply, NewState}
108 | end.
109 |
110 | handle_info0({recv, _From, Data}, #state{last_recv_t = LastT, delay_esti = DelayEsti,
111 | size_esti = SizeEsti,
112 | queue = Q, total = Total} = State) ->
113 | T = cur_tick(),
114 | NewDelayEsti = esti_run(T - LastT, DelayEsti),
115 | NewSizeEsti = esti_run(size(Data), SizeEsti),
116 | NewState = State#state{last_recv_t = T,
117 | delay_esti = NewDelayEsti,
118 | size_esti = NewSizeEsti,
119 | total = Total + size(Data),
120 | queue = queue:in(Data, Q)},
121 | {noreply, NewState};
122 | handle_info0(flush, State) ->
123 | flush0(State),
124 | {noreply, State#state{queue = queue:new(), total = 0}};
125 | handle_info0({flush, N}, #state{output = Output, queue = Q, total = Total} = State) ->
126 | N2 = min(N, queue:len(Q)),
127 | Self = self(),
128 | {NewQ, SendTotal} =
129 | lists:foldl(fun (_, {AQueue, Acc}) ->
130 | {{value, Bin}, Q2} = queue:out(AQueue),
131 | Output ! {recv, Self, Bin},
132 | {Q2, Acc + size(Bin)}
133 | end, {Q, 0},
134 | lists:seq(1, N2)),
135 | {noreply, State#state{queue = NewQ, total = Total - SendTotal}};
136 | handle_info0({schedule, Size}, State) ->
137 | NewState = schedule_send(Size, State),
138 | {noreply, NewState}.
139 |
140 | terminate(_Reason, State) ->
141 | flush0(State),
142 | normal.
143 |
144 | code_change(_OldVsn, State, _Extra) ->
145 | {ok, State}.
146 |
147 | % ----------------------
148 | % helpers
149 | % ----------------------
150 |
151 | flush0(#state{output = Output, queue = Q} = _State) ->
152 | Pid = self(),
153 | lists:foreach(fun (X) -> Output ! {recv, Pid, X} end, queue:to_list(Q)).
154 |
155 | cur_tick() -> erlang:monotonic_time(millisecond).
156 |
157 | schedule_send(Size, State) when Size =< 0 -> State;
158 | schedule_send(Size, #state{output = Output, queue = Q, total = Total} = State) ->
159 | case queue:out(Q) of
160 | {empty, _Q2} -> State;
161 | {{value, Bin}, Q2} ->
162 | SZ = size(Bin),
163 | case Size >= SZ of
164 | true ->
165 | Output ! {recv, self(), Bin},
166 | schedule_send(Size - SZ, State#state{queue = Q2, total = Total - SZ});
167 | _ ->
168 | <> = Bin,
169 | Output ! {recv, self(), Send},
170 | State#state{queue = queue:in_r(Rem, Q2), total = Total - Size}
171 | end
172 | end.
173 |
174 | schedule_delay(#state{delay_dist = identity} = _State) ->
175 | 0;
176 | schedule_delay(#state{last_send_t = LastT,
177 | delay_esti = DelayEsti, delay_dist = constant} = _State) ->
178 | {Mean, _Var} = esti_get(DelayEsti),
179 | Mean + LastT - cur_tick();
180 | schedule_delay(#state{last_send_t = LastT,
181 | delay_esti = DelayEsti, delay_dist = uniform} = _State) ->
182 | {Mean, Var} = esti_get(DelayEsti),
183 | X = math:sqrt(12 * Var),
184 | Mean + rand:uniform() * X + LastT - cur_tick();
185 | schedule_delay(#state{last_send_t = LastT,
186 | delay_esti = DelayEsti, delay_dist = gaussian} = _State) ->
187 | {Mean, Var} = esti_get(DelayEsti),
188 | Z = rand:normal(Mean, Var),
189 | LastT + Z - cur_tick();
190 | schedule_delay(#state{last_send_t = LastT,
191 | delay_esti = DelayEsti, delay_dist = poission} = _State) ->
192 | % here, the delay between two packages follows exponential distribution
193 | % pdf(x) = lambda exp(-lambda * x), for x >= 0
194 | % while lambda can be estimated as 1/Mean
195 | % let's generate exponential distribution random variable by inverse transforming
196 | {Mean, _Var} = esti_get(DelayEsti),
197 | Z = - math:log(max(rand:uniform(), ?EPSILON)) * Mean,
198 | LastT + Z - cur_tick().
199 |
200 | schedule_size(#state{size_dist = identity, queue = Q} = _State) ->
201 | size(queue:head(Q));
202 | schedule_size(#state{size_dist = constant, size_esti = SizeEsti} = _State) ->
203 | {Mean, _Var} = esti_get(SizeEsti),
204 | Mean;
205 | schedule_size(#state{size_dist = uniform, size_esti = SizeEsti} = _State) ->
206 | {Mean, Var} = esti_get(SizeEsti),
207 | X = math:sqrt(12 * Var),
208 | Mean + rand:uniform() * X;
209 | schedule_size(#state{size_dist = gaussian, size_esti = SizeEsti} = _State) ->
210 | {Mean, Var} = esti_get(SizeEsti),
211 | rand:normal(Mean, Var).
212 |
213 | -define(Q_LEN, 4).
214 | -define(Q_BYTES, 100 * 1024).
215 |
216 | schedule(#state{queue = Q, total = Total, create_t = CreateT} = State) ->
217 | T = cur_tick(),
218 | case {T - CreateT < 2000, queue:len(Q), Total} of
219 | {true, _, _} -> self() ! flush;
220 | {_, _L, T} when T < 100 -> self() ! flush;
221 | {_, L, _T} when L > ?Q_LEN -> self() ! {flush, queue:len(Q) - ?Q_LEN};
222 | {_, _L, T} when T > ?Q_BYTES -> self() ! {flush, 1};
223 | _ ->
224 | Delay = min(schedule_delay(State), 100),
225 | Size = round(schedule_size(State)),
226 | case {Delay > 10, Size > 0} of
227 | {_, false} -> self() ! flush;
228 | {true, true} -> timer:send_after(Delay, {schedule, Size});
229 | {_, true} -> self() ! {schedule, Size}
230 | end
231 | end.
232 |
233 | %@doc init a algorithm for mean/variance estimation
234 | esti_init(welford) ->
235 | #param_esti{algo = {fun welford_run/2, fun welford_get/1},
236 | state = welford_init()};
237 | esti_init(iir) ->
238 | #param_esti{algo = {fun iir_run/2, fun iir_get/1},
239 | state = iir_init()}.
240 |
241 | esti_run(X, #param_esti{algo = {RunFun, _GetFun}, state = AlgoData} = EsitState) ->
242 | EsitState#param_esti{state = RunFun(X, AlgoData)}.
243 |
244 | esti_get(#param_esti{algo = {_RunFun, GetFun}, state = AlgoData} = _EsitState) ->
245 | GetFun(AlgoData).
246 |
247 | % -----------------------
248 | % welford algorithm
249 | % -----------------------
250 |
251 | %@doc online mean & variance estimator
252 | -record(welford_state,
253 | {
254 | n = 0,
255 | mean = 0.0,
256 | m2 = 0.0
257 | }).
258 |
259 | welford_init() ->
260 | #welford_state{}.
261 |
262 | welford_run(X, #welford_state{n = N, mean = Mean, m2 = M2} = _State) ->
263 | NewN = N + 1,
264 | Delta = X - Mean,
265 | NewMean = Mean + Delta / NewN,
266 | Delta2 = X - NewMean,
267 | NewM2 = M2 + Delta2 * Delta2,
268 | #welford_state{n = NewN, mean = NewMean, m2 = NewM2}.
269 |
270 | welford_get(#welford_state{n = N, mean = Mean, m2 = M2} = _State) when N > 1 ->
271 | {Mean, M2 / (N - 1)};
272 | welford_get(#welford_state{mean = Mean} = _State) ->
273 | {Mean, 0}.
274 |
275 | % -----------------------
276 | % IIR-based estimator
277 | % -----------------------
278 |
279 | %@doc online mean & variance estimator
280 | -record(iir_state,
281 | {
282 | mean = 0.0,
283 | var = 0.0
284 | }).
285 |
286 | iir_init() ->
287 | #iir_state{}.
288 |
289 | -define(MEAN_ALPHA, 0.1).
290 | -define(VAR_ALPHA, 0.05).
291 |
292 | iir_run(X, #iir_state{mean = Mean, var = Var} = _State) ->
293 | NewMean = ?MEAN_ALPHA * X + (1 - ?MEAN_ALPHA) * Mean,
294 | Delta = X - NewMean,
295 | NewVar = ?VAR_ALPHA * Delta * Delta + (1 - ?VAR_ALPHA) * Var,
296 | #iir_state{mean = NewMean, var = NewVar}.
297 |
298 | iir_get(#iir_state{mean = Mean, var = Var} = _State) ->
299 | {Mean, Var}.
300 |
301 | choice(L) ->
302 | Len = length(L),
303 | lists:nth(rand:uniform(Len), L).
304 |
305 | rand_size_model() -> choice([identity, constant, uniform, gaussian]).
306 | rand_delay_model() -> choice([identity, constant, uniform, gaussian, poission]).
--------------------------------------------------------------------------------
/src/mimicsocks_remote.erl:
--------------------------------------------------------------------------------
1 | %@doc remote socket forwarding server
2 | %
3 | % tcp traffic is either forwarded to a socks5 handler or relayed to another mimicsocks proxy
4 | %@author foldl
5 | -module(mimicsocks_remote).
6 |
7 | -include("mimicsocks.hrl").
8 |
9 | -behaviour(gen_statem).
10 |
11 | % api
12 | -export([start_link/1, socket_ready/2]).
13 |
14 | % callbacks
15 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
16 |
17 | -import(mimicsocks_wormhole_local, [show_sock/1, report_disconn/2]).
18 |
19 | % FSM States
20 | -export([
21 | init/3,
22 | forward/3
23 | ]).
24 |
25 | -record(state,
26 | {
27 | channel,
28 | handler,
29 | handler_mod
30 | }
31 | ).
32 |
33 | start_link(Args) ->
34 | gen_statem:start_link(?MODULE, Args, []).
35 |
36 | socket_ready(Pid, LSock) when is_pid(Pid), is_port(LSock) ->
37 | gen_tcp:controlling_process(LSock, Pid),
38 | gen_statem:cast(Pid, {socket_ready, LSock}).
39 |
40 | %%%===================================================================
41 | %%% gen_fsm callbacks
42 | %%%===================================================================
43 | init([Key, HandlerMod, HandlerArgs]) ->
44 | {ok, Handler} = HandlerMod:start_link([self() | HandlerArgs]),
45 | case mimicsocks_wormhole_remote:start_link([self(), Key]) of
46 | {ok, Channel} ->
47 | {ok, init, #state{handler_mod = HandlerMod,
48 | handler = Handler,
49 | channel = Channel}};
50 | {error, Reason} -> {stop, Reason}
51 | end.
52 |
53 | callback_mode() ->
54 | state_functions.
55 |
56 | init(cast, {socket_ready, Socket}, #state{channel = Channel} = State) when is_port(Socket) ->
57 | mimicsocks_wormhole_remote:socket_ready(Channel, Socket),
58 | {next_state, forward, State};
59 | init(info, Msg, StateData) -> handle_info(Msg, init, StateData).
60 |
61 | forward(info, Msg, StateData) -> handle_info(Msg, forward, StateData).
62 |
63 | handle_info({recv, Channel, Data}, _StateName, #state{handler = Handler, channel = Channel} = State) ->
64 | Handler ! {recv, self(), Data},
65 | {keep_state, State};
66 | handle_info({recv, Handler, Data}, _StateName, #state{handler = Handler, channel = Channel} = State) ->
67 | Channel ! {recv, self(), Data},
68 | {keep_state, State};
69 | handle_info(Info, StateName, State) ->
70 | ?ERROR("unexpected ~p in state ~p", [Info, StateName]),
71 | {keep_state, State}.
72 |
73 | terminate(_Reason, _StateName, #state{channel = Channel,
74 | handler = Handler,
75 | handler_mod = Mod}) ->
76 | (catch mimicsocks_wormhole_remote:stop(Channel)),
77 | (catch Mod:stop(Handler)),
78 | ok.
79 |
80 | code_change(_OldVsn, StateName, State, _Extra) ->
81 | {ok, StateName, State}.
82 |
--------------------------------------------------------------------------------
/src/mimicsocks_remote_agg.erl:
--------------------------------------------------------------------------------
1 | %@doc remote socket forwarding server
2 | %
3 | % tcp traffic is either forwarded to a socks5 handler or relayed to another mimicsocks proxy
4 | %@author foldl
5 | -module(mimicsocks_remote_agg).
6 |
7 | -include("mimicsocks.hrl").
8 |
9 | -behaviour(gen_statem).
10 |
11 | % api
12 | -export([start_link/1, socket_ready/2]).
13 |
14 | % callbacks
15 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
16 |
17 | -import(mimicsocks_wormhole_local, [show_sock/1, report_disconn/2]).
18 | -import(mimicsocks_local_agg, [parse_full/2, send_data/3]).
19 |
20 | % FSM States
21 | -export([
22 | init/3,
23 | forward/3,
24 | closing/3
25 | ]).
26 |
27 | -record(state,
28 | {
29 | channel,
30 | wormhole,
31 |
32 | handler_mod,
33 | handler_args,
34 |
35 | cmd_ref,
36 | buff = <<>>,
37 | t_p2i,
38 | t_i2p,
39 | d_buf = <<>>
40 | }
41 | ).
42 |
43 | start_link(Args) ->
44 | gen_statem:start_link(?MODULE, Args, []).
45 |
46 | socket_ready(Pid, LSock) when is_pid(Pid), is_port(LSock) ->
47 | gen_tcp:controlling_process(LSock, Pid),
48 | gen_statem:cast(Pid, {socket_ready, LSock}).
49 |
50 | %%%===================================================================
51 | %%% gen_fsm callbacks
52 | %%%===================================================================
53 | init([Key, HandlerMod, HandlerArgs]) ->
54 | case mimicsocks_wormhole_remote:start_link([self(), Key]) of
55 | {ok, Channel} ->
56 | {ok, init, #state{handler_mod = HandlerMod,
57 | handler_args = HandlerArgs,
58 | wormhole = mimicsocks_wormhole_remote,
59 | t_i2p = ets:new(tablei2p, []),
60 | t_p2i = ets:new(tablep2i, []),
61 | channel = Channel}};
62 | {error, Reason} -> {stop, Reason}
63 | end;
64 | init([RemoteIp, RemotePort, OtherPorts, Key, HandlerMod, HandlerArgs]) ->
65 | case mimicsocks_wormhole_local:start_link([self(), RemoteIp, RemotePort, OtherPorts, Key]) of
66 | {ok, Channel} ->
67 | {ok, forward, #state{handler_mod = HandlerMod,
68 | handler_args = HandlerArgs,
69 | wormhole = mimicsocks_wormhole_local,
70 | t_i2p = ets:new(tablei2p, []),
71 | t_p2i = ets:new(tablep2i, []),
72 | channel = Channel}};
73 | {error, Reason} -> {stop, Reason}
74 | end.
75 |
76 | callback_mode() ->
77 | state_functions.
78 |
79 | init(cast, {socket_ready, Socket}, #state{channel = Channel} = State) when is_port(Socket) ->
80 | mimicsocks_wormhole_remote:socket_ready(Channel, Socket),
81 | {next_state, forward, State};
82 | init(info, Msg, StateData) -> handle_info(Msg, init, StateData).
83 |
84 | forward(info, Msg, StateData) -> handle_info(Msg, forward, StateData).
85 |
86 | closing(cast, _, State) -> {keep_state, State};
87 | closing(info, stop, State) -> {stop, normal, State};
88 | closing(info, _Msg, State) -> {keep_state, State}.
89 |
90 | handle_info({recv, Channel, Bin}, _StateName, #state{channel = Channel,
91 | d_buf = Buf} = State) ->
92 | All = <>,
93 | {ok, Frames, Rem} = parse_full(All, []),
94 | State10 = lists:foldl(fun handle_cmd/2, State, Frames),
95 | NewState = State10#state{d_buf = Rem},
96 | {keep_state, NewState};
97 | handle_info({recv, Handler, Data}, _StateName, #state{handler_mod = Mod, channel = Channel, t_p2i = Tp2i} = State) ->
98 | case ets:lookup(Tp2i, Handler) of
99 | [{Handler, ID}] ->
100 | % traffic control
101 | case proc_high(Channel) of
102 | true ->
103 | Mod:active(Handler, false),
104 | timer:send_after(20, {recv, Handler, ID, Data});
105 | _ ->
106 | send_data(Channel, ID, Data)
107 | end;
108 | _ -> ok
109 | end,
110 | {keep_state, State};
111 | handle_info({recv, Handler, ID, Data} = Msg, _StateName, #state{handler_mod = Mod, channel = Channel} = State) ->
112 | case proc_low(Channel) of
113 | true ->
114 | send_data(Channel, ID, Data),
115 | Mod:active(Handler, true);
116 | _ ->
117 | timer:send_after(20, Msg)
118 | end,
119 | {keep_state, State};
120 | handle_info({'DOWN', _Ref, process, Handler, _Reason}, _StateName,
121 | #state{t_i2p = Ti2p, t_p2i = Tp2i} = State) ->
122 | case ets:lookup(Tp2i, Handler) of
123 | [{Handler, Id}] ->
124 | ets:match_delete(Tp2i, {Handler, '_'}),
125 | ets:match_delete(Ti2p, {Id, '_'});
126 | _ -> ok
127 | end,
128 | {keep_state, State};
129 | handle_info(Info, StateName, State) ->
130 | ?ERROR("unexpected ~p in state ~p", [Info, StateName]),
131 | {keep_state, State}.
132 |
133 | terminate(_Reason, _StateName, #state{channel = Channel,
134 | handler_mod = Mod,
135 | wormhole = W,
136 | t_i2p = Ti2p,
137 | t_p2i = Tp2i}) ->
138 | (catch W:stop(Channel)),
139 | ets:foldl(fun (Pid, _) -> (catch Mod:stop(Pid)) end, 0, Ti2p),
140 | ets:delete(Ti2p),
141 | ets:delete(Tp2i),
142 | ok.
143 |
144 | code_change(_OldVsn, StateName, State, _Extra) ->
145 | {ok, StateName, State}.
146 |
147 | %---------------
148 | % utils
149 | %---------------
150 |
151 | handle_cmd(?AGG_CMD_NOP, State) -> State;
152 | handle_cmd({?AGG_CMD_NEW_SOCKET, Id}, #state{t_p2i = Tp2i, t_i2p = Ti2p,
153 | handler_mod = Module,
154 | handler_args = Args,
155 | wormhole = W,
156 | channel = Channel} = State) ->
157 | case ets:lookup(Ti2p, Id) of
158 | [{Id, Pid}] ->
159 | ?ERROR("?AGG_CMD_NEW_SOCKET id already exsits, stop it", []),
160 | (catch Module:stop(Pid));
161 | _ -> ok
162 | end,
163 | W:suspend_mimic(Channel, 5000),
164 | {ok, NewPid} = Module:start_link([self() | Args]),
165 | unlink(NewPid),
166 | erlang:monitor(process, NewPid),
167 | ets:insert(Ti2p, {Id, NewPid}),
168 | ets:insert(Tp2i, {NewPid, Id}),
169 | State;
170 | handle_cmd({?AGG_CMD_CLOSE_SOCKET, Id}, #state{t_p2i = Tp2i, t_i2p = Ti2p,
171 | handler_mod = Mod} = State) ->
172 | case ets:lookup(Ti2p, Id) of
173 | [{Id, Pid}] ->
174 | ets:match_delete(Tp2i, {Pid, '_'}),
175 | ets:match_delete(Ti2p, {Id, '_'}),
176 | (catch Mod:stop(Pid));
177 | _ -> ok
178 | end,
179 | State;
180 | handle_cmd({?AGG_CMD_DATA, Id, Data}, #state{t_i2p = Ti2p} = State) ->
181 | case ets:lookup(Ti2p, Id) of
182 | [{Id, Pid}] ->
183 | Pid ! {recv, self(), Data};
184 | _ ->
185 | ?WARNING("invalid port id: ~p", [Id])
186 | end,
187 | State.
188 |
189 | proc_high(Pid) -> element(2, process_info(Pid, message_queue_len)) > 8000.
190 | proc_low(Pid) -> element(2, process_info(Pid, message_queue_len)) < 1000.
191 |
--------------------------------------------------------------------------------
/src/mimicsocks_remote_ho.erl:
--------------------------------------------------------------------------------
1 | %@doc Handover management
2 | %@author foldl
3 | -module(mimicsocks_remote_ho).
4 |
5 | -export([start_link/1, socket_ready/2]).
6 |
7 | -include("mimicsocks.hrl").
8 |
9 | start_link([]) ->
10 | {ok, spawn_link(fun f/0)}.
11 |
12 | socket_ready(Pid, Socket) ->
13 | ok = gen_tcp:controlling_process(Socket, Pid),
14 | Pid ! {socket_read, Socket}.
15 |
16 | f() ->
17 | receive
18 | {socket_read, Socket} ->
19 | {ok, ID} = gen_tcp:recv(Socket, ?MIMICSOCKS_HELLO_SIZE, 1000),
20 | case mimicsocks_cfg:get_remote(ID) of
21 | {ok, RemotePid} ->
22 | ok = gen_tcp:controlling_process(Socket, RemotePid),
23 | RemotePid ! {ho_socket, Socket};
24 | _ ->
25 | ?ERROR("cann't find IVec remote, closing",[]),
26 | gen_tcp:close(Socket)
27 | end
28 | end.
--------------------------------------------------------------------------------
/src/mimicsocks_remote_http.erl:
--------------------------------------------------------------------------------
1 | %@doc a handler of mimicsocks_remote
2 | % a super simple http proxy
3 | %@author foldl
4 | -module(mimicsocks_remote_http).
5 |
6 | -include("mimicsocks.hrl").
7 |
8 | -behaviour(gen_statem).
9 |
10 | % api
11 | -export([start_link/1, socket_ready/2, stop/1, active/2]).
12 |
13 | % callbacks
14 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
15 |
16 | -define(TIMEOUT, 1000).
17 |
18 | -import(mimicsocks_wormhole_local, [show_sock/1, report_disconn/2]).
19 | -import(mimicsocks_remote_socks, [send_to_local/2]).
20 |
21 | % FSM States
22 | -export([
23 | wait_req/3,
24 | data/3
25 | ]).
26 |
27 | -record(state,
28 | {
29 | local, % local data source (e.g. mimicsocks_remote or socket)
30 | rsock, % remote socket
31 | buff = <<>>
32 | }
33 | ).
34 |
35 | start_link(Args) ->
36 | gen_statem:start_link(?MODULE, Args, []).
37 |
38 | socket_ready(Pid, Sock) when is_port(Sock) ->
39 | ok = gen_tcp:controlling_process(Sock, Pid),
40 | gen_statem:cast(Pid, {socket_ready, Sock}).
41 |
42 | stop(Pid) -> gen_statem:stop(Pid).
43 |
44 | active(Pid, Option) -> Pid ! {active, Option}.
45 |
46 | %%%===================================================================
47 | %%% gen_fsm callbacks
48 | %%%===================================================================
49 | init([Local]) ->
50 | process_flag(trap_exit, true),
51 | {ok, wait_req, #state{local = Local}}.
52 |
53 | callback_mode() ->
54 | state_functions.
55 |
56 | %% wait_req -> data
57 | wait_req(cast, {socket_ready, Sock}, State) ->
58 | ok = inet:setopts(Sock, [{active, true}]),
59 | {keep_state, State#state{local = Sock}};
60 | wait_req(cast, {local, Bin}, State) ->
61 | Buffer = <<(State#state.buff)/binary, Bin/binary>>,
62 | case decode_http_req(Buffer) of
63 | incomplete ->
64 | {keep_state, State#state{buff = Buffer}, ?TIMEOUT};
65 | {{Host, Port, Method, Ver, RequestLine}, Headers} = _All ->
66 | %% connect to remote server & send first message
67 | case gen_tcp:connect(Host, Port, [{active, true}, {packet, raw}, binary,
68 | {reuseaddr, true}, {nodelay, true}]) of
69 | {ok, RSocket} ->
70 | ?INFO("Connected to remote ~p:~p for proxying", [Host, Port]),
71 | case Method of
72 | <<"CONNECT">> -> send_to_local(State#state.local, <<"HTTP/1.1 200 OK\r\n\r\n">>);
73 | _ -> gen_tcp:send(RSocket, [RequestLine, "\r\n", Headers])
74 | end,
75 | {next_state, data,
76 | State#state{buff= <<>>, rsock = RSocket}};
77 | {error, Reason} ->
78 | ?ERROR("wait_req can't connect to remote: ~p, ~p~n", [{Host, Port}, Reason]),
79 | send_to_local(State#state.local, [Ver, <<" 504 Gateway Time-out\r\n">>]),
80 | {stop, normal, State}
81 | end;
82 | Error ->
83 | ?ERROR("wait_req with error: ~p~n", [Error]),
84 | {stop, Error, State}
85 | end;
86 | wait_req(info, Msg, StateData) -> handle_info(Msg, wait_req, StateData).
87 |
88 | data(cast, {local, Bin}, #state{rsock = Socket} = State) ->
89 | gen_tcp:send(Socket, Bin),
90 | {next_state, data, State};
91 | data(cast, {remote, Bin}, State) ->
92 | send_to_local(State#state.local, Bin),
93 | {next_state, data, State};
94 | data(info, Msg, StateData) -> handle_info(Msg, data, StateData).
95 |
96 | handle_info({active, Option}, _StateName, #state{rsock = Socket} = StateData) ->
97 | case is_port(Socket) of
98 | true -> ok = inet:setopts(Socket, [{active, Option}]);
99 | _ -> ok
100 | end,
101 | {keep_state, StateData};
102 | handle_info({recv, From, Bin}, StateName, StateData) when is_pid(From) ->
103 | ?MODULE:StateName(cast, {local, Bin}, StateData);
104 | handle_info({tcp, Socket, Bin}, StateName, #state{local=Socket} = StateData) ->
105 | ?MODULE:StateName(cast, {local, Bin}, StateData);
106 | handle_info({tcp, Socket, Bin}, StateName, #state{rsock=Socket} = StateData) ->
107 | ?MODULE:StateName(cast, {remote, Bin}, StateData);
108 | handle_info({tcp_closed, Socket}, _StateName, #state{rsock = Socket} = StateData) ->
109 | report_disconn(Socket, "Remote"),
110 | {stop, normal, StateData};
111 | handle_info({tcp_closed, Socket}, _StateName, #state{local = Socket} = StateData) ->
112 | report_disconn(Socket, "local"),
113 | {stop, normal, StateData};
114 | handle_info(Info, StateName, State) ->
115 | ?ERROR("unexpected ~p", [Info]),
116 | {next_state, StateName, State}.
117 |
118 | terminate(_Reason, _StateName, #state{rsock=RSocket, local = Local}) ->
119 | (catch gen_tcp:close(RSocket)),
120 | case is_port(Local) of
121 | true -> (catch gen_tcp:close(Local));
122 | _ -> ok
123 | end.
124 |
125 | code_change(_OldVsn, StateName, State, _Extra) ->
126 | {ok, StateName, State}.
127 |
128 | decode_http_req(Req) ->
129 | case binary:split(Req, <<"\r\n">>) of
130 | [_Data] -> incomplete;
131 | [Request, Headers] -> {parse_req_line(Request), Headers}
132 | end.
133 |
134 | parse_req_line(Req) ->
135 | [Method, URL, Ver] = binary:split(Req, <<" ">>, [global]),
136 | {match, [Scheme, Host, Port, Path]} = re:run(
137 | URL, "^((?http|https)://)?(?[^:/]+):?(?\\d*)(?/?.*)",
138 | [{capture, all_names, binary}]),
139 |
140 | Port10 = get_port(Scheme, Port),
141 | Path10 = case Path of <<>> -> <<"/">>; _ -> Path end,
142 | RequestLine = erlang:iolist_to_binary([Method, " ", Path10, " ", Ver]),
143 | {binary_to_list(Host), Port10, Method, Ver, RequestLine}.
144 |
145 | get_port(<<>>, <<>>) -> 80;
146 | get_port(<<"http">>, <<>>) -> 80;
147 | get_port(<<"https">>, <<>>) -> 443;
148 | get_port(_, PortBin) -> binary_to_integer(PortBin).
--------------------------------------------------------------------------------
/src/mimicsocks_remote_relay.erl:
--------------------------------------------------------------------------------
1 | %@doc a relay handler for remote node
2 | %@author foldl
3 | -module(mimicsocks_remote_relay).
4 |
5 | -export([start_link/1, stop/1, recv/2, active/2]).
6 |
7 | -include("mimicsocks.hrl").
8 |
9 | -record(state,
10 | {
11 | local,
12 | sock
13 | }).
14 |
15 | %doc connect to mimicsocks proxy or another raw tcp server
16 | start_link([Local, Addr, Port]) ->
17 | {ok, spawn_link(fun () -> init([Addr, Port, Local]) end)}.
18 |
19 | recv(Pid, Bin) ->
20 | Pid ! {recv, self(), Bin}.
21 |
22 | stop(Pid) -> Pid ! stop.
23 |
24 | active(Pid, Option) -> Pid ! {active, Option}.
25 |
26 | init([Addr, Port, Local]) ->
27 | % connect to remote server & send first message
28 | case gen_tcp:connect(Addr, Port, [{active, true}, {packet, raw}, binary,
29 | {reuseaddr, true}]) of
30 | {ok, Socket} ->
31 | loop(#state{local = Local, sock = Socket});
32 | {error, Reason} ->
33 | ?ERROR("failed to connect to mimicsocks: ~p, ~p\n", [{Addr, Port}, Reason])
34 | end.
35 |
36 | loop(#state{local = Local, sock = Socket} = State) ->
37 | receive
38 | {tcp, Socket, Bin} ->
39 | Local ! {recv, self(), Bin},
40 | loop(State);
41 | {tcp_closed, Socket} ->
42 | ok;
43 | {recv, Local, Bin} ->
44 | gen_tcp:send(Socket, Bin),
45 | loop(State);
46 | {active, Option} ->
47 | ok = inet:setops(Socket, [{active, Option}]),
48 | loop(State);
49 | stop ->
50 | ok;
51 | X ->
52 | ?WARNING("unexpected msg: ~p", [X]),
53 | loop(State)
54 | end.
--------------------------------------------------------------------------------
/src/mimicsocks_remote_socks.erl:
--------------------------------------------------------------------------------
1 | %@doc a handler of mimicsocks_remote
2 | % a super simple socks5 proxy
3 | %@author foldl
4 | -module(mimicsocks_remote_socks).
5 |
6 | -include("mimicsocks.hrl").
7 |
8 | -behaviour(gen_statem).
9 |
10 | % api
11 | -export([start_link/1, socket_ready/2, stop/1, active/2]).
12 |
13 | % callbacks
14 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
15 |
16 | -define(TIMEOUT, 1000).
17 |
18 | -import(mimicsocks_wormhole_local, [show_sock/1, report_disconn/2]).
19 | -export([send_to_local/2]).
20 |
21 | % FSM States
22 | -export([
23 | wait_auth/3,
24 | wait_req/3,
25 | data/3
26 | ]).
27 |
28 | -record(state,
29 | {
30 | local, % local data source (e.g. mimicsocks_remote or socket)
31 | rsock, % remote socket
32 | buff = <<>>
33 | }
34 | ).
35 |
36 | start_link(Args) ->
37 | gen_statem:start_link(?MODULE, Args, []).
38 |
39 | socket_ready(Pid, Sock) when is_port(Sock) ->
40 | ok = gen_tcp:controlling_process(Sock, Pid),
41 | gen_statem:cast(Pid, {socket_ready, Sock}).
42 |
43 | active(Pid, Option) -> Pid ! {active, Option}.
44 |
45 | stop(Pid) -> gen_statem:stop(Pid).
46 |
47 | %% definitions for socksv5
48 | %% https://tools.ietf.org/html/rfc1928
49 | -define(SOCKS5_VER, 16#05).
50 |
51 | -define(SOCKS5_AUTH_NONE, 16#00).
52 | -define(SOCKS5_AUTH_GSSAPI, 16#01).
53 | -define(SOCKS5_AUTH_USER, 16#02).
54 | -define(SOCKS5_AUTH_ERR, 16#ff).
55 |
56 | -define(SOCKS5_REQ_CONNECT, 16#01).
57 | -define(SOCKS5_REQ_BIND, 16#02).
58 | -define(SOCKS5_REQ_UDP_ASSOC,16#03).
59 |
60 | -define(SOCKS5_ATYP_V4, 16#01).
61 | -define(SOCKS5_ATYP_DOM, 16#03).
62 | -define(SOCKS5_ATYP_V6, 16#04).
63 |
64 | -define(SOCKS5_REP_OK, 16#00).
65 | -define(SOCKS5_REP_FAIL, 16#01).
66 | -define(SOCKS5_REP_NOT_ALLOWED, 16#02).
67 | -define(SOCKS5_REP_NET_UNREACHABLE, 16#03).
68 | -define(SOCKS5_REP_HOST_UNREACHABLE, 16#04).
69 | -define(SOCKS5_REP_REFUSED, 16#05).
70 | -define(SOCKS5_REP_TTL_EXPIRED, 16#06).
71 | -define(SOCKS5_REP_CMD_NOT_SUPPORTED, 16#07).
72 | -define(SOCKS5_REP_ATYP_NOT_SUPPORTED, 16#08).
73 |
74 | -define(SOCKS5_RESERVED_FIELD, 16#00).
75 |
76 | %% definitions for socksv4
77 | %% http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
78 | -define(SOCKS4_VER, 16#04).
79 | -define(SOCKS4_CMD_CONNECT, 16#01).
80 | -define(SOCKS4_CMD_BIND, 16#02).
81 |
82 | -define(SOCKS4_RES_GRANTED, 90).
83 | -define(SOCKS4_RES_REJECTED, 91).
84 | -define(SOCKS4_RES_REJECTED_CONN, 92).
85 | -define(SOCKS4_RES_REJECTED_USERID, 93).
86 |
87 | %%%===================================================================
88 | %%% gen_fsm callbacks
89 | %%%===================================================================
90 | init([Local]) ->
91 | process_flag(trap_exit, true),
92 | {ok, wait_auth, #state{local = Local}}.
93 |
94 | callback_mode() ->
95 | state_functions.
96 |
97 | %% wait_auth -> wait_req -> data
98 | wait_auth(cast, {socket_ready, Sock}, State) ->
99 | ok = inet:setopts(Sock, [{active, true}]),
100 | {keep_state, State#state{local = Sock}};
101 | wait_auth(cast, {local, Bin}, State) ->
102 | Buffer = <<(State#state.buff)/binary, Bin/binary>>,
103 | case decode_socks5_auth(Buffer) of
104 | incomplete ->
105 | {next_state, wait_auth, State#state{buff = Buffer}, ?TIMEOUT};
106 | {?SOCKS5_VER, _, _, Rest} ->
107 | send_to_local(State#state.local, <>),
108 | {next_state, wait_req, State#state{buff = Rest}, ?TIMEOUT};
109 | {error, {not_supported_version, ?SOCKS4_VER}} ->
110 | do_socks4(Buffer, State);
111 | Error ->
112 | ?ERROR("socks5_auth with error: ~p~n", [Error]),
113 | {stop, Error, State}
114 | end;
115 | wait_auth(timeout, _, State) ->
116 | ?ERROR("Client connection timeout: wait_req~n", []),
117 | {stop, normal, State};
118 | wait_auth(info, Msg, StateData) -> handle_info(Msg, wait_auth, StateData).
119 |
120 | do_socks4(Buffer, State) ->
121 | case decode_socks4_req(Buffer) of
122 | incomplete ->
123 | {next_state, wait_auth, State#state{buff = Buffer}, ?TIMEOUT};
124 | {?SOCKS4_VER, DestAddr, Host, Port, Rest} = _Socks4Req ->
125 | case gen_tcp:connect(Host, Port,
126 | [{active, true}, {packet, raw}, binary,
127 | {reuseaddr, true},
128 | {nodelay, true}]) of
129 | {ok, RSocket} ->
130 | Addr = case inet:peername(RSocket) of
131 | {ok, {Addr10, Port}} -> Addr10;
132 | _ -> {8,8,8,8}
133 | end,
134 | ?INFO("Connected to remote ~p:~p for proxying", [Addr, Port]),
135 | gen_tcp:send(RSocket, Rest),
136 | BinAddr = list_to_binary(tuple_to_list(Addr)),
137 | Socks4Rsp = <<0, ?SOCKS4_RES_GRANTED, Port:16/big, BinAddr/binary>>,
138 | send_to_local(State#state.local, Socks4Rsp),
139 | {next_state, data, State#state{buff= <<>>, rsock = RSocket}};
140 | {error, _Reason} ->
141 | Socks4Rsp = <<0, ?SOCKS4_RES_REJECTED_CONN, Port:16/big, DestAddr/binary>>,
142 | send_to_local(State#state.local, Socks4Rsp),
143 | {stop, normal, State}
144 | end;
145 | Error ->
146 | ?ERROR("socks4 with error: ~p~n", [Error]),
147 | {stop, Error, State}
148 | end.
149 |
150 | wait_req(cast, {local, Bin}, State) ->
151 | Buffer = <<(State#state.buff)/binary, Bin/binary>>,
152 | case decode_socks5_req(Buffer) of
153 | incomplete ->
154 | {next_state, wait_req, State#state{buff = Buffer}, ?TIMEOUT};
155 | {?SOCKS5_VER, AddrType, BinAddr, Addr, Port, Rest}->
156 | Target = case AddrType of
157 | ?SOCKS5_ATYP_DOM ->
158 | AddrSize = size(BinAddr),
159 | <>;
160 | _ ->
161 | <>
162 | end,
163 |
164 | %% connect to remote server & send first message
165 | case gen_tcp:connect(Addr, Port, [{active, true}, {packet, raw}, binary,
166 | {reuseaddr, true},
167 | {nodelay, true}]) of
168 | {ok, RSocket} ->
169 | ?INFO("Connected to remote ~p:~p for proxying",
170 | [Addr, Port]),
171 | gen_tcp:send(RSocket, Rest),
172 | Socks5Rsp = <>,
173 | send_to_local(State#state.local, [Socks5Rsp, Target]),
174 | {next_state, data,
175 | State#state{buff= <<>>, rsock = RSocket}};
176 | {error, Reason} ->
177 | ?ERROR("wait_req can't connect to remote: ~p, ~p~n", [{Addr, Port}, Reason]),
178 | Socks5Rsp = <>,
179 | send_to_local(State#state.local, [Socks5Rsp, Target]),
180 | {stop, normal, State}
181 | end;
182 | Error ->
183 | ?ERROR("wait_req with error: ~p~n", [Error]),
184 | {stop, Error, State}
185 | end;
186 | wait_req(timeout, _, State) ->
187 | ?ERROR("Client connection timeout: wait_req~n", []),
188 | {stop, normal, State};
189 | wait_req(info, Msg, StateData) -> handle_info(Msg, wait_req, StateData).
190 |
191 | data(cast, {local, Bin}, #state{rsock = Socket} = State) ->
192 | gen_tcp:send(Socket, Bin),
193 | {next_state, data, State};
194 | data(cast, {remote, Bin}, State) ->
195 | send_to_local(State#state.local, Bin),
196 | {next_state, data, State};
197 | data(info, Msg, StateData) -> handle_info(Msg, data, StateData).
198 |
199 | handle_info({active, Option}, _StateName, #state{rsock = Socket} = StateData) ->
200 | case is_port(Socket) of
201 | true -> ok = inet:setopts(Socket, [{active, Option}]);
202 | _ -> ok
203 | end,
204 | {keep_state, StateData};
205 | handle_info({recv, From, Bin}, StateName, StateData) when is_pid(From) ->
206 | ?MODULE:StateName(cast, {local, Bin}, StateData);
207 | handle_info({tcp, Socket, Bin}, StateName, #state{local=Socket} = StateData) ->
208 | ?MODULE:StateName(cast, {local, Bin}, StateData);
209 | handle_info({tcp, Socket, Bin}, StateName, #state{rsock=Socket} = StateData) ->
210 | ?MODULE:StateName(cast, {remote, Bin}, StateData);
211 | handle_info({tcp_closed, Socket}, _StateName, #state{rsock = Socket} = StateData) ->
212 | report_disconn(Socket, "Remote"),
213 | {stop, normal, StateData};
214 | handle_info({tcp_closed, Socket}, _StateName, #state{local = Socket} = StateData) ->
215 | report_disconn(Socket, "local"),
216 | {stop, normal, StateData};
217 | handle_info(Info, StateName, State) ->
218 | ?ERROR("unexpected ~p", [Info]),
219 | {next_state, StateName, State}.
220 |
221 | terminate(_Reason, _StateName, #state{rsock=RSocket}) ->
222 | (catch gen_tcp:close(RSocket)),
223 | ok.
224 |
225 | code_change(_OldVsn, StateName, State, _Extra) ->
226 | {ok, StateName, State}.
227 |
228 | % helper functions
229 | send_to_local(Local, IoData) when is_pid(Local) ->
230 | Local ! {recv, self(), IoData};
231 | send_to_local(LSock, IoData) when is_port(LSock) ->
232 | gen_tcp:send(LSock, IoData).
233 |
234 | decode_socks5_auth(<>) when Ver =/= ?SOCKS5_VER ->
235 | {error, {not_supported_version, Ver}};
236 | decode_socks5_auth(<>) ->
238 | {?SOCKS5_VER, NMethods, Methods, Rest};
239 | decode_socks5_auth(_) ->
240 | incomplete.
241 |
242 | decode_socks4_req(<<>>) -> incomplete;
243 | decode_socks4_req(<>) -> decode_socks4_req0(Rem);
244 | decode_socks4_req(<>) -> {error, {not_supported_version, Ver}}.
245 |
246 | decode_socks4_req0(<>) ->
247 | case binary:split(Rem, <<0>>) of
248 | [_USERID, More] ->
249 | case DestAddr of
250 | % socks4a
251 | <<0, 0, 0, X>> when X /= 0 ->
252 | % socks4a
253 | case binary:split(More, <<0>>) of
254 | [Host, Rest] ->
255 | {?SOCKS4_VER, DestAddr, binary_to_list(Host), DestPort, Rest};
256 | _ -> incomplete
257 | end;
258 | _ ->
259 | {?SOCKS4_VER, DestAddr, list_to_tuple(binary_to_list(DestAddr)), DestPort, More}
260 | end;
261 | _ -> incomplete
262 | end;
263 | decode_socks4_req0(<>) -> incomplete;
264 | decode_socks4_req0(<>) -> {error, {not_supported_command, Cmd}};
265 | decode_socks4_req0(<<>>) -> incomplete.
266 |
267 | decode_socks5_req(<<>>) -> incomplete;
268 | decode_socks5_req(<>) -> decode_socks5_req0(Rem);
269 | decode_socks5_req(<>) -> {error, {not_supported_version, Ver}}.
270 |
271 | decode_socks5_req0(<<>>) -> incomplete;
272 | decode_socks5_req0(<>) -> incomplete;
273 | decode_socks5_req0(<>) -> incomplete;
274 | decode_socks5_req0(<> = _Req) -> decode_socks5_req_conn(Rem);
275 | decode_socks5_req0(<> = _Req) -> {error, {not_supported_command, Cmd}}.
276 |
277 | decode_socks5_req_conn(<>) ->
278 | {?SOCKS5_VER, ?SOCKS5_ATYP_V4, DestAddr, list_to_tuple(binary_to_list(DestAddr)), DestPort, Rem};
279 | decode_socks5_req_conn(<>) ->
280 | <> = DestAddr,
281 | {?SOCKS5_VER, ?SOCKS5_ATYP_V6, DestAddr, {A1,A2,A3,A4,A5,A6,A7,A8}, DestPort, Rem};
282 | decode_socks5_req_conn(<>) ->
283 | {?SOCKS5_VER, ?SOCKS5_ATYP_DOM, Domain, binary_to_list(Domain), DestPort, Rem};
284 | decode_socks5_req_conn(<>) -> incomplete;
285 | decode_socks5_req_conn(<>) -> incomplete;
286 | decode_socks5_req_conn(<>) -> incomplete;
287 | decode_socks5_req_conn(<>) -> {error, {bad_atype, AType}}.
--------------------------------------------------------------------------------
/src/mimicsocks_sup.erl:
--------------------------------------------------------------------------------
1 | %@doc the supervisor
2 | %@author foldl
3 | -module(mimicsocks_sup).
4 | -behaviour(supervisor).
5 |
6 | -export([start_link/0, start_link/1, start_link/2]).
7 | -export([init/1]).
8 |
9 | start_link() ->
10 | Config = case application:get_env(mimicsocks, config) of
11 | {ok, X} when is_atom(X) -> X;
12 | _ -> mimicsocks
13 | end,
14 | start_link(Config, both).
15 |
16 | start_link(local) ->
17 | start_link(mimicsocks, local);
18 | start_link(remote) ->
19 | start_link(mimicsocks, remote);
20 | start_link(both) ->
21 | start_link(mimicsocks, both);
22 | start_link(Config) -> start_link(Config, both).
23 |
24 | start_link(Config, Type) ->
25 | mimicsocks_cfg:start_link(Config),
26 | Set = sets:from_list(
27 | case application:get_env(mimicsocks, log) of
28 | {ok, L} when is_list(L) -> L;
29 | _ -> []
30 | end),
31 | case sets:is_element(file, Set) of
32 | true ->
33 | LogFile = filename:join(code:priv_dir(mimicsocks), "log"),
34 | ok = error_logger:logfile({open, LogFile});
35 | _ -> ok
36 | end,
37 | error_logger:tty(true), % sets:is_element(tty, Set)),
38 | supervisor:start_link({local, ?MODULE}, ?MODULE, [Type]).
39 |
40 | init([Type]) ->
41 | Servers = mimicsocks_cfg:list_servers(),
42 | LocalAddresses = sets:from_list(list_addrs()),
43 |
44 | ChildLocal = case (Type == both) or (Type == local) of
45 | true -> [create_local_child(LocalAddresses, X) || X <- Servers];
46 | _ -> []
47 | end,
48 | ChildRemote = case (Type == both) or (Type == remote) of
49 | true -> [create_remote_child(LocalAddresses, X) || X <- Servers];
50 | _ -> []
51 | end,
52 | Children = lists:flatten(ChildRemote ++ ChildLocal),
53 |
54 | SupFlags = #{strategy => one_for_one, intensity => 10, period => 1},
55 | ChildSpecs = lists:zipwith(
56 | fun (Child, Id) -> maps:put(id, Id, Child) end,
57 | Children, lists:seq(1, length(Children))),
58 | {ok, {SupFlags, ChildSpecs}}.
59 |
60 | % helper function
61 |
62 | %@doc list all addresses
63 | list_addrs() ->
64 | {ok, L} = inet:getifaddrs(),
65 | lists:flatten([proplists:get_all_values(addr, X) || {_Dev, X} <- L]).
66 |
67 | %@doc create child spec for local node
68 | create_local_child(LocalAddresses, Server) ->
69 | case mimicsocks_cfg:get_value(Server, reverse) of
70 | true ->
71 | aggregated = mimicsocks_cfg:get_value(Server, wormhole),
72 | create_local_child1(LocalAddresses, Server);
73 | _ -> create_local_child0(LocalAddresses, Server)
74 | end.
75 |
76 | create_remote_child(LocalAddresses, Server) ->
77 | case mimicsocks_cfg:get_value(Server, reverse) of
78 | true ->
79 | aggregated = mimicsocks_cfg:get_value(Server, wormhole),
80 | create_remote_child1(LocalAddresses, Server);
81 | _ -> create_remote_child0(LocalAddresses, Server)
82 | end.
83 |
84 | create_local_child0(LocalAddresses, Server) ->
85 | {Ip, Port} = mimicsocks_cfg:get_value(Server, server),
86 | {RemoteIp, RemotePort} = mimicsocks_cfg:get_value(Server, wormhole_remote),
87 | Key = mimicsocks_cfg:get_value(Server, key),
88 | OtherPorts = mimicsocks_cfg:get_value(Server, wormhole_extra_ports),
89 | LocalProxy = mimicsocks_cfg:get_value(Server, local_proxy),
90 | LocalArgs = [RemoteIp, RemotePort, OtherPorts, Key, LocalProxy],
91 | case sets:is_element(Ip, LocalAddresses) of
92 | true ->
93 | case mimicsocks_cfg:get_value(Server, wormhole) of
94 | aggregated ->
95 | LocalServerArgs = [Ip, Port, mimicsocks_local_agg, {agg, LocalArgs}],
96 | #{
97 | start => {mimicsocks_tcp_listener, start_link, [LocalServerArgs]},
98 | restart => permanent,
99 | shutdown => brutal_kill
100 | };
101 | _ ->
102 | LocalServerArgs = [Ip, Port, mimicsocks_local, LocalArgs],
103 | #{
104 | start => {mimicsocks_tcp_listener, start_link, [LocalServerArgs]},
105 | restart => permanent,
106 | shutdown => brutal_kill
107 | }
108 | end;
109 | _ -> []
110 | end.
111 |
112 | create_local_child1(LocalAddresses, Server) ->
113 | {RemoteIp, RemotePort} = mimicsocks_cfg:get_value(Server, wormhole_remote),
114 | Key = mimicsocks_cfg:get_value(Server, key),
115 | OtherPorts = mimicsocks_cfg:get_value(Server, wormhole_extra_ports),
116 | {Handler, HandlerArgs} = get_handler_cfg(Server),
117 |
118 | case (not sets:is_element(RemoteIp, LocalAddresses))
119 | or (RemoteIp == {127,0,0,1}) of
120 | true ->
121 | RemoteArgs = [RemoteIp, RemotePort, OtherPorts, Key, Handler, HandlerArgs],
122 | #{
123 | start => {mimicsocks_remote_agg, start_link, [RemoteArgs]},
124 | restart => permanent,
125 | shutdown => brutal_kill
126 | };
127 | _ -> []
128 | end.
129 |
130 | %@doc create child spec for remote node
131 | create_remote_child0(LocalAddresses, Server) ->
132 | {RemoteIp, RemotePort} = mimicsocks_cfg:get_value(Server, wormhole_remote),
133 | Key = mimicsocks_cfg:get_value(Server, key),
134 | {Handler, HandlerArgs} = get_handler_cfg(Server),
135 | ExtraPorts = mimicsocks_cfg:get_value(Server, wormhole_extra_ports),
136 |
137 | Mod = case mimicsocks_cfg:get_value(Server, wormhole) of
138 | aggregated -> mimicsocks_remote_agg;
139 | _ -> mimicsocks_remote
140 | end,
141 |
142 | case sets:is_element(RemoteIp, LocalAddresses) of
143 | true ->
144 | RemoteArgs = [Key, Handler, HandlerArgs],
145 | RemoteServerArgs = [RemoteIp, RemotePort, Mod, RemoteArgs],
146 | RemoteMain = #{
147 | start => {mimicsocks_tcp_listener, start_link, [RemoteServerArgs]},
148 | restart => permanent,
149 | shutdown => brutal_kill
150 | },
151 | HoWorkers = [#{
152 | start => {mimicsocks_tcp_listener, start_link,
153 | [[RemoteIp, APort, mimicsocks_remote_ho, []]]},
154 | restart => permanent,
155 | shutdown => brutal_kill
156 | } || APort <- ExtraPorts],
157 | [RemoteMain | HoWorkers];
158 | _ -> []
159 | end.
160 |
161 | create_remote_child1(LocalAddresses, Server) ->
162 | {Ip, Port} = mimicsocks_cfg:get_value(Server, server),
163 | {RemoteIp, RemotePort} = mimicsocks_cfg:get_value(Server, wormhole_remote),
164 | Key = mimicsocks_cfg:get_value(Server, key),
165 | ExtraPorts = mimicsocks_cfg:get_value(Server, wormhole_extra_ports),
166 | LocalArgs = [Key],
167 |
168 | case sets:is_element(RemoteIp, LocalAddresses) of
169 | true ->
170 | RemoteServerArgs = {[{Ip, Port}, {RemoteIp, RemotePort}],
171 | mimicsocks_local_agg, [accept, accept2], LocalArgs},
172 | ServerCfg = #{
173 | start => {mimicsocks_tcp_listener, start_link, [RemoteServerArgs]},
174 | restart => permanent,
175 | shutdown => brutal_kill
176 | },
177 | HoWorkers = [#{
178 | start => {mimicsocks_tcp_listener, start_link,
179 | [[RemoteIp, APort, mimicsocks_remote_ho, []]]},
180 | restart => permanent,
181 | shutdown => brutal_kill
182 | } || APort <- ExtraPorts],
183 | [ServerCfg | HoWorkers];
184 | _ -> []
185 | end.
186 |
187 | get_handler_cfg(Server) ->
188 | case mimicsocks_cfg:get_value(Server, handler) of
189 | socks5 -> {mimicsocks_remote_socks, []};
190 | socks4 -> {mimicsocks_remote_socks, []};
191 | socks -> {mimicsocks_remote_socks, []};
192 | http -> {mimicsocks_remote_http, []};
193 | {relay, {RelayIp, RelayPort}} ->
194 | {mimicsocks_remote_relay, [RelayIp, RelayPort]};
195 | {relay, ProxyName} when is_atom(ProxyName) ->
196 | {RelayIp, RelayPort} = mimicsocks_cfg:get_value(ProxyName, server),
197 | {mimicsocks_remote_relay, [RelayIp, RelayPort]}
198 | end.
--------------------------------------------------------------------------------
/src/mimicsocks_tcp_listener.erl:
--------------------------------------------------------------------------------
1 | %@doc a simple tcp server
2 | %@author foldl
3 | -module(mimicsocks_tcp_listener).
4 |
5 | -export([start_link/1]).
6 |
7 | start_link([_Ip, Port, Module | _T] = Args) when is_integer(Port), is_atom(Module)->
8 | {ok, spawn_link(fun () -> init(Args) end)};
9 | start_link({IpPortList, Module, FList, Args10} = _Args) when is_list(IpPortList) ->
10 | {ok, spawn_link(fun () ->
11 | {ok, Pid} = Module:start_link(Args10),
12 | lists:zipwith(fun ({Ip, Port}, F) ->
13 | spawn_link(fun () -> init([Ip, Port, Module, {pid, F, Pid}]) end)
14 | end, IpPortList, FList),
15 | loop()
16 | end)}.
17 |
18 | %% callbacks
19 | init([Ip, Port, Module, Args]) ->
20 | Opts = [binary, {packet, raw}, {ip, Ip}, {backlog, 128}, {active, false}, {reuseaddr, true}],
21 | case gen_tcp:listen(Port, Opts) of
22 | {ok, Listen_socket} ->
23 | case Args of
24 | {pid, F, Pid} when is_pid(Pid) ->
25 | link(Pid),
26 | accept_loop3(Listen_socket, [Module, F, Pid]);
27 | {agg, Pid} when is_pid(Pid) -> accept_loop3(Listen_socket, [Module, Pid]);
28 | {agg, Args10} ->
29 | {ok, Pid} = Module:start_link(Args10),
30 | accept_loop2(Listen_socket, [Module, Pid]);
31 | _ -> accept_loop1(Listen_socket, [Module, Args])
32 | end;
33 | {error, Reason} ->
34 | {stop, Reason}
35 | end.
36 |
37 | accept_loop1(LSock, [Module, Args]) ->
38 | case gen_tcp:accept(LSock) of
39 | {ok, Socket} ->
40 | ok = inet:setopts(Socket, [{linger, {true, 10}}]),
41 | {ok, Pid} = Module:start_link(Args),
42 | unlink(Pid),
43 | Module:socket_ready(Pid, Socket),
44 | accept_loop1(LSock, [Module, Args]);
45 | {error, Reason} ->
46 | {error, Reason}
47 | end.
48 |
49 | accept_loop2(LSock, [Module, Pid]) ->
50 | case gen_tcp:accept(LSock) of
51 | {ok, Socket} ->
52 | ok = inet:setopts(Socket, [{linger, {true, 10}}]),
53 | Module:accept(Pid, Socket),
54 | accept_loop2(LSock, [Module, Pid]);
55 | {error, Reason} ->
56 | {error, Reason}
57 | end.
58 |
59 | accept_loop3(LSock, [Module, F, Pid] = Args) ->
60 | case gen_tcp:accept(LSock) of
61 | {ok, Socket} ->
62 | ok = inet:setopts(Socket, [{linger, {true, 10}}]),
63 | Module:F(Pid, Socket),
64 | accept_loop3(LSock, Args);
65 | {error, Reason} ->
66 | {error, Reason}
67 | end.
68 |
69 | loop() ->
70 | receive
71 | _ -> loop()
72 | end.
--------------------------------------------------------------------------------
/src/mimicsocks_wormhole_local.erl:
--------------------------------------------------------------------------------
1 | %@doc generalized communication channel
2 | %@author foldl
3 | -module(mimicsocks_wormhole_local).
4 |
5 | -include("mimicsocks.hrl").
6 |
7 | -behaviour(gen_statem).
8 |
9 | %% API
10 | -export([start_link/1, stop/1, recv/2, suspend_mimic/2, handover_now/1]).
11 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
12 |
13 | -export([report_disconn/2, show_sock/1, next_id/2]).
14 |
15 | % FSM States
16 | -export([
17 | forward/3,
18 | ho_initiated/3,
19 | ho_wait_r2l/3,
20 | ho_sending_complete/3,
21 | ho_wait_close/3
22 | ]).
23 |
24 | % utils
25 | -import(mimicsocks_mimic, [choice/1]).
26 |
27 | start_link(Args) ->
28 | gen_statem:start_link(?MODULE, Args, []).
29 |
30 | stop(Pid) -> gen_statem:stop(Pid).
31 |
32 | recv(Pid, Data) -> Pid ! {recv, self(), Data}.
33 |
34 | suspend_mimic(Pid, Duration) -> Pid ! {suspend_mimic, Duration}.
35 |
36 | handover_now(Pid) -> Pid ! handover_now.
37 |
38 | callback_mode() ->
39 | state_functions.
40 |
41 | -record(state,
42 | {
43 | up_stream, % data receiver
44 |
45 | addr, % remote addr & port
46 | port,
47 | other_ports = [], % ports for handover
48 | extra_args,
49 |
50 | send, % process chain
51 | send_sink,
52 | recv,
53 | recv_sink,
54 | recv_inband,
55 | send_inband,
56 |
57 | ho_id,
58 |
59 | rsock, % remote socket
60 | rsock2, % handover socket
61 | key,
62 | ivec,
63 |
64 | cmd_ref,
65 | ho_timer,
66 | ho_buf = <<>>,
67 |
68 | ping_timer
69 | }
70 | ).
71 |
72 | %% callback funcitons
73 | init([UpStream, ServerAddr, ServerPort, OtherPorts, Key | T]) ->
74 | process_flag(trap_exit, true),
75 | IVec = gen_ivec(),
76 | ID0 = next_id(Key, IVec),
77 | HOID = next_id(Key, ID0),
78 |
79 | RecvSink = mimicsocks_inband_recv:start_link([self(), self()]),
80 | mimicsocks_inband_recv:set_key(RecvSink, Key),
81 | Recv = mimicsocks_crypt:start_link(decrypt, [RecvSink, mimicsocks_crypt:init_aes_ctr_dec(Key, IVec)]),
82 |
83 | {ok, SendSink} = mimicsocks_mimic:start_link([self(), identity, identity, iir]),
84 | SendEncrypt = mimicsocks_crypt:start_link(encrypt, [SendSink, mimicsocks_crypt:init_aes_ctr_enc(Key, IVec)]),
85 | Send = mimicsocks_inband_send:start_link([SendEncrypt, self()]),
86 | mimicsocks_inband_send:set_key(Send, Key),
87 |
88 | case connect(ServerAddr, ServerPort, T) of
89 | {ok, RSocket} ->
90 | ?INFO("Connected to remote ~p:~p\n", [ServerAddr, ServerPort]),
91 | gen_tcp:send(RSocket, <>),
92 | {ok, HOTimer} = create_ho_timer(OtherPorts),
93 | {ok, forward, #state{
94 | up_stream = UpStream,
95 | addr = ServerAddr,
96 | port = ServerPort,
97 | extra_args = T,
98 | other_ports = OtherPorts,
99 | key = Key,
100 | rsock = RSocket,
101 | ivec = IVec,
102 | recv = Recv,
103 | recv_sink = RecvSink,
104 | send = Send,
105 | send_sink = SendSink,
106 | send_inband = Send,
107 | recv_inband = RecvSink,
108 | ho_timer = HOTimer,
109 | ho_id = HOID
110 | }};
111 | {error, Reason} ->
112 | ?ERROR("can't connect to remote: ~p~n", [Reason]),
113 | {stop, Reason}
114 | end.
115 |
116 | forward(info, Info, State) -> handle_info(Info, forward, State).
117 |
118 | ho_initiated(info, {ho_socket, ok, RSock2}, #state{
119 | ho_id = Id, recv_inband = RecvInband, key = Key} = StateData) ->
120 | ?INFO("ho_initiated", []),
121 | mimicsocks_inband_recv:tapping(RecvInband, true),
122 | gen_tcp:send(RSock2, Id),
123 | {next_state, ho_wait_r2l,
124 | StateData#state{rsock2 = RSock2, ho_buf = <<>>, ho_id = next_id(Key, Id)},
125 | [{state_timeout, 3000, ho_wait_r2l}]};
126 | ho_initiated(info, {ho_socket, error, Reason}, #state{recv_inband = RecvInband} = StateData) ->
127 | ?WARNING("ho_initiated failed with reason ~p", [Reason]),
128 | mimicsocks_inband_recv:tapping(RecvInband, false),
129 | {next_state, forward, StateData};
130 | ho_initiated(info, Msg, Data) -> handle_info(Msg, ho_initiated, Data).
131 |
132 | ho_wait_r2l(state_timeout, _, StateData) ->
133 | {stop, {ho_wait_r2l, state_timeout}, StateData#state{ho_buf = <<"... truncated ...">>}};
134 | ho_wait_r2l(info, {inband, ho_r2l}, #state{recv = Recv, ho_buf = Buf,
135 | send_inband = SendInband,
136 | recv_inband = RecvInband} = StateData) ->
137 | Ref = mimicsocks_inband_send:recv_cmd(SendInband, <>, hold),
138 | mimicsocks_inband_recv:tapping(RecvInband, false),
139 | Recv ! {recv, self(), Buf},
140 | {next_state, ho_sending_complete, StateData#state{cmd_ref = Ref, ho_buf = <<>>}};
141 | ho_wait_r2l(info, {tcp, Socket, Bin}, #state{rsock2 = Socket, ho_buf = Buf} = StateData) ->
142 | {keep_state, StateData#state{ho_buf = <>}};
143 | ho_wait_r2l(info, Msg, Data) -> handle_info(Msg, ho_wait_r2l, Data).
144 |
145 | ho_sending_complete(info, {cmd_sent, Ref}, #state{send = Send} = StateData) ->
146 | Send ! {flush, Ref, self()},
147 | {next_state, ho_sending_complete, StateData};
148 | ho_sending_complete(info, {flush, Ref, SendSink}, #state{rsock2 = Socket2, send_sink = SendSink,
149 | rsock = Socket1, cmd_ref = Ref,
150 | send_inband = SendInband} = StateData) ->
151 | mimicsocks_inband_send:continue(SendInband),
152 | {next_state, ho_wait_close, StateData#state{rsock = Socket2, rsock2 = Socket1}};
153 | ho_sending_complete(info, {tcp, Socket, Bin}, #state{rsock2 = Socket, recv = Recv} = StateData) ->
154 | Recv ! {recv, self(), Bin},
155 | {keep_state, StateData};
156 | ho_sending_complete(info, Msg, Data) -> handle_info(Msg, ho_sending_complete, Data).
157 |
158 | ho_wait_close(info, {tcp_closed, Socket}, #state{rsock2 = Socket, other_ports = OtherPorts,
159 | send_sink = SendSink} = StateData) ->
160 | ?INFO("ho complete", []),
161 | mimicsocks_mimic:change(SendSink),
162 | {ok, HOTimer} = create_ho_timer(OtherPorts),
163 | {next_state, forward, StateData#state{rsock2 = undefined, ho_timer = HOTimer}};
164 | ho_wait_close(info, {recv, SendSink, Bin}, #state{rsock2 = Socket, send_sink = SendSink} = StateData) ->
165 | gen_tcp:send(Socket, Bin),
166 | {keep_state, StateData};
167 | ho_wait_close(info, {tcp, Socket, Bin}, #state{rsock2 = Socket, recv = Recv} = StateData) ->
168 | Recv ! {recv, self(), Bin},
169 | {keep_state, StateData};
170 | ho_wait_close(info, Msg, Data) -> handle_info(Msg, ho_wait_close, Data).
171 |
172 | handle_info(handover_now, _StateName, #state{ho_timer = HOTimer} = _StateData) ->
173 | ?INFO("handover_now", []),
174 | case HOTimer of
175 | undefined -> ok;
176 | _ -> timer:cancel(HOTimer)
177 | end,
178 | self() ! ho_timer,
179 | keep_state_and_data;
180 | handle_info(ho_timer, _StateName, #state{addr = Addr, other_ports = OtherPorts, extra_args = Extra} = StateData) ->
181 | ?INFO("ho_timer", []),
182 | Port = choice(OtherPorts),
183 | Pid = self(),
184 | spawn(fun () ->
185 | case connect(Addr, Port, Extra) of
186 | {ok, RSocket} ->
187 | gen_tcp:controlling_process(RSocket, Pid),
188 | Pid ! {ho_socket, ok, RSocket};
189 | {error, Reason} ->
190 | Pid ! {ho_socket, error, Reason}
191 | end end),
192 | {next_state, ho_initiated, StateData#state{ho_timer = undefined}};
193 | handle_info({inband_cmd, Pid, Cmds}, _StateName, #state{recv_inband = Pid} = StateData) ->
194 | parse_cmds(Cmds, self()),
195 | {keep_state, StateData};
196 | handle_info({tcp, RSocket, Bin}, _StateName, #state{rsock = RSocket, recv = Recv} = State) ->
197 | Recv ! {recv, self(), Bin},
198 | {keep_state, State};
199 | handle_info({tcp_closed, RSocket}, _StateName, #state{rsock = RSocket} = State) ->
200 | {stop, remote_down, State};
201 | handle_info({recv, SendSink, Bin}, _StateName, #state{send_sink = SendSink, rsock = Socket} = State) ->
202 | gen_tcp:send(Socket, Bin),
203 | {keep_state, State};
204 | handle_info({recv, Output, Bin}, _StateName, #state{send = Send, up_stream = Output} = State) ->
205 | Send ! {recv, self(), Bin},
206 | {keep_state, State};
207 | handle_info({recv, RecvSink, Bin}, _StateName, #state{recv_sink = RecvSink, up_stream = Output} = State) ->
208 | Output ! {recv, self(), Bin},
209 | {keep_state, State};
210 | handle_info({suspend_mimic, Duration}, _StateName, #state{send_sink = SendSink} = State) ->
211 | mimicsocks_mimic:suspend(SendSink, Duration),
212 | {keep_state, State};
213 | handle_info(stop, _StateName, State) ->
214 | {stop, normal, State};
215 | handle_info(Info, _StateName, State) ->
216 | ?WARNING("unexpected msg: ~p", [Info]),
217 | {keep_state, State}.
218 |
219 | terminate(_Reason, _StateName, #state{rsock = Sock1, rsock2 = Sock2} =
220 | _State) ->
221 | (catch gen_tcp:close(Sock1)),
222 | (catch gen_tcp:close(Sock2)),
223 | normal.
224 |
225 | code_change(_OldVsn, OldStateName, OldStateData, _Extra) ->
226 | {ok, OldStateName, OldStateData}.
227 |
228 | %---------------
229 | % utils
230 | %---------------
231 |
232 | -ifdef(debug).
233 | create_ho_timer(Ports) ->
234 | case length(Ports) > 0 of
235 | true -> timer:send_after(5 * 1000, ho_timer);
236 | _ -> {ok, undefined}
237 | end.
238 | -else.
239 | create_ho_timer(Ports) ->
240 | case length(Ports) > 0 of
241 | true -> timer:send_after((rand:uniform(10) + 20) * 60 * 1000, ho_timer);
242 | _ -> {ok, undefined}
243 | end.
244 | -endif.
245 |
246 | report_disconn(Socket, Type) ->
247 | case inet:peername(Socket) of
248 | {ok, {Addr, Port}} ->
249 | ?INFO("~p ~p disconnected (port ~p).", [Type, Addr, Port]);
250 | {error, _} ->
251 | ?INFO("~p disconnected", [Type])
252 | end.
253 |
254 | show_sock(Socket) ->
255 | {ok, {Addr, Port}} = inet:peername(Socket),
256 | {Addr, Port}.
257 |
258 | parse_cmds(<> = _Cmds, _Pid) -> ok;
259 | parse_cmds(<> = _Cmds, Pid) ->
260 | Pid ! {inband, ho_r2l},
261 | parse_cmds(Rem, Pid);
262 | parse_cmds(<> = _Cmds, Pid) ->
263 | Pid ! {inband, ho_complete},
264 | parse_cmds(Rem, Pid).
265 |
266 | -define(REMOTE_TCP_OPTS, [{packet, raw}, binary, {reuseaddr, true}, {keepalive, true},
267 | {send_timeout, 3000}, {send_timeout_close, true}]).
268 |
269 | connect(ServerAddr, ServerPort, [{http_proxy, ProxyAddr, ProxyPort}]) ->
270 | case gen_tcp:connect(ProxyAddr, ProxyPort, [{active, false} | ?REMOTE_TCP_OPTS]) of
271 | {ok, Socket} ->
272 | Req = ["CONNECT ", ip_to_list(ServerAddr), ":", integer_to_list(ServerPort), " HTTP/1.1\r\n\r\n"],
273 | gen_tcp:send(Socket, Req),
274 | case wait_result(Socket) of
275 | {ok, <<>>} ->
276 | inet:setopts(Socket, [{active, true}]),
277 | {ok, Socket};
278 | {ok, Remain} ->
279 | self() ! {tcp, Socket, Remain},
280 | inet:setopts(Socket, [{active, true}]),
281 | {ok, Socket};
282 | OtherError ->
283 | ?ERROR("~p~n", [OtherError]),
284 | gen_tcp:close(Socket),
285 | OtherError
286 | end;
287 | Result -> Result
288 | end;
289 | connect(ServerAddr, ServerPort, _) ->
290 | gen_tcp:connect(ServerAddr, ServerPort, [{active, true} | ?REMOTE_TCP_OPTS]).
291 |
292 | ip_to_list(X) when is_list(X) -> X;
293 | ip_to_list({A,B,C,D}) ->
294 | io_lib:format("~B.~B.~B.~B",[A,B,C,D]);
295 | ip_to_list({A,B,C,D,E,F,G,H}) ->
296 | io_lib:format("~.16B.~.16B.~.16B.~.16B.~.16B.~.16B.~.16B.~.16B",[A,B,C,D,E,F,G,H]).
297 |
298 | wait_line(_Socket, _Acc, N) when N < 0 -> {error, timeout};
299 | wait_line(Socket, Acc, N) ->
300 | receive
301 | after 50 -> ok
302 | end,
303 | case gen_tcp:recv(Socket, 0) of
304 | {ok, Data} ->
305 | All = <>,
306 | case binary:split(All, <<"\r\n\r\n">>) of
307 | [_, Remain] -> {ok, Remain};
308 | [_] -> wait_line(Socket, All, N - 1)
309 | end;
310 | X -> X
311 | end.
312 |
313 | wait_result(Socket) ->
314 | Expected = <<"HTTP/1.1 200">>,
315 | case gen_tcp:recv(Socket, size(Expected), 2000) of
316 | {ok, Expected} -> wait_line(Socket, <<>>, 40);
317 | {ok, <<"HTTP/1.0 200">>} -> wait_line(Socket, <<>>, 40);
318 | {ok, Other} -> {error, Other};
319 | X -> X
320 | end.
321 |
322 | next_id(Key, ID) -> mimicsocks_crypt:hmac_sha(Key, ID, ?MIMICSOCKS_HELLO_SIZE).
323 |
324 | %@doc generate a IVEC using random algo in order to randomized entropy on IVEC
325 | gen_ivec() ->
326 | L = lists:seq(1, ?MIMICSOCKS_HELLO_SIZE),
327 | T = rand:uniform(256) - 1,
328 | case rand:uniform(3) of
329 | 1 -> crypto:strong_rand_bytes(?MIMICSOCKS_HELLO_SIZE);
330 | _ ->
331 | Q = 256 div rand:uniform(10),
332 | list_to_binary([(rand:uniform(Q) + T) rem 256 || _ <- L])
333 | end.
334 |
--------------------------------------------------------------------------------
/src/mimicsocks_wormhole_remote.erl:
--------------------------------------------------------------------------------
1 | %@doc generalized communication channel
2 | %@author foldl
3 | -module(mimicsocks_wormhole_remote).
4 |
5 | -include("mimicsocks.hrl").
6 |
7 | -behaviour(gen_statem).
8 |
9 | % api
10 | -export([start_link/1, stop/1, recv/2, socket_ready/2, suspend_mimic/2]).
11 |
12 | % callbacks
13 | -export([init/1, callback_mode/0, terminate/3, code_change/4]).
14 |
15 | -import(mimicsocks_wormhole_local, [show_sock/1, report_disconn/2, next_id/2]).
16 |
17 | % FSM States
18 | -export([
19 | init/3,
20 | wait_ivec/3,
21 | forward/3,
22 | wait_sending_cmd/3,
23 | wait_ho_complete/3,
24 | bad_key/3
25 | ]).
26 |
27 | -record(state,
28 | {
29 | send, % process chain
30 | send_sink,
31 | recv,
32 | recv_sink,
33 | recv_inband,
34 | send_inband,
35 |
36 | rsock, % remote socket
37 | rsock2, % handover socket
38 | key,
39 | ivec,
40 | ho_id,
41 |
42 | up_stream,
43 |
44 | cmd_ref,
45 | buff = <<>>
46 | }
47 | ).
48 |
49 | start_link(Args) ->
50 | gen_statem:start_link(?MODULE, Args, []).
51 |
52 | stop(Pid) -> gen_statem:stop(Pid).
53 |
54 | socket_ready(Pid, LSock) when is_pid(Pid), is_port(LSock) ->
55 | gen_tcp:controlling_process(LSock, Pid),
56 | gen_statem:cast(Pid, {socket_ready, LSock}).
57 |
58 | recv(Pid, Data) -> Pid ! {recv, self(), Data}.
59 |
60 | suspend_mimic(Pid, Duration) -> Pid ! {suspend_mimic, Duration}.
61 |
62 | %%%===================================================================
63 | %%% gen_fsm callbacks
64 | %%%===================================================================
65 | init([Upstream, Key]) ->
66 | {ok, init, #state{up_stream = Upstream, key = Key}}.
67 |
68 | callback_mode() ->
69 | state_functions.
70 |
71 | init(cast, {socket_ready, Socket}, State) when is_port(Socket) ->
72 | ok = (catch inet:setopts(Socket, [{active, true}, {packet, raw}, binary])),
73 | {next_state, wait_ivec, State#state{rsock = Socket}};
74 | init(info, Msg, StateData) -> handle_info(Msg, init, StateData).
75 |
76 | wait_ivec(cast, {local, Bin}, #state{buff = Buff, key = Key} = State) ->
77 | All = <>,
78 | case All of
79 | <> ->
80 | case next_id(Key, IVec) of
81 | ID0 ->
82 | RecvSink = mimicsocks_inband_recv:start_link([self(), self()]),
83 | mimicsocks_inband_recv:set_key(RecvSink, Key),
84 | Recv = mimicsocks_crypt:start_link(decrypt, [RecvSink, mimicsocks_crypt:init_aes_ctr_dec(Key, IVec)]),
85 |
86 | {ok, SendSink} = mimicsocks_mimic:start_link([self()]),
87 | SendCrypt = mimicsocks_crypt:start_link(encrypt, [SendSink, mimicsocks_crypt:init_aes_ctr_enc(Key, IVec)]),
88 | Send = mimicsocks_inband_send:start_link([SendCrypt, self()]),
89 | mimicsocks_inband_send:set_key(Send, Key),
90 | Recv ! {recv, self(), Rem},
91 |
92 | HOID = next_id(Key, ID0),
93 | mimicsocks_cfg:register_remote(HOID, self()),
94 | {next_state, forward, State#state{
95 | ivec = IVec,
96 | recv = Recv,
97 | recv_sink = RecvSink,
98 | send = Send,
99 | send_sink = SendSink,
100 | recv_inband = RecvSink,
101 | send_inband = Send,
102 | ho_id = HOID
103 | }};
104 | _ ->
105 | create_close_timer(),
106 | {next_state, bad_key, State}
107 | end;
108 | _ ->
109 |
110 | {next_state, wait_ivec, State#state{buff = All}}
111 | end;
112 | wait_ivec(info, {tcp, _Socket, Bin}, StateData) ->
113 | wait_ivec(cast, {local, Bin}, StateData);
114 | wait_ivec(info, Msg, StateData) -> handle_info(Msg, wait_ivec, StateData).
115 |
116 | forward(info, Msg, StateData) -> handle_info(Msg, forward, StateData).
117 |
118 | bad_key(info, {tcp, _Socket, _Bin}, StateData) -> {keep_state, StateData};
119 | bad_key(info, close, StateData) -> {stop, bad_key, StateData};
120 | bad_key(info, Msg, StateData) -> handle_info(Msg, bad_key, StateData).
121 |
122 | wait_sending_cmd(info,{cmd_sent, Ref}, #state{cmd_ref = Ref, send = Send} = State) ->
123 | Send ! {flush, Ref, self()},
124 | {keep_state, State};
125 | wait_sending_cmd(info,{flush, Ref, SendSink}, #state{cmd_ref = Ref, send_sink = SendSink,
126 | send_inband = SendInband} = State) ->
127 | mimicsocks_inband_send:continue(SendInband),
128 | {next_state, wait_ho_complete, State};
129 | wait_sending_cmd(info, {tcp, Socket, Bin}, #state{rsock2 = Socket, buff = Buff} = State) ->
130 | {keep_state, State#state{buff = <>}};
131 | wait_sending_cmd(info, Msg, State) -> handle_info(Msg, wait_sending_cmd, State).
132 |
133 | wait_ho_complete(info, {inband, ho_complete}, #state{rsock = Sock1, rsock2 = Sock2,
134 | recv = Recv, buff = Buff,
135 | recv_inband = RecvInband,
136 | send_sink = SendSink} = State) ->
137 | mimicsocks_inband_recv:tapping(RecvInband, false),
138 | gen_tcp:close(Sock1),
139 | mimicsocks_mimic:change(SendSink),
140 | Recv ! {recv, self(), Buff},
141 | {next_state, forward, State#state{rsock = Sock2, rsock2 = undefined, buff = <<>>}};
142 | wait_ho_complete(info, {tcp, Socket, Bin}, #state{rsock2 = Socket, buff = Buff} = State) ->
143 | {keep_state, State#state{buff = <>}};
144 | wait_ho_complete(info, {recv, SendSink, Bin}, #state{send_sink = SendSink,
145 | rsock2 = Socket} = State) ->
146 | gen_tcp:send(Socket, Bin),
147 | {keep_state, State};
148 | wait_ho_complete(info, Msg, State) -> handle_info(Msg, wait_ho_complete, State).
149 |
150 | handle_info({ho_socket, Socket}, _StateName, #state{send_inband = SendInband, recv_inband = RecvInband,
151 | ho_id = Id, key = Key} = StateData) ->
152 | ok = inet:setopts(Socket, [{active, true}]),
153 | mimicsocks_inband_recv:tapping(RecvInband, true),
154 | Ref = mimicsocks_inband_send:recv_cmd(SendInband, <>, hold),
155 | NewId = next_id(Key, Id),
156 | mimicsocks_cfg:deregister_remote(Id),
157 | mimicsocks_cfg:register_remote(NewId, self()),
158 | {next_state, wait_sending_cmd, StateData#state{rsock2 = Socket, cmd_ref = Ref,
159 | ho_id = NewId}};
160 | handle_info({inband_cmd, Pid, Cmds}, _StateName, #state{recv_sink = Pid} = State) ->
161 | parse_cmds(Cmds, self()),
162 | {keep_state, State};
163 | handle_info({recv, RecvSink, Data}, _StateName, #state{up_stream = Upstream, recv_sink = RecvSink} = State) ->
164 | Upstream ! {recv, self(), Data},
165 | {keep_state, State};
166 | handle_info({recv, SendSink, Data}, _StateName, #state{rsock = Socket, send_sink = SendSink} = State) ->
167 | gen_tcp:send(Socket, Data),
168 | {keep_state, State};
169 | handle_info({recv, Upstream, Data}, _StateName, #state{up_stream = Upstream, send = Send} = State) ->
170 | Send ! {recv, self(), Data},
171 | {keep_state, State};
172 | handle_info({tcp, Socket, Bin}, _StateName, #state{rsock = Socket, recv = Recv} = State) ->
173 | Recv ! {recv, self(), Bin},
174 | {keep_state, State};
175 | handle_info({tcp_closed, Socket}, _StateName, #state{rsock = Socket} = StateData) ->
176 | report_disconn(Socket, "Remote"),
177 | {stop, local_down, StateData};
178 | handle_info({tcp_closed, Socket2}, _StateName, StateData) ->
179 | report_disconn(Socket2, "Remote2"),
180 | {stop, ho_error, StateData};
181 | handle_info({suspend_mimic, Duration}, _StateName, #state{send_sink = SendSink} = State) ->
182 | mimicsocks_mimic:suspend(SendSink, Duration),
183 | {keep_state, State};
184 | handle_info({cmd_sent, Ref}, StateName, StateData) ->
185 | ?WARNING("cmd_sent ~p in state ~p", [Ref, StateName]),
186 | {keep_state, StateData};
187 | handle_info(Info, StateName, State) ->
188 | ?ERROR("unexpected ~p in state ~p", [Info, StateName]),
189 | {keep_state, State}.
190 |
191 | terminate(_Reason, _StateName, #state{rsock=RSocket,
192 | rsock2 = RSocket2,
193 | recv = Recv,
194 | send = Send,
195 | ho_id = Id}) ->
196 | (catch gen_tcp:close(RSocket)),
197 | (catch gen_tcp:close(RSocket2)),
198 | (catch Recv ! stop),
199 | (catch Send ! stop),
200 | mimicsocks_cfg:deregister_remote(Id),
201 | ok.
202 |
203 | code_change(_OldVsn, StateName, State, _Extra) ->
204 | {ok, StateName, State}.
205 |
206 | %---------------
207 | % utils
208 | %---------------
209 | parse_cmds(<> = _Cmds, _Pid) -> ok;
210 | parse_cmds(<> = _Cmds, Pid) ->
211 | Pid ! {inband, start_ho, Port},
212 | parse_cmds(Rem, Pid);
213 | parse_cmds(<> = _Cmds, Pid) ->
214 | Pid ! {inband, ho_complete},
215 | parse_cmds(Rem, Pid).
216 |
217 | -ifdef(debug).
218 | create_close_timer() -> timer:send_after(100, close).
219 | -else.
220 | create_close_timer() -> timer:send_after((rand:uniform(50) + 1) * 60 * 1000, close).
221 | -endif.
222 |
--------------------------------------------------------------------------------
/test/mimicsocks_test.erl:
--------------------------------------------------------------------------------
1 | %@doc some tests
2 | %@author foldl
3 | -module(mimicsocks_test).
4 |
5 | -include_lib("eunit/include/eunit.hrl").
6 | -include("mimicsocks.hrl").
7 |
8 | -compile([export_all]).
9 |
10 | crypt_test() ->
11 | Key = crypto:strong_rand_bytes(256 div 8),
12 | IVec = crypto:strong_rand_bytes(?MIMICSOCKS_HELLO_SIZE),
13 | Cipher = crypto:stream_init(aes_ctr, Key, IVec),
14 | Decrypt = mimicsocks_crypt:start_link(decrypt, [self(), Cipher]),
15 | Encrypt = mimicsocks_crypt:start_link(encrypt, [Decrypt, Cipher]),
16 | io:format("~p~n", [{Encrypt, Decrypt}]),
17 | test_data({Encrypt, Decrypt}, {<<1,2,3,4>>, <<1,2,3,4>>}),
18 | test_data({Encrypt, Decrypt}, {<<5, 1,2,3,4>>, <<5, 1,2,3,4>>}).
19 |
20 | test_data({Entry, Exit}, {Data, Expected}) ->
21 | Entry ! {recv, self(), Data},
22 | receive
23 | {recv, Exit, Expected} -> ok;
24 | X -> throw(X)
25 | after
26 | 1000 -> throw(timeout)
27 | end.
28 |
29 |
30 | send_and_wait(Send, Msg) ->
31 | receive
32 | Msg -> ok
33 | after
34 | 10 ->
35 | Buf = crypto:strong_rand_bytes(rand:uniform(20) + 5),
36 | mimicsocks_inband_send:recv(Send, Buf),
37 | send_and_wait(Send, Msg)
38 | end.
39 |
40 | dump_data(Pid) ->
41 | receive
42 | {recv, Pid, <<>>} -> dump_data(Pid);
43 | {recv, Pid, Data} ->
44 | io:format("~p~n", [Data]),
45 | dump_data(Pid)
46 | after
47 | 1000 -> ok
48 | end.
49 |
50 | socks5_server() ->
51 | Server = mimicsocks_tcp_listener:start_link([{127,0,0,1}, 8888,
52 | mimicsocks_remote_socks, [undefined]]),
53 | Server.
54 |
55 | http_server() ->
56 | spawn_link(fun http_server0/0).
57 |
58 | http_server0() ->
59 | Opts = [binary, {packet, raw}, {ip, {127,0,0,1}},
60 | {keepalive, true}, {backlog, 30}, {active, true}],
61 | case gen_tcp:listen(8001, Opts) of
62 | {ok, LSock} ->
63 | io:format("http_server on 8001\n", []),
64 | case gen_tcp:accept(LSock) of
65 | {ok, Socket} ->
66 | wait_req(Socket),
67 | gen_tcp:send(Socket, "HTTP/1.1 200 OK\r\n"),
68 | gen_tcp:send(Socket, "Date: Mon, 23 May 2005 22:38:34 GMT\r\n"),
69 | gen_tcp:send(Socket, "Content-Type: text/html; charset=UTF-8\r\n"),
70 | gen_tcp:send(Socket, "Content-Encoding: UTF-8\r\n"),
71 | gen_tcp:send(Socket, "Content-Length: 400000000\r\n"),
72 | gen_tcp:send(Socket, "Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT\r\n"),
73 | gen_tcp:send(Socket, "Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)\r\n"),
74 | gen_tcp:send(Socket, "ETag: \"3f80f-1b6-3e1cb03b\"\r\n"),
75 | gen_tcp:send(Socket, "Accept-Ranges: bytes\r\n"),
76 | gen_tcp:send(Socket, "Connection: close\r\n\r\n"),
77 | send(Socket, 0),
78 | gen_tcp:close(LSock),
79 | io:format("http_server stopped\n", []);
80 | {error, Reason} ->
81 | {error, Reason}
82 | end;
83 | {error, Reason} ->
84 | io:format("error: ~p", [Reason]),
85 | {stop, Reason}
86 | end.
87 |
88 | wait_req(Socket) ->
89 | receive
90 | {tcp, Socket, Bin} -> io:format("REQ: ~p~n", [Bin])
91 | end.
92 |
93 | send(Socket, N) ->
94 | gen_tcp:send(Socket, [integer_to_list(N), ":abcdefghijklmnopqrstuvwxyz\r\n"]),
95 | receive
96 | {tcp_closed, Socket} -> ok
97 | after 100 ->
98 | send(Socket, N + 1)
99 | end.
--------------------------------------------------------------------------------