girl 0.75.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of girl might be problematic. Click here for more details.

@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ac2f8ddb303a57e5c898754c88710847686e84b3ef406c053ef926424da78e2b
4
+ data.tar.gz: 4506688a3176c9effe1804e3d247379a79d96906e4c8417d6f0660ecaa082141
5
+ SHA512:
6
+ metadata.gz: 34559a0ab1375eb9b07bab0fbea3d762e94ac7282ed8868e3ab672aa816f87f6edc3273b1204c565c189201bd2a49e993477a6da525b472e95c18b804526323e
7
+ data.tar.gz: d7580c92099e6974287fbc8384010d3b5ed34966832ff9c1a0bda42cdeac923768fa431b84c83dee8926c3b4bc8f2b0761bdb9a4a14212870a2dde3abb89f338
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'girl/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'girl'
8
+ spec.version = Girl::VERSION
9
+ spec.authors = ['takafan']
10
+ spec.email = ['qqtakafan@gmail.com']
11
+
12
+ spec.summary = %q{妹子}
13
+ spec.description = %q{while internet is evil, here's a girl.}
14
+ spec.homepage = 'https://github.com/takafan/girl'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = %w[
18
+ girl.gemspec
19
+ lib/girl.rb
20
+ lib/girl/custom.rb
21
+ lib/girl/head.rb
22
+ lib/girl/proxy_custom.rb
23
+ lib/girl/proxy_worker.rb
24
+ lib/girl/proxy.rb
25
+ lib/girl/proxyd_custom.rb
26
+ lib/girl/proxyd_worker.rb
27
+ lib/girl/proxyd.rb
28
+ lib/girl/udp.rb
29
+ lib/girl/udpd.rb
30
+ lib/girl/version.rb
31
+ ]
32
+
33
+ spec.require_paths = ['lib']
34
+ end
@@ -0,0 +1,2 @@
1
+ require 'girl/proxy'
2
+ require 'girl/proxyd'
@@ -0,0 +1,12 @@
1
+ module Girl
2
+ module Custom
3
+ def encode( data )
4
+ # overwrite me, you'll be free
5
+ data
6
+ end
7
+
8
+ def decode( data )
9
+ data
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ module Girl
2
+ PACK_SIZE = 1328 # 包大小 1400(console MTU) - 8(PPPoE header) - 40(IPv6 header) - 8(UDP header) - 8(pack id) - 8(src id) = 1328
3
+ CHUNK_SIZE = PACK_SIZE * 1000 # 块大小
4
+ WBUFFS_LIMIT = 1000 # 写前上限,超过上限结一个块
5
+ WMEMS_LIMIT = 100_000 # 写后上限,达到上限暂停写
6
+ RESUME_BELOW = 50_000 # 降到多少以下恢复写
7
+ EXPIRE_NEW = 10 # 创建之后多久没有流量进来,过期
8
+ EXPIRE_AFTER = 300 # 多久没有新流量,过期
9
+ CHECK_EXPIRE_INTERVAL = 30 # 检查过期间隔
10
+ STATUS_INTERVAL = 0.5 # 发送状态间隔
11
+ SEND_STATUS_UNTIL = 10 # 持续的告之对面状态,直到没有流量往来,持续多少秒
12
+ BREAK_SEND_MISS = 10_000 # miss包个数上限,达到上限忽略要后面的段,可控碎片缓存
13
+ CONFUSE_UNTIL = 5 # 混淆前几个包
14
+ RESOLV_CACHE_EXPIRE = 300 # dns查询结果缓存多久过期
15
+ TUND_PORT = 1
16
+ HEARTBEAT = 2
17
+ A_NEW_SOURCE = 3
18
+ PAIRED = 4
19
+ DEST_STATUS = 5
20
+ SOURCE_STATUS = 6
21
+ MISS = 7
22
+ FIN1 = 8
23
+ GOT_FIN1 = 9
24
+ FIN2 = 10
25
+ GOT_FIN2 = 11
26
+ TUND_FIN = 12
27
+ TUN_FIN = 13
28
+ HTTP_OK = "HTTP/1.1 200 OK\r\n\r\n"
29
+ # https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
30
+ RESERVED_ROUTE = <<EOF
31
+ 0.0.0.0/8
32
+ 10.0.0.0/8
33
+ 100.64.0.0/10
34
+ 127.0.0.0/8
35
+ 169.254.0.0/16
36
+ 172.16.0.0/12
37
+ 192.0.0.0/24
38
+ 192.0.2.0/24
39
+ 192.31.196.0/24
40
+ 192.52.193.0/24
41
+ 192.88.99.0/24
42
+ 192.168.0.0/16
43
+ 192.175.48.0/24
44
+ 198.18.0.0/15
45
+ 198.51.100.0/24
46
+ 203.0.113.0/24
47
+ 240.0.0.0/4
48
+ 255.255.255.255/32
49
+ EOF
50
+ end
@@ -0,0 +1,208 @@
1
+ require 'etc'
2
+ require 'girl/head'
3
+ require 'girl/proxy_custom'
4
+ require 'girl/proxy_worker'
5
+ require 'girl/version'
6
+ require 'ipaddr'
7
+ require 'json'
8
+ require 'socket'
9
+
10
+ ##
11
+ # Girl::Proxy - 代理服务,近端。
12
+ #
13
+ # 包结构
14
+ # ======
15
+ #
16
+ # tun-proxyd:
17
+ #
18
+ # hello
19
+ #
20
+ # proxyd-tun:
21
+ #
22
+ # Q>: 0 ctlmsg -> C: 1 tund port -> n: tund port
23
+ #
24
+ # tun-tund:
25
+ #
26
+ # Q>: 0 ctlmsg -> C: 2 heartbeat -> C: random char
27
+ # 3 a new source -> Q>: src id -> encoded destination address
28
+ # 4 paired -> Q>: src id -> n: dst port
29
+ # 5 dest status -> n: dst port -> Q>: biggest relayed dst pack id -> Q>: continue src pack id
30
+ # 6 source status -> Q>: src id -> Q>: biggest relayed src pack id -> Q>: continue dst pack id
31
+ # 7 miss -> Q>/n: src id / dst port -> Q>: pack id begin -> Q>: pack id end
32
+ # 8 fin1 -> Q>/n: src id / dst port -> Q>: biggest src pack id / biggest dst pack id -> Q>: continue dst pack id / continue src pack id
33
+ # 9 not use
34
+ # 10 fin2 -> Q>/n: src id / dst port
35
+ # 11 not use
36
+ # 12 tund fin
37
+ # 13 tun fin
38
+ #
39
+ # Q>: 1+ pack_id -> Q>/n: src id / dst port -> traffic
40
+ #
41
+ # close logic
42
+ # ===========
43
+ #
44
+ # 1-1. after close src -> dst closed ? no -> send fin1
45
+ # 1-2. tun recv fin2 -> del src ext
46
+ #
47
+ # 2-1. tun recv fin1 -> all traffic received ? -> close src after write
48
+ # 2-2. tun recv traffic -> dst closed and all traffic received ? -> close src after write
49
+ # 2-3. after close src -> dst closed ? yes -> del src ext -> send fin2
50
+ #
51
+ # 3-1. after close dst -> src closed ? no -> send fin1
52
+ # 3-2. tund recv fin2 -> del dst ext
53
+ #
54
+ # 4-1. tund recv fin1 -> all traffic received ? -> close dst after write
55
+ # 4-2. tund recv traffic -> src closed and all traffic received ? -> close dst after write
56
+ # 4-3. after close dst -> src closed ? yes -> del dst ext -> send fin2
57
+ #
58
+ module Girl
59
+ class Proxy
60
+
61
+ def initialize( config_path = nil )
62
+ unless config_path
63
+ config_path = File.expand_path( '../girl.conf.json', __FILE__ )
64
+ end
65
+
66
+ unless File.exist?( config_path )
67
+ raise "missing config file #{ config_path }"
68
+ end
69
+
70
+ # {
71
+ # "proxy_port": 6666, // 代理服务,近端(本地)端口
72
+ # "proxyd_host": "1.2.3.4", // 代理服务,远端服务器
73
+ # "proxyd_port": 6060, // 代理服务,远端端口
74
+ # "direct_path": "girl.direct.txt", // 直连ip段
75
+ # "remote_path": "girl.remote.txt", // 交给远端解析的域名列表
76
+ # "proxy_tmp_dir": "/tmp/girl.proxy", // 近端缓存根路径
77
+ # "proxyd_tmp_dir": "/tmp/girl.proxyd", // 远端缓存根路径
78
+ # "im": "girl", // 标识,用来识别近端
79
+ # "worker_count": 4 // 子进程数,默认取cpu个数
80
+ # }
81
+ conf = JSON.parse( IO.binread( config_path ), symbolize_names: true )
82
+ proxy_port = conf[ :proxy_port ]
83
+ proxyd_host = conf[ :proxyd_host ]
84
+ proxyd_port = conf[ :proxyd_port ]
85
+ direct_path = conf[ :direct_path ]
86
+ remote_path = conf[ :remote_path ]
87
+ proxy_tmp_dir = conf[ :proxy_tmp_dir ]
88
+ im = conf[ :im ]
89
+ worker_count = conf[ :worker_count ]
90
+
91
+ unless proxy_port
92
+ proxy_port = 6666
93
+ end
94
+
95
+ unless proxyd_host
96
+ raise "missing proxyd host"
97
+ end
98
+
99
+ unless proxyd_port
100
+ proxyd_port = 6060
101
+ end
102
+
103
+ directs = []
104
+
105
+ if direct_path
106
+ unless File.exist?( direct_path )
107
+ raise "not found direct file #{ direct_path }"
108
+ end
109
+
110
+ directs = ( [ Addrinfo.ip( proxyd_host ).ip_address ] + RESERVED_ROUTE.split( "\n" ) + IO.binread( direct_path ).split( "\n" ) ).map { | line | IPAddr.new( line.strip ) }
111
+ end
112
+
113
+ remotes = []
114
+
115
+ if remote_path
116
+ unless File.exist?( remote_path )
117
+ raise "not found remote file #{ remote_path }"
118
+ end
119
+
120
+ remotes = IO.binread( remote_path ).split( "\n" ).map { | line | line.strip }
121
+ end
122
+
123
+ unless proxy_tmp_dir
124
+ proxy_tmp_dir = '/tmp/girl.proxy'
125
+ end
126
+
127
+ unless File.exist?( proxy_tmp_dir )
128
+ Dir.mkdir( proxy_tmp_dir )
129
+ end
130
+
131
+ src_chunk_dir = File.join( proxy_tmp_dir, 'src.chunk' )
132
+
133
+ unless Dir.exist?( src_chunk_dir )
134
+ Dir.mkdir( src_chunk_dir )
135
+ end
136
+
137
+ dst_chunk_dir = File.join( proxy_tmp_dir, 'dst.chunk' )
138
+
139
+ unless Dir.exist?( dst_chunk_dir )
140
+ Dir.mkdir( dst_chunk_dir )
141
+ end
142
+
143
+ tun_chunk_dir = File.join( proxy_tmp_dir, 'tun.chunk' )
144
+
145
+ unless Dir.exist?( tun_chunk_dir )
146
+ Dir.mkdir( tun_chunk_dir )
147
+ end
148
+
149
+ unless im
150
+ im = 'girl'
151
+ end
152
+
153
+ nprocessors = Etc.nprocessors
154
+
155
+ if worker_count.nil? || worker_count <= 0 || worker_count > nprocessors
156
+ worker_count = nprocessors
157
+ end
158
+
159
+ title = "girl proxy #{ Girl::VERSION }"
160
+ puts title
161
+ puts "proxy port #{ proxy_port }"
162
+ puts "proxyd host #{ proxyd_host }"
163
+ puts "proxyd port #{ proxyd_port }"
164
+ puts "#{ direct_path } #{ directs.size } directs"
165
+ puts "#{ remote_path } #{ remotes.size } remotes"
166
+ puts "src chunk dir #{ src_chunk_dir }"
167
+ puts "dst chunk dir #{ dst_chunk_dir }"
168
+ puts "tun chunk dir #{ tun_chunk_dir }"
169
+ puts "im #{ im }"
170
+ puts "worker count #{ worker_count }"
171
+
172
+ if RUBY_PLATFORM.include?( 'linux' )
173
+ $0 = title
174
+ workers = []
175
+
176
+ worker_count.times do | i |
177
+ workers << fork do
178
+ $0 = 'girl proxy worker'
179
+ worker = Girl::ProxyWorker.new( proxy_port, proxyd_host, proxyd_port, directs, remotes, src_chunk_dir, dst_chunk_dir, tun_chunk_dir, im )
180
+
181
+ Signal.trap( :TERM ) do
182
+ puts "w#{ i } exit"
183
+ worker.quit!
184
+ end
185
+
186
+ worker.looping
187
+ end
188
+ end
189
+
190
+ Signal.trap( :TERM ) do
191
+ puts 'trap TERM'
192
+ workers.each do | pid |
193
+ begin
194
+ Process.kill( :TERM, pid )
195
+ rescue Errno::ESRCH => e
196
+ puts e.class
197
+ end
198
+ end
199
+ end
200
+
201
+ Process.waitall
202
+ else
203
+ Girl::ProxyWorker.new( proxy_port, proxyd_host, proxyd_port, directs, remotes, src_chunk_dir, dst_chunk_dir, tun_chunk_dir, im ).looping
204
+ end
205
+ end
206
+
207
+ end
208
+ end
@@ -0,0 +1,15 @@
1
+ require 'girl/custom'
2
+
3
+ module Girl
4
+ class ProxyCustom
5
+ include Custom
6
+
7
+ def initialize( im )
8
+ @im = im
9
+ end
10
+
11
+ def hello
12
+ @im
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,1425 @@
1
+ module Girl
2
+ class ProxyWorker
3
+
4
+ ##
5
+ # initialize
6
+ #
7
+ def initialize( proxy_port, proxyd_host, proxyd_port, directs, remotes, src_chunk_dir, dst_chunk_dir, tun_chunk_dir, im )
8
+ @proxyd_host = proxyd_host
9
+ @proxyd_addr = Socket.sockaddr_in( proxyd_port, proxyd_host )
10
+ @directs = directs
11
+ @remotes = remotes
12
+ @src_chunk_dir = src_chunk_dir
13
+ @dst_chunk_dir = dst_chunk_dir
14
+ @tun_chunk_dir = tun_chunk_dir
15
+ @custom = Girl::ProxyCustom.new( im )
16
+ @mutex = Mutex.new
17
+ @reads = []
18
+ @writes = []
19
+ @roles = {} # sock => :dotr / :proxy / :src / :dst / :tun
20
+ @src_infos = {} # src => {}
21
+ @dst_infos = {} # dst => {}
22
+ @resolv_caches = {} # domain => [ ip, created_at ]
23
+
24
+ dotr, dotw = IO.pipe
25
+ @dotw = dotw
26
+ add_read( dotr, :dotr )
27
+ new_a_proxy( proxy_port )
28
+ end
29
+
30
+ ##
31
+ # looping
32
+ #
33
+ def looping
34
+ puts "p#{ Process.pid } #{ Time.new } looping"
35
+ loop_check_expire
36
+ loop_check_status
37
+
38
+ loop do
39
+ rs, ws = IO.select( @reads, @writes )
40
+
41
+ @mutex.synchronize do
42
+ # 先写,再读
43
+ ws.each do | sock |
44
+ case @roles[ sock ]
45
+ when :src
46
+ write_src( sock )
47
+ when :dst
48
+ write_dst( sock )
49
+ when :tun
50
+ write_tun( sock )
51
+ end
52
+ end
53
+
54
+ rs.each do | sock |
55
+ case @roles[ sock ]
56
+ when :dotr
57
+ read_dotr( sock )
58
+ when :proxy
59
+ read_proxy( sock )
60
+ when :src
61
+ read_src( sock )
62
+ when :dst
63
+ read_dst( sock )
64
+ when :tun
65
+ read_tun( sock )
66
+ end
67
+ end
68
+ end
69
+ end
70
+ rescue Interrupt => e
71
+ puts e.class
72
+ quit!
73
+ end
74
+
75
+ ##
76
+ # quit!
77
+ #
78
+ def quit!
79
+ if @tun && !@tun.closed? && @tun_info[ :tund_addr ]
80
+ # puts "debug1 send tun fin"
81
+ data = [ 0, TUN_FIN ].pack( 'Q>C' )
82
+ @tun.sendmsg( data, 0, @tun_info[ :tund_addr ] )
83
+ end
84
+
85
+ # puts "debug1 exit"
86
+ exit
87
+ end
88
+
89
+ private
90
+
91
+ ##
92
+ # loop check expire
93
+ #
94
+ def loop_check_expire
95
+ Thread.new do
96
+ loop do
97
+ sleep CHECK_EXPIRE_INTERVAL
98
+
99
+ @mutex.synchronize do
100
+ need_trigger = false
101
+ now = Time.new
102
+
103
+ if @tun && !@tun.closed?
104
+ is_expired = @tun_info[ :last_recv_at ] ? ( now - @tun_info[ :last_recv_at ] > EXPIRE_AFTER ) : ( now - @tun_info[ :created_at ] > EXPIRE_NEW )
105
+
106
+ if is_expired
107
+ puts "p#{ Process.pid } #{ Time.new } expire tun"
108
+ set_is_closing( @tun )
109
+ else
110
+ data = [ 0, HEARTBEAT, rand( 128 ) ].pack( 'Q>CC' )
111
+ # puts "debug1 #{ Time.new } heartbeat"
112
+ add_tun_ctlmsg( data )
113
+
114
+ @tun_info[ :src_exts ].each do | src_id, src_ext |
115
+ if src_ext[ :src ].closed? && ( now - src_ext[ :last_continue_at ] > EXPIRE_AFTER )
116
+ puts "p#{ Process.pid } #{ Time.new } expire src ext #{ src_ext[ :destination_domain ] }"
117
+ del_src_ext( src_id )
118
+ end
119
+ end
120
+ end
121
+
122
+ need_trigger = true
123
+ end
124
+
125
+ @src_infos.each do | src, src_info |
126
+ if now - src_info[ :last_continue_at ] > EXPIRE_AFTER
127
+ puts "p#{ Process.pid } #{ Time.new } expire src #{ src_info[ :destination_domain ] }"
128
+ set_is_closing( src )
129
+ need_trigger = true
130
+ end
131
+ end
132
+
133
+ @dst_infos.each do | dst, dst_info |
134
+ if now - dst_info[ :last_continue_at ] > EXPIRE_AFTER
135
+ puts "p#{ Process.pid } #{ Time.new } expire dst #{ dst_info[ :domain ] }"
136
+ set_is_closing( dst )
137
+ need_trigger = true
138
+ end
139
+ end
140
+
141
+ if need_trigger
142
+ next_tick
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ ##
150
+ # loop check status
151
+ #
152
+ def loop_check_status
153
+ Thread.new do
154
+ loop do
155
+ sleep STATUS_INTERVAL
156
+
157
+ @mutex.synchronize do
158
+ if @tun && !@tun.closed? && @tun_info[ :tund_addr ]
159
+ need_trigger = false
160
+
161
+ if @tun_info[ :src_exts ].any?
162
+ now = Time.new
163
+
164
+ @tun_info[ :src_exts ].each do | src_id, src_ext |
165
+ if src_ext[ :dst_port ] && ( now - src_ext[ :last_continue_at ] < SEND_STATUS_UNTIL )
166
+ data = [ 0, SOURCE_STATUS, src_id, src_ext[ :relay_pack_id ], src_ext[ :continue_dst_pack_id ] ].pack( 'Q>CQ>Q>Q>' )
167
+ add_tun_ctlmsg( data )
168
+ need_trigger = true
169
+ end
170
+ end
171
+ end
172
+
173
+ if @tun_info[ :paused ] && ( @tun_info[ :src_exts ].map{ | _, src_ext | src_ext[ :wmems ].size }.sum < RESUME_BELOW )
174
+ puts "p#{ Process.pid } #{ Time.new } resume tun"
175
+ @tun_info[ :paused ] = false
176
+ add_write( @tun )
177
+ need_trigger = true
178
+ end
179
+
180
+ if need_trigger
181
+ next_tick
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ ##
190
+ # loop send a new source
191
+ #
192
+ def loop_send_a_new_source( src_ext, data )
193
+ Thread.new do
194
+ EXPIRE_NEW.times do
195
+ if src_ext[ :src ].closed? || src_ext[ :dst_port ]
196
+ # puts "debug1 break loop send a new source #{ src_ext[ :dst_port ] }"
197
+ break
198
+ end
199
+
200
+ @mutex.synchronize do
201
+ # puts "debug1 send a new source #{ data.inspect }"
202
+ add_tun_ctlmsg( data )
203
+ next_tick
204
+ end
205
+
206
+ sleep 1
207
+ end
208
+ end
209
+ end
210
+
211
+ ##
212
+ # resolve domain
213
+ #
214
+ def resolve_domain( src, domain )
215
+ if @remotes.any? { | remote | ( domain.size >= remote.size ) && ( domain[ ( remote.size * -1 )..-1 ] == remote ) }
216
+ # puts "debug1 #{ domain } hit remotes"
217
+ new_a_src_ext( src )
218
+ return
219
+ end
220
+
221
+ resolv_cache = @resolv_caches[ domain ]
222
+
223
+ if resolv_cache
224
+ ip_info, created_at = resolv_cache
225
+
226
+ if Time.new - created_at < RESOLV_CACHE_EXPIRE
227
+ # puts "debug1 #{ domain } hit resolv cache #{ ip_info.inspect }"
228
+ deal_with_destination_ip( src, ip_info )
229
+ return
230
+ end
231
+
232
+ # puts "debug1 expire #{ domain } resolv cache"
233
+ @resolv_caches.delete( domain )
234
+ end
235
+
236
+ src_info = @src_infos[ src ]
237
+ src_info[ :proxy_type ] = :checking
238
+
239
+ Thread.new do
240
+ begin
241
+ ip_info = Addrinfo.ip( domain )
242
+ rescue Exception => e
243
+ puts "p#{ Process.pid } #{ Time.new } resolv #{ domain } #{ e.class }"
244
+ end
245
+
246
+ @mutex.synchronize do
247
+ if ip_info
248
+ @resolv_caches[ domain ] = [ ip_info, Time.new ]
249
+
250
+ unless src.closed?
251
+ puts "p#{ Process.pid } #{ Time.new } resolved #{ domain } #{ ip_info.ip_address }"
252
+ deal_with_destination_ip( src, ip_info )
253
+ end
254
+ else
255
+ set_is_closing( src )
256
+ end
257
+
258
+ next_tick
259
+ end
260
+ end
261
+ end
262
+
263
+ ##
264
+ # deal with destination ip
265
+ #
266
+ def deal_with_destination_ip( src, ip_info )
267
+ if @directs.any? { | direct | direct.include?( ip_info.ip_address ) }
268
+ # ip命中直连列表,直连
269
+ # puts "debug1 #{ ip_info.inspect } hit directs"
270
+ new_a_dst( src, ip_info )
271
+ else
272
+ # 走远端
273
+ new_a_src_ext( src )
274
+ end
275
+ end
276
+
277
+ ##
278
+ # new a proxy
279
+ #
280
+ def new_a_proxy( proxy_port )
281
+ proxy = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
282
+ proxy.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
283
+
284
+ if RUBY_PLATFORM.include?( 'linux' )
285
+ proxy.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1 )
286
+ proxy.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
287
+ end
288
+
289
+ proxy.bind( Socket.sockaddr_in( proxy_port, '0.0.0.0' ) )
290
+ proxy.listen( 511 )
291
+ puts "p#{ Process.pid } #{ Time.new } proxy listen on #{ proxy_port }"
292
+ add_read( proxy, :proxy )
293
+ @proxy_local_address = proxy.local_address
294
+ end
295
+
296
+ ##
297
+ # new a tun
298
+ #
299
+ def new_a_tun
300
+ tun = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
301
+ tun.bind( Socket.sockaddr_in( 0, '0.0.0.0' ) )
302
+ port = tun.local_address.ip_port
303
+ tun_info = {
304
+ port: port, # 端口
305
+ ctlmsg_rbuffs: [], # 还没配上tund,暂存的ctlmsg
306
+ ctlmsgs: [], # [ to_addr, data ]
307
+ wbuffs: [], # 写前缓存 [ src_id, pack_id, data ]
308
+ caches: [], # 块读出缓存 [ src_id, pack_id, data ]
309
+ chunks: [], # 块队列 filename
310
+ spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
311
+ tund_addr: nil, # tund地址
312
+ src_exts: {}, # src额外信息 src_id => {}
313
+ src_ids: {}, # dst_port => src_id
314
+ paused: false, # 是否暂停写
315
+ resendings: [], # 重传队列 [ src_id, pack_id ]
316
+ created_at: Time.new, # 创建时间
317
+ last_recv_at: nil, # 上一次收到流量的时间,过期关闭
318
+ is_closing: false # 是否准备关闭
319
+ }
320
+
321
+ @tun = tun
322
+ @tun_info = tun_info
323
+
324
+ add_read( tun, :tun )
325
+ data = @custom.hello
326
+ puts "p#{ Process.pid } #{ Time.new } hello i'm tun"
327
+ # puts "debug1 #{ data.inspect }"
328
+ add_tun_ctlmsg( data, @proxyd_addr )
329
+ end
330
+
331
+ ##
332
+ # new a dst
333
+ #
334
+ def new_a_dst( src, ip_info )
335
+ src_info = @src_infos[ src ]
336
+ domain = src_info[ :destination_domain ]
337
+ destination_addr = Socket.sockaddr_in( src_info[ :destination_port ], ip_info.ip_address )
338
+ dst = Socket.new( ip_info.ipv4? ? Socket::AF_INET : Socket::AF_INET6, Socket::SOCK_STREAM, 0 )
339
+
340
+ if RUBY_PLATFORM.include?( 'linux' )
341
+ dst.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
342
+ end
343
+
344
+ begin
345
+ dst.connect_nonblock( destination_addr )
346
+ rescue IO::WaitWritable
347
+ # connect nonblock 必抛 wait writable,这里仅仅接一下,逻辑写外面,整齐
348
+ rescue Exception => e
349
+ puts "p#{ Process.pid } #{ Time.new } connect destination #{ e.class }, close src"
350
+ set_is_closing( src )
351
+ return
352
+ end
353
+
354
+ # puts "debug1 a new dst #{ dst.local_address.inspect }"
355
+ local_port = dst.local_address.ip_port
356
+ @dst_infos[ dst ] = {
357
+ local_port: local_port, # 本地端口
358
+ src: src, # 对应src
359
+ domain: domain, # 域名
360
+ wbuff: '', # 写前
361
+ cache: '', # 块读出缓存
362
+ chunks: [], # 块队列,写前达到块大小时结一个块 filename
363
+ spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
364
+ last_continue_at: Time.new, # 上一次发生流量的时间
365
+ is_closing: false # 是否准备关闭
366
+ }
367
+
368
+ add_read( dst, :dst )
369
+ src_info[ :proxy_type ] = :direct
370
+ src_info[ :dst ] = dst
371
+
372
+ if src_info[ :proxy_proto ] == :http
373
+ if src_info[ :is_connect ]
374
+ # puts "debug1 add src wbuff http ok"
375
+ add_src_wbuff( src, HTTP_OK )
376
+ else
377
+ # puts "debug1 add src rbuffs to dst wbuff"
378
+
379
+ src_info[ :rbuffs ].each do | _, data |
380
+ add_dst_wbuff( dst, data )
381
+ end
382
+ end
383
+ elsif src_info[ :proxy_proto ] == :socks5
384
+ add_src_wbuff_socks5_conn_reply( src )
385
+ end
386
+ end
387
+
388
+ ##
389
+ # new a src ext
390
+ #
391
+ def new_a_src_ext( src )
392
+ if @tun.nil? || @tun.closed?
393
+ new_a_tun
394
+ end
395
+
396
+ src_info = @src_infos[ src ]
397
+ src_id = src_info[ :id ]
398
+ destination_port = src_info[ :destination_port ]
399
+ destination_domain = src_info[ :destination_domain ]
400
+
401
+ src_ext = {
402
+ src: src, # src
403
+ dst_port: nil, # 远端dst端口
404
+ destination_domain: destination_domain, # 目的地域名
405
+ wmems: {}, # 写后 pack_id => data
406
+ send_ats: {}, # 上一次发出时间 pack_id => send_at
407
+ relay_pack_id: 0, # 转发到几
408
+ continue_dst_pack_id: 0, # 收到几
409
+ pieces: {}, # 跳号包 dst_pack_id => data
410
+ is_dst_closed: false, # dst是否已关闭
411
+ biggest_dst_pack_id: 0, # dst最大包号码
412
+ completed_pack_id: 0, # 完成到几(对面收到几)
413
+ last_continue_at: Time.new # 上一次发生流量的时间
414
+ }
415
+
416
+ @tun_info[ :src_exts ][ src_id ] = src_ext
417
+ src_info[ :proxy_type ] = :tunnel
418
+
419
+ destination_domain_port = [ destination_domain, destination_port ].join( ':' )
420
+ data = [ [ 0, A_NEW_SOURCE, src_id ].pack( 'Q>CQ>' ), @custom.encode( destination_domain_port ) ].join
421
+ loop_send_a_new_source( src_ext, data )
422
+ end
423
+
424
+ ##
425
+ # add src wbuff socks5 conn reply
426
+ #
427
+ def add_src_wbuff_socks5_conn_reply( src )
428
+ # +----+-----+-------+------+----------+----------+
429
+ # |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
430
+ # +----+-----+-------+------+----------+----------+
431
+ # | 1 | 1 | X'00' | 1 | Variable | 2 |
432
+ # +----+-----+-------+------+----------+----------+
433
+ proxy_ip, proxy_port = @proxy_local_address.ip_unpack
434
+ data = [ [ 5, 0, 0, 1 ].pack( 'C4' ), IPAddr.new( proxy_ip ).hton, [ proxy_port ].pack( 'n' ) ].join
435
+ # puts "debug1 add src wbuff socks5 conn reply #{ data.inspect }"
436
+ add_src_wbuff( src, data )
437
+ end
438
+
439
+ ##
440
+ # sub http request
441
+ #
442
+ def sub_http_request( data )
443
+ lines = data.split( "\r\n" )
444
+
445
+ if lines.empty?
446
+ return [ data, nil ]
447
+ end
448
+
449
+ method, url, proto = lines.first.split( ' ' )
450
+
451
+ if proto && url && proto[ 0, 4 ] == 'HTTP' && url[ 0, 7 ] == 'http://'
452
+ domain_and_port = url.split( '/' )[ 2 ]
453
+ data = data.sub( "http://#{ domain_and_port }", '' )
454
+ # puts "debug1 subed #{ data.inspect } #{ domain_and_port }"
455
+ end
456
+
457
+ [ data, domain_and_port ]
458
+ end
459
+
460
+ ##
461
+ # add tun ctlmsg
462
+ #
463
+ def add_tun_ctlmsg( data, to_addr = nil )
464
+ unless to_addr
465
+ to_addr = @tun_info[ :tund_addr ]
466
+ end
467
+
468
+ if to_addr
469
+ @tun_info[ :ctlmsgs ] << [ to_addr, data ]
470
+ add_write( @tun )
471
+ else
472
+ @tun_info[ :ctlmsg_rbuffs ] << data
473
+ end
474
+ end
475
+
476
+ ##
477
+ # add tun wbuff
478
+ #
479
+ def add_tun_wbuff( src_id, pack_id, data )
480
+ @tun_info[ :wbuffs ] << [ src_id, pack_id, data ]
481
+
482
+ if @tun_info[ :wbuffs ].size >= WBUFFS_LIMIT
483
+ spring = @tun_info[ :chunks ].size > 0 ? ( @tun_info[ :spring ] + 1 ) : 0
484
+ filename = "#{ Process.pid }-#{ @tun_info[ :port ] }.#{ spring }"
485
+ chunk_path = File.join( @tun_chunk_dir, filename )
486
+ datas = @tun_info[ :wbuffs ].map{ | _src_id, _pack_id, _data | [ [ _src_id, _pack_id, _data.bytesize ].pack( 'Q>Q>n' ), _data ].join }
487
+
488
+ begin
489
+ IO.binwrite( chunk_path, datas.join )
490
+ rescue Errno::ENOSPC => e
491
+ puts "p#{ Process.pid } #{ Time.new } #{ e.class }, close tun"
492
+ set_is_closing( @tun )
493
+ return
494
+ end
495
+
496
+ @tun_info[ :chunks ] << filename
497
+ @tun_info[ :spring ] = spring
498
+ @tun_info[ :wbuffs ].clear
499
+ end
500
+
501
+ add_write( @tun )
502
+ end
503
+
504
+ ##
505
+ # add src wbuff
506
+ #
507
+ def add_src_wbuff( src, data )
508
+ src_info = @src_infos[ src ]
509
+ src_info[ :wbuff ] << data
510
+
511
+ if src_info[ :wbuff ].bytesize >= CHUNK_SIZE
512
+ spring = src_info[ :chunks ].size > 0 ? ( src_info[ :spring ] + 1 ) : 0
513
+ filename = "#{ Process.pid }-#{ src_info[ :id ] }.#{ spring }"
514
+ chunk_path = File.join( @src_chunk_dir, filename )
515
+
516
+ begin
517
+ IO.binwrite( chunk_path, src_info[ :wbuff ] )
518
+ rescue Errno::ENOSPC => e
519
+ puts "p#{ Process.pid } #{ Time.new } #{ e.class }, close src"
520
+ set_is_closing( src )
521
+ return
522
+ end
523
+
524
+ src_info[ :chunks ] << filename
525
+ src_info[ :spring ] = spring
526
+ src_info[ :wbuff ].clear
527
+ end
528
+
529
+ add_write( src )
530
+ end
531
+
532
+ ##
533
+ # add dst wbuff
534
+ #
535
+ def add_dst_wbuff( dst, data )
536
+ dst_info = @dst_infos[ dst ]
537
+ dst_info[ :wbuff ] << data
538
+
539
+ if dst_info[ :wbuff ].bytesize >= CHUNK_SIZE
540
+ spring = dst_info[ :chunks ].size > 0 ? ( dst_info[ :spring ] + 1 ) : 0
541
+ filename = "#{ Process.pid }-#{ dst_info[ :local_port ] }.#{ spring }"
542
+ chunk_path = File.join( @dst_chunk_dir, filename )
543
+
544
+ begin
545
+ IO.binwrite( chunk_path, dst_info[ :wbuff ] )
546
+ rescue Errno::ENOSPC => e
547
+ puts "p#{ Process.pid } #{ Time.new } #{ e.class }, close dst"
548
+ set_is_closing( dst )
549
+ return
550
+ end
551
+
552
+ dst_info[ :chunks ] << filename
553
+ dst_info[ :spring ] = spring
554
+ dst_info[ :wbuff ].clear
555
+ end
556
+
557
+ add_write( dst )
558
+ end
559
+
560
+ ##
561
+ # add read
562
+ #
563
+ def add_read( sock, role )
564
+ unless @reads.include?( sock )
565
+ @reads << sock
566
+ end
567
+
568
+ @roles[ sock ] = role
569
+ end
570
+
571
+ ##
572
+ # add write
573
+ #
574
+ def add_write( sock )
575
+ if sock && !sock.closed? && !@writes.include?( sock )
576
+ @writes << sock
577
+ end
578
+ end
579
+
580
+ ##
581
+ # set is closing
582
+ #
583
+ def set_is_closing( sock )
584
+ if sock && !sock.closed?
585
+ role = @roles[ sock ]
586
+ # puts "debug1 set #{ role.to_s } is closing"
587
+
588
+ case role
589
+ when :src
590
+ src_info = @src_infos[ sock ]
591
+ src_info[ :is_closing ] = true
592
+ when :dst
593
+ dst_info = @dst_infos[ sock ]
594
+ dst_info[ :is_closing ] = true
595
+ when :tun
596
+ @tun_info[ :is_closing ] = true
597
+ end
598
+
599
+ @reads.delete( sock )
600
+ add_write( sock )
601
+ end
602
+ end
603
+
604
+ ##
605
+ # send data
606
+ #
607
+ def send_data( tun, data, to_addr )
608
+ begin
609
+ tun.sendmsg( data, 0, to_addr )
610
+ rescue IO::WaitWritable, Errno::EINTR
611
+ return false
612
+ rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ENETDOWN => e
613
+ puts "#{ Time.new } #{ e.class }, close tun"
614
+ close_tun( tun )
615
+ return false
616
+ end
617
+
618
+ true
619
+ end
620
+
621
+ ##
622
+ # close src
623
+ #
624
+ def close_src( src )
625
+ # puts "debug1 close src"
626
+ close_sock( src )
627
+ src_info = @src_infos.delete( src )
628
+
629
+ src_info[ :chunks ].each do | filename |
630
+ begin
631
+ File.delete( File.join( @src_chunk_dir, filename ) )
632
+ rescue Errno::ENOENT
633
+ end
634
+ end
635
+
636
+ src_id = src_info[ :id ]
637
+
638
+ if src_info[ :proxy_type ] == :tunnel
639
+ return if @tun.closed?
640
+
641
+ src_ext = @tun_info[ :src_exts ][ src_id ]
642
+ return if src_ext.nil? || src_ext[ :dst_port ].nil?
643
+
644
+ if src_ext[ :is_dst_closed ]
645
+ # puts "debug1 2-3. after close src -> dst closed ? yes -> del src ext -> send fin2"
646
+ del_src_ext( src_id )
647
+ data = [ 0, FIN2, src_id ].pack( 'Q>CQ>' )
648
+ add_tun_ctlmsg( data )
649
+ else
650
+ # puts "debug1 1-1. after close src -> dst closed ? no -> send fin1"
651
+ data = [ 0, FIN1, src_id, src_info[ :biggest_pack_id ], src_ext[ :continue_dst_pack_id ] ].pack( 'Q>CQ>Q>Q>' )
652
+ add_tun_ctlmsg( data )
653
+ end
654
+ elsif src_info[ :proxy_type ] == :direct
655
+ set_is_closing( src_info[ :dst ] )
656
+ end
657
+ end
658
+
659
+ ##
660
+ # close dst
661
+ #
662
+ def close_dst( dst )
663
+ # puts "debug1 close dst"
664
+ close_sock( dst )
665
+ dst_info = @dst_infos.delete( dst )
666
+
667
+ dst_info[ :chunks ].each do | filename |
668
+ begin
669
+ File.delete( File.join( @dst_chunk_dir, filename ) )
670
+ rescue Errno::ENOENT
671
+ end
672
+ end
673
+
674
+ set_is_closing( dst_info[ :src ] )
675
+ end
676
+
677
+ ##
678
+ # close tun
679
+ #
680
+ def close_tun( tun )
681
+ # puts "debug1 close tun"
682
+ close_sock( tun )
683
+
684
+ @tun_info[ :chunks ].each do | filename |
685
+ begin
686
+ File.delete( File.join( @tun_chunk_dir, filename ) )
687
+ rescue Errno::ENOENT
688
+ end
689
+ end
690
+
691
+ @tun_info[ :src_exts ].each{ | _, src_ext | set_is_closing( src_ext[ :src ] ) }
692
+ end
693
+
694
+ ##
695
+ # close sock
696
+ #
697
+ def close_sock( sock )
698
+ sock.close
699
+ @reads.delete( sock )
700
+ @writes.delete( sock )
701
+ @roles.delete( sock )
702
+ end
703
+
704
+ ##
705
+ # del src ext
706
+ #
707
+ def del_src_ext( src_id )
708
+ src_ext = @tun_info[ :src_exts ].delete( src_id )
709
+
710
+ if src_ext
711
+ @tun_info[ :src_ids ].delete( src_ext[ :dst_port ] )
712
+ end
713
+ end
714
+
715
+ ##
716
+ # release wmems
717
+ #
718
+ def release_wmems( src_ext, completed_pack_id )
719
+ if completed_pack_id > src_ext[ :completed_pack_id ]
720
+ # puts "debug2 update completed pack #{ completed_pack_id }"
721
+ pack_ids = src_ext[ :wmems ].keys.select { | pack_id | pack_id <= completed_pack_id }
722
+
723
+ pack_ids.each do | pack_id |
724
+ src_ext[ :wmems ].delete( pack_id )
725
+ src_ext[ :send_ats ].delete( pack_id )
726
+ end
727
+
728
+ src_ext[ :completed_pack_id ] = completed_pack_id
729
+ end
730
+ end
731
+
732
+ ##
733
+ # next tick
734
+ #
735
+ def next_tick
736
+ @dotw.write( '.' )
737
+ end
738
+
739
+ ##
740
+ # write src
741
+ #
742
+ def write_src( src )
743
+ src_info = @src_infos[ src ]
744
+ data = src_info[ :cache ]
745
+ from = :cache
746
+
747
+ if data.empty?
748
+ if src_info[ :chunks ].any?
749
+ path = File.join( @src_chunk_dir, src_info[ :chunks ].shift )
750
+
751
+ begin
752
+ src_info[ :cache ] = data = IO.binread( path )
753
+ File.delete( path )
754
+ rescue Errno::ENOENT => e
755
+ puts "p#{ Process.pid } #{ Time.new } read #{ path } #{ e.class }"
756
+ close_src( src )
757
+ return
758
+ end
759
+ else
760
+ data = src_info[ :wbuff ]
761
+ from = :wbuff
762
+ end
763
+ end
764
+
765
+ if data.empty?
766
+ if src_info[ :is_closing ]
767
+ close_src( src )
768
+ else
769
+ @writes.delete( src )
770
+ end
771
+
772
+ return
773
+ end
774
+
775
+ begin
776
+ written = src.write_nonblock( data )
777
+ rescue IO::WaitWritable, Errno::EINTR
778
+ return
779
+ rescue Exception => e
780
+ close_src( src )
781
+ return
782
+ end
783
+
784
+ # puts "debug2 write src #{ written }"
785
+ data = data[ written..-1 ]
786
+ src_info[ from ] = data
787
+ src_info[ :last_continue_at ] = Time.new
788
+ end
789
+
790
+ ##
791
+ # write dst
792
+ #
793
+ def write_dst( dst )
794
+ dst_info = @dst_infos[ dst ]
795
+ data = dst_info[ :cache ]
796
+ from = :cache
797
+
798
+ if data.empty?
799
+ if dst_info[ :chunks ].any?
800
+ path = File.join( @dst_chunk_dir, dst_info[ :chunks ].shift )
801
+
802
+ begin
803
+ dst_info[ :cache ] = data = IO.binread( path )
804
+ File.delete( path )
805
+ rescue Errno::ENOENT => e
806
+ puts "p#{ Process.pid } #{ Time.new } read #{ path } #{ e.class }"
807
+ close_dst( dst )
808
+ return
809
+ end
810
+ else
811
+ data = dst_info[ :wbuff ]
812
+ from = :wbuff
813
+ end
814
+ end
815
+
816
+ if data.empty?
817
+ if dst_info[ :is_closing ]
818
+ close_dst( dst )
819
+ else
820
+ @writes.delete( dst )
821
+ end
822
+
823
+ return
824
+ end
825
+
826
+ begin
827
+ written = dst.write_nonblock( data )
828
+ rescue IO::WaitWritable, Errno::EINTR
829
+ return
830
+ rescue Exception => e
831
+ # puts "debug1 write dst #{ e.class }"
832
+ close_dst( dst )
833
+ return
834
+ end
835
+
836
+ # puts "debug2 write dst #{ written }"
837
+ data = data[ written..-1 ]
838
+ dst_info[ from ] = data
839
+ dst_info[ :last_continue_at ] = Time.new
840
+ end
841
+
842
+ ##
843
+ # write tun
844
+ #
845
+ def write_tun( tun )
846
+ if @tun_info[ :is_closing ]
847
+ close_tun( tun )
848
+ return
849
+ end
850
+
851
+ now = Time.new
852
+
853
+ # 传ctlmsg
854
+ while @tun_info[ :ctlmsgs ].any?
855
+ to_addr, data = @tun_info[ :ctlmsgs ].first
856
+
857
+ unless send_data( tun, data, to_addr )
858
+ return
859
+ end
860
+
861
+ @tun_info[ :ctlmsgs ].shift
862
+ end
863
+
864
+ # 重传
865
+ while @tun_info[ :resendings ].any?
866
+ src_id, pack_id = @tun_info[ :resendings ].first
867
+ src_ext = @tun_info[ :src_exts ][ src_id ]
868
+
869
+ if src_ext
870
+ data = src_ext[ :wmems ][ pack_id ]
871
+
872
+ if data
873
+ unless send_data( tun, data, @tun_info[ :tund_addr ] )
874
+ return
875
+ end
876
+ end
877
+ end
878
+
879
+ @tun_info[ :resendings ].shift
880
+ return
881
+ end
882
+
883
+ # 若写后达到上限,暂停取写前
884
+ if @tun_info[ :src_exts ].map{ | _, src_ext | src_ext[ :wmems ].size }.sum >= WMEMS_LIMIT
885
+ unless @tun_info[ :paused ]
886
+ puts "p#{ Process.pid } #{ Time.new } pause tun"
887
+ @tun_info[ :paused ] = true
888
+ end
889
+
890
+ @writes.delete( tun )
891
+ return
892
+ end
893
+
894
+ # 取写前
895
+ if @tun_info[ :caches ].any?
896
+ src_id, pack_id, data = @tun_info[ :caches ].first
897
+ from = :caches
898
+ elsif @tun_info[ :chunks ].any?
899
+ path = File.join( @tun_chunk_dir, @tun_info[ :chunks ].shift )
900
+
901
+ begin
902
+ data = IO.binread( path )
903
+ File.delete( path )
904
+ rescue Errno::ENOENT => e
905
+ puts "p#{ Process.pid } #{ Time.new } read #{ path } #{ e.class }"
906
+ close_tun( tun )
907
+ return
908
+ end
909
+
910
+ caches = []
911
+
912
+ until data.empty?
913
+ _src_id, _pack_id, pack_size = data[ 0, 18 ].unpack( 'Q>Q>n' )
914
+ caches << [ _src_id, _pack_id, data[ 18, pack_size ] ]
915
+ data = data[ ( 18 + pack_size )..-1 ]
916
+ end
917
+
918
+ @tun_info[ :caches ] = caches
919
+ src_id, pack_id, data = caches.first
920
+ from = :caches
921
+ elsif @tun_info[ :wbuffs ].any?
922
+ src_id, pack_id, data = @tun_info[ :wbuffs ].first
923
+ from = :wbuffs
924
+ else
925
+ @writes.delete( tun )
926
+ return
927
+ end
928
+
929
+ src_ext = @tun_info[ :src_exts ][ src_id ]
930
+
931
+ if src_ext
932
+ if pack_id <= CONFUSE_UNTIL
933
+ data = @custom.encode( data )
934
+ # puts "debug1 encoded pack #{ pack_id }"
935
+ end
936
+
937
+ data = [ [ pack_id, src_id ].pack( 'Q>Q>' ), data ].join
938
+
939
+ unless send_data( tun, data, @tun_info[ :tund_addr ] )
940
+ return
941
+ end
942
+
943
+ # puts "debug2 written pack #{ pack_id }"
944
+ src_ext[ :relay_pack_id ] = pack_id
945
+ src_ext[ :wmems ][ pack_id ] = data
946
+ src_ext[ :send_ats ][ pack_id ] = now
947
+ src_ext[ :last_continue_at ] = now
948
+ end
949
+
950
+ @tun_info[ from ].shift
951
+ end
952
+
953
+ ##
954
+ # read dotr
955
+ #
956
+ def read_dotr( dotr )
957
+ dotr.read( 1 )
958
+ end
959
+
960
+ ##
961
+ # read proxy
962
+ #
963
+ def read_proxy( proxy )
964
+ begin
965
+ src, addrinfo = proxy.accept_nonblock
966
+ rescue IO::WaitReadable, Errno::EINTR
967
+ return
968
+ end
969
+
970
+ id = rand( ( 2 ** 64 ) - 1 ) + 1
971
+ # puts "debug1 accept a src #{ addrinfo.inspect } #{ id }"
972
+
973
+ @src_infos[ src ] = {
974
+ id: id, # id
975
+ proxy_proto: :uncheck, # :uncheck / :http / :socks5
976
+ proxy_type: :uncheck, # :uncheck / :checking / :direct / :tunnel / :negotiation
977
+ dst: nil, # :direct的场合,对应的dst
978
+ destination_domain: nil, # 目的地域名
979
+ destination_port: nil, # 目的地端口
980
+ biggest_pack_id: 0, # 最大包号码
981
+ is_connect: true, # 代理协议是http的场合,是否是CONNECT
982
+ rbuffs: [], # 非CONNECT,dst或者远端dst未准备好,暂存流量 [ pack_id, data ]
983
+ wbuff: '', # 写前
984
+ cache: '', # 块读出缓存
985
+ chunks: [], # 块队列,写前达到块大小时结一个块 filename
986
+ spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
987
+ last_continue_at: Time.new, # 上一次发生流量的时间
988
+ is_closing: false # 是否准备关闭
989
+ }
990
+
991
+ add_read( src, :src )
992
+ end
993
+
994
+ ##
995
+ # read src
996
+ #
997
+ def read_src( src )
998
+ begin
999
+ data = src.read_nonblock( PACK_SIZE )
1000
+ rescue IO::WaitReadable, Errno::EINTR
1001
+ return
1002
+ rescue Exception => e
1003
+ # puts "debug1 read src #{ e.class }"
1004
+ set_is_closing( src )
1005
+ return
1006
+ end
1007
+
1008
+ # puts "debug2 read src #{ data.inspect }"
1009
+ src_info = @src_infos[ src ]
1010
+ src_info[ :last_continue_at ] = Time.new
1011
+ proxy_type = src_info[ :proxy_type ]
1012
+
1013
+ case proxy_type
1014
+ when :uncheck
1015
+ if data[ 0, 7 ] == 'CONNECT'
1016
+ # puts "debug1 CONNECT"
1017
+ domain_and_port = data.split( "\r\n" )[ 0 ].split( ' ' )[ 1 ]
1018
+
1019
+ unless domain_and_port
1020
+ puts "p#{ Process.pid } #{ Time.new } CONNECT miss domain"
1021
+ set_is_closing( src )
1022
+ return
1023
+ end
1024
+ elsif data[ 0 ].unpack( 'C' ).first == 5
1025
+ # puts "debug1 socks5 #{ data.inspect }"
1026
+
1027
+ # https://tools.ietf.org/html/rfc1928
1028
+ #
1029
+ # +----+----------+----------+
1030
+ # |VER | NMETHODS | METHODS |
1031
+ # +----+----------+----------+
1032
+ # | 1 | 1 | 1 to 255 |
1033
+ # +----+----------+----------+
1034
+ nmethods = data[ 1 ].unpack( 'C' ).first
1035
+ methods = data[ 2, nmethods ].unpack( 'C*' )
1036
+
1037
+ unless methods.include?( 0 )
1038
+ puts "p#{ Process.pid } #{ Time.new } miss method 00"
1039
+ set_is_closing( src )
1040
+ return
1041
+ end
1042
+
1043
+ # +----+--------+
1044
+ # |VER | METHOD |
1045
+ # +----+--------+
1046
+ # | 1 | 1 |
1047
+ # +----+--------+
1048
+ data2 = [ 5, 0 ].pack( 'CC' )
1049
+ add_src_wbuff( src, data2 )
1050
+
1051
+ src_info[ :proxy_proto ] = :socks5
1052
+ src_info[ :proxy_type ] = :negotiation
1053
+
1054
+ return
1055
+ else
1056
+ # puts "debug1 not CONNECT #{ data.inspect }"
1057
+ host_line = data.split( "\r\n" ).find { | _line | _line[ 0, 6 ] == 'Host: ' }
1058
+
1059
+ unless host_line
1060
+ # puts "debug1 not found host line"
1061
+ set_is_closing( src )
1062
+ return
1063
+ end
1064
+
1065
+ data, domain_and_port = sub_http_request( data )
1066
+
1067
+ unless domain_and_port
1068
+ # puts "debug1 not HTTP"
1069
+ domain_and_port = host_line.split( ' ' )[ 1 ]
1070
+
1071
+ unless domain_and_port
1072
+ puts "p#{ Process.pid } #{ Time.new } Host line miss domain"
1073
+ set_is_closing( src )
1074
+ return
1075
+ end
1076
+ end
1077
+
1078
+ src_info[ :is_connect ] = false
1079
+ pack_id = src_info[ :biggest_pack_id ] + 1
1080
+ src_info[ :biggest_pack_id ] = pack_id
1081
+ src_info[ :rbuffs ] << [ pack_id, data ]
1082
+ end
1083
+
1084
+ domain, port = domain_and_port.split( ':' )
1085
+ port = port ? port.to_i : 80
1086
+
1087
+ src_info[ :proxy_proto ] = :http
1088
+ src_info[ :destination_domain ] = domain
1089
+ src_info[ :destination_port ] = port
1090
+
1091
+ resolve_domain( src, domain )
1092
+ when :checking
1093
+ # puts "debug1 add src rbuff while checking #{ data.inspect }"
1094
+ pack_id = src_info[ :biggest_pack_id ] + 1
1095
+ src_info[ :biggest_pack_id ] = pack_id
1096
+ src_info[ :rbuffs ] << [ pack_id, data ]
1097
+ when :negotiation
1098
+ # +----+-----+-------+------+----------+----------+
1099
+ # |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
1100
+ # +----+-----+-------+------+----------+----------+
1101
+ # | 1 | 1 | X'00' | 1 | Variable | 2 |
1102
+ # +----+-----+-------+------+----------+----------+
1103
+ # puts "debug1 negotiation #{ data.inspect }"
1104
+ ver, cmd, rsv, atyp = data[ 0, 4 ].unpack( 'C4' )
1105
+
1106
+ if cmd == 1
1107
+ # puts "debug1 socks5 CONNECT"
1108
+
1109
+ if atyp == 1
1110
+ destination_host, destination_port = data[ 4, 6 ].unpack( 'Nn' )
1111
+ destination_addr = Socket.sockaddr_in( destination_port, destination_host )
1112
+ destination_addrinfo = Addrinfo.new( destination_addr )
1113
+ destination_ip = destination_addrinfo.ip_address
1114
+ src_info[ :destination_domain ] = destination_ip
1115
+ src_info[ :destination_port ] = destination_port
1116
+ # puts "debug1 IP V4 address #{ destination_addrinfo.inspect }"
1117
+ deal_with_destination_ip( src, destination_addrinfo )
1118
+ elsif atyp == 3
1119
+ domain_len = data[ 4 ].unpack( 'C' ).first
1120
+
1121
+ if ( domain_len + 7 ) == data.bytesize
1122
+ domain = data[ 5, domain_len ]
1123
+ port = data[ ( 5 + domain_len ), 2 ].unpack( 'n' ).first
1124
+ src_info[ :destination_domain ] = domain
1125
+ src_info[ :destination_port ] = port
1126
+ # puts "debug1 DOMAINNAME #{ domain } #{ port }"
1127
+ resolve_domain( src, domain )
1128
+ end
1129
+ end
1130
+ else
1131
+ puts "p#{ Process.pid } #{ Time.new } socks5 cmd #{ cmd } not implement"
1132
+ end
1133
+ when :tunnel
1134
+ src_id = src_info[ :id ]
1135
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1136
+
1137
+ unless src_ext
1138
+ # puts "debug1 not found src ext"
1139
+ set_is_closing( src )
1140
+ return
1141
+ end
1142
+
1143
+ pack_id = src_info[ :biggest_pack_id ] + 1
1144
+ src_info[ :biggest_pack_id ] = pack_id
1145
+
1146
+ if src_ext[ :dst_port ]
1147
+ if @tun.closed?
1148
+ # puts "debug1 tun closed, close src"
1149
+ set_is_closing( src )
1150
+ return
1151
+ end
1152
+
1153
+ unless src_info[ :is_connect ]
1154
+ data, _ = sub_http_request( data )
1155
+ end
1156
+
1157
+ add_tun_wbuff( src_id, pack_id, data )
1158
+ else
1159
+ # puts "debug1 remote dst not ready, save data to src rbuff"
1160
+ src_info[ :rbuffs ] << [ pack_id, data ]
1161
+ end
1162
+ when :direct
1163
+ dst = src_info[ :dst ]
1164
+
1165
+ if dst
1166
+ if dst.closed?
1167
+ # puts "debug1 dst closed, close src"
1168
+ set_is_closing( src )
1169
+ return
1170
+ end
1171
+
1172
+ unless src_info[ :is_connect ]
1173
+ data, _ = sub_http_request( data )
1174
+ end
1175
+
1176
+ add_dst_wbuff( dst, data )
1177
+ else
1178
+ # puts "debug1 dst not ready, save data to src rbuff"
1179
+ src_info[ :rbuffs ] << [ nil, data ]
1180
+ end
1181
+ end
1182
+ end
1183
+
1184
+ ##
1185
+ # read dst
1186
+ #
1187
+ def read_dst( dst )
1188
+ begin
1189
+ data = dst.read_nonblock( PACK_SIZE )
1190
+ rescue IO::WaitReadable, Errno::EINTR
1191
+ return
1192
+ rescue Exception => e
1193
+ # puts "debug1 read dst #{ e.class }"
1194
+ set_is_closing( dst )
1195
+ return
1196
+ end
1197
+
1198
+ # puts "debug2 read dst #{ data.inspect }"
1199
+ dst_info = @dst_infos[ dst ]
1200
+ dst_info[ :last_continue_at ] = Time.new
1201
+ src = dst_info[ :src ]
1202
+
1203
+ if src.closed?
1204
+ puts "p#{ Process.pid } #{ Time.new } src closed, close dst"
1205
+ set_is_closing( dst )
1206
+ return
1207
+ end
1208
+
1209
+ add_src_wbuff( src, data )
1210
+ end
1211
+
1212
+ ##
1213
+ # read tun
1214
+ #
1215
+ def read_tun( tun )
1216
+ data, addrinfo, rflags, *controls = tun.recvmsg
1217
+ from_addr = addrinfo.to_sockaddr
1218
+ now = Time.new
1219
+ @tun_info[ :last_recv_at ] = now
1220
+ pack_id = data[ 0, 8 ].unpack( 'Q>' ).first
1221
+
1222
+ if pack_id == 0
1223
+ ctl_num = data[ 8 ].unpack( 'C' ).first
1224
+
1225
+ case ctl_num
1226
+ when TUND_PORT
1227
+ return if ( from_addr != @proxyd_addr ) || @tun_info[ :tund_addr ]
1228
+
1229
+ tund_port = data[ 9, 2 ].unpack( 'n' ).first
1230
+
1231
+ # puts "debug1 got tund port #{ tund_port }"
1232
+ tund_addr = Socket.sockaddr_in( tund_port, @proxyd_host )
1233
+ @tun_info[ :tund_addr ] = tund_addr
1234
+
1235
+ if @tun_info[ :ctlmsg_rbuffs ].any?
1236
+ # puts "debug1 move #{ @tun_info[ :ctlmsg_rbuffs ].size } ctlmsg rbuffs to ctlmsgs"
1237
+ @tun_info[ :ctlmsgs ] += @tun_info[ :ctlmsg_rbuffs ].map{ | _data | [ tund_addr, _data ] }
1238
+ @tun_info[ :ctlmsg_rbuffs ].clear
1239
+ add_write( tun )
1240
+ end
1241
+ when PAIRED
1242
+ return if from_addr != @tun_info[ :tund_addr ]
1243
+
1244
+ src_id, dst_port = data[ 9, 10 ].unpack( 'Q>n' )
1245
+
1246
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1247
+ return if src_ext.nil? || src_ext[ :dst_port ]
1248
+
1249
+ src = src_ext[ :src ]
1250
+ return if src.closed?
1251
+
1252
+ # puts "debug1 got paired #{ src_id } #{ dst_port }"
1253
+
1254
+ if dst_port == 0
1255
+ set_is_closing( src )
1256
+ return
1257
+ end
1258
+
1259
+ src_ext[ :dst_port ] = dst_port
1260
+ @tun_info[ :src_ids ][ dst_port ] = src_id
1261
+
1262
+ src_info = @src_infos[ src ]
1263
+
1264
+ if src_info[ :proxy_proto ] == :http
1265
+ if src_info[ :is_connect ]
1266
+ # puts "debug1 add src wbuff http ok"
1267
+ add_src_wbuff( src, HTTP_OK )
1268
+ else
1269
+ # puts "debug1 add src rbuffs to tun wbuffs"
1270
+
1271
+ src_info[ :rbuffs ].each do | pack_id, _data |
1272
+ add_tun_wbuff( src_id, pack_id, _data )
1273
+ end
1274
+ end
1275
+ elsif src_info[ :proxy_proto ] == :socks5
1276
+ add_src_wbuff_socks5_conn_reply( src_ext[ :src ] )
1277
+ end
1278
+ when DEST_STATUS
1279
+ return if from_addr != @tun_info[ :tund_addr ]
1280
+
1281
+ dst_port, relay_dst_pack_id, continue_src_pack_id = data[ 9, 18 ].unpack( 'nQ>Q>' )
1282
+
1283
+ src_id = @tun_info[ :src_ids ][ dst_port ]
1284
+ return unless src_id
1285
+
1286
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1287
+ return unless src_ext
1288
+
1289
+ # puts "debug2 got dest status"
1290
+
1291
+ release_wmems( src_ext, continue_src_pack_id )
1292
+
1293
+ # 发miss
1294
+ if !src_ext[ :src ].closed? && ( src_ext[ :continue_dst_pack_id ] < relay_dst_pack_id )
1295
+ ranges = []
1296
+ curr_pack_id = src_ext[ :continue_dst_pack_id ] + 1
1297
+
1298
+ src_ext[ :pieces ].keys.sort.each do | pack_id |
1299
+ if pack_id > curr_pack_id
1300
+ ranges << [ curr_pack_id, pack_id - 1 ]
1301
+ end
1302
+
1303
+ curr_pack_id = pack_id + 1
1304
+ end
1305
+
1306
+ if curr_pack_id <= relay_dst_pack_id
1307
+ ranges << [ curr_pack_id, relay_dst_pack_id ]
1308
+ end
1309
+
1310
+ pack_count = 0
1311
+ # puts "debug1 continue/relay #{ src_ext[ :continue_dst_pack_id ] }/#{ relay_dst_pack_id } send MISS #{ ranges.size }"
1312
+
1313
+ ranges.each do | pack_id_begin, pack_id_end |
1314
+ if pack_count >= BREAK_SEND_MISS
1315
+ puts "p#{ Process.pid } #{ Time.new } break send miss at #{ pack_id_begin }"
1316
+ break
1317
+ end
1318
+
1319
+ data2 = [ 0, MISS, dst_port, pack_id_begin, pack_id_end ].pack( 'Q>CnQ>Q>' )
1320
+ add_tun_ctlmsg( data2 )
1321
+ pack_count += ( pack_id_end - pack_id_begin + 1 )
1322
+ end
1323
+ end
1324
+ when MISS
1325
+ return if from_addr != @tun_info[ :tund_addr ]
1326
+
1327
+ src_id, pack_id_begin, pack_id_end = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
1328
+
1329
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1330
+ return unless src_ext
1331
+
1332
+ ( pack_id_begin..pack_id_end ).each do | pack_id |
1333
+ send_at = src_ext[ :send_ats ][ pack_id ]
1334
+
1335
+ if send_at
1336
+ break if now - send_at < STATUS_INTERVAL
1337
+ @tun_info[ :resendings ] << [ src_id, pack_id ]
1338
+ end
1339
+ end
1340
+
1341
+ add_write( tun )
1342
+ when FIN1
1343
+ return if from_addr != @tun_info[ :tund_addr ]
1344
+
1345
+ dst_port, biggest_dst_pack_id, continue_src_pack_id = data[ 9, 18 ].unpack( 'nQ>Q>' )
1346
+
1347
+ src_id = @tun_info[ :src_ids ][ dst_port ]
1348
+ return unless src_id
1349
+
1350
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1351
+ return unless src_ext
1352
+
1353
+ # puts "debug1 got fin1 #{ dst_port } biggest dst pack #{ biggest_dst_pack_id } completed src pack #{ continue_src_pack_id }"
1354
+ src_ext[ :is_dst_closed ] = true
1355
+ src_ext[ :biggest_dst_pack_id ] = biggest_dst_pack_id
1356
+ release_wmems( src_ext, continue_src_pack_id )
1357
+
1358
+ if ( biggest_dst_pack_id == src_ext[ :continue_dst_pack_id ] )
1359
+ # puts "debug1 2-1. tun recv fin1 -> all traffic received ? -> close src after write"
1360
+ set_is_closing( src_ext[ :src ] )
1361
+ end
1362
+ when FIN2
1363
+ return if from_addr != @tun_info[ :tund_addr ]
1364
+
1365
+ dst_port = data[ 9, 2 ].unpack( 'n' ).first
1366
+
1367
+ src_id = @tun_info[ :src_ids ][ dst_port ]
1368
+ return unless src_id
1369
+
1370
+ # puts "debug1 1-2. tun recv fin2 -> del src ext"
1371
+ del_src_ext( src_id )
1372
+ when TUND_FIN
1373
+ return if from_addr != @tun_info[ :tund_addr ]
1374
+
1375
+ puts "p#{ Process.pid } #{ Time.new } recv tund fin"
1376
+ set_is_closing( tun )
1377
+ end
1378
+
1379
+ return
1380
+ end
1381
+
1382
+ return if from_addr != @tun_info[ :tund_addr ]
1383
+
1384
+ dst_port = data[ 8, 2 ].unpack( 'n' ).first
1385
+
1386
+ src_id = @tun_info[ :src_ids ][ dst_port ]
1387
+ return unless src_id
1388
+
1389
+ src_ext = @tun_info[ :src_exts ][ src_id ]
1390
+ return if src_ext.nil? || src_ext[ :src ].closed?
1391
+ return if ( pack_id <= src_ext[ :continue_dst_pack_id ] ) || src_ext[ :pieces ].include?( pack_id )
1392
+
1393
+ data = data[ 10..-1 ]
1394
+ # puts "debug2 got pack #{ pack_id }"
1395
+
1396
+ if pack_id <= CONFUSE_UNTIL
1397
+ # puts "debug2 #{ data.inspect }"
1398
+ data = @custom.decode( data )
1399
+ # puts "debug1 decoded pack #{ pack_id }"
1400
+ end
1401
+
1402
+ # 放进写前,跳号放碎片缓存
1403
+ if pack_id - src_ext[ :continue_dst_pack_id ] == 1
1404
+ while src_ext[ :pieces ].include?( pack_id + 1 )
1405
+ data << src_ext[ :pieces ].delete( pack_id + 1 )
1406
+ pack_id += 1
1407
+ end
1408
+
1409
+ src_ext[ :continue_dst_pack_id ] = pack_id
1410
+ src_ext[ :last_continue_at ] = now
1411
+ add_src_wbuff( src_ext[ :src ], data )
1412
+ # puts "debug2 update continue dst pack #{ pack_id }"
1413
+
1414
+ # 接到流量,若对面已关闭,且流量正好收全,关闭src
1415
+ if src_ext[ :is_dst_closed ] && ( pack_id == src_ext[ :biggest_dst_pack_id ] )
1416
+ # puts "debug1 2-2. tun recv traffic -> dst closed and all traffic received ? -> close src after write"
1417
+ set_is_closing( src_ext[ :src ] )
1418
+ end
1419
+ else
1420
+ src_ext[ :pieces ][ pack_id ] = data
1421
+ end
1422
+ end
1423
+
1424
+ end
1425
+ end