girl 0.63.0 → 0.67.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.
- checksums.yaml +4 -4
- data/lib/girl/head.rb +3 -5
- data/lib/girl/proxy.rb +7 -7
- data/lib/girl/proxy_worker.rb +84 -85
- data/lib/girl/proxyd_worker.rb +58 -52
- data/lib/girl/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f8e5816785b5a946fbf35da638fc06a6e496704e3271153640ceb4301a27a84
|
4
|
+
data.tar.gz: 12813e957b5cdb5cbf726b5078792651197c5e84cfeaa4251c491897e6017a56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d3721a009010761bbcc196a9e650e801cecdd287410cd118981e0ceaeb1225d720f592fedfb988a5341229e9eeef53c94ab949a9562b3ad659d53d40481b9ca
|
7
|
+
data.tar.gz: 18b23dc9a36b43d6a9aeda602fea61398d236d1fc2baf680d312f048e2f5270479aeb88b2b7c6b445ab98e146c8444624434dfc3a91f3608708af85afb598e22
|
data/lib/girl/head.rb
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
module Girl
|
2
|
-
PACK_SIZE = 1328 # 包大小 1400(console MTU) - 8(PPPoE header) - 40(IPv6 header) - 8(UDP header) - 8(
|
2
|
+
PACK_SIZE = 1328 # 包大小 1400(console MTU) - 8(PPPoE header) - 40(IPv6 header) - 8(UDP header) - 8(pack id) - 8(src id) = 1328
|
3
3
|
CHUNK_SIZE = PACK_SIZE * 1000 # 块大小
|
4
|
-
PROXY_PACK_SIZE = 1320 # 1400(console MTU) - 8(PPPoE header) - 40(IPv6 header) - 8(UDP header) - 8(pack id) - 16(src_addr) = 1320
|
5
|
-
PROXY_CHUNK_SIZE = PROXY_PACK_SIZE * 1000 # proxy块大小
|
6
4
|
WBUFFS_LIMIT = 1000 # 写前上限,超过上限结一个块
|
7
5
|
WMEMS_LIMIT = 100_000 # 写后上限,达到上限暂停写
|
8
6
|
RESUME_BELOW = 50_000 # 降到多少以下恢复写
|
9
7
|
EXPIRE_NEW = 10 # 创建之后多久没有流量进来,过期
|
10
|
-
EXPIRE_AFTER =
|
8
|
+
EXPIRE_AFTER = 300 # 多久没有新流量,过期
|
11
9
|
CHECK_EXPIRE_INTERVAL = 30 # 检查过期间隔
|
12
10
|
HEARTBEAT_INTERVAL = 30 # 心跳间隔
|
13
|
-
STATUS_INTERVAL = 0.
|
11
|
+
STATUS_INTERVAL = 0.5 # 发送状态间隔
|
14
12
|
SEND_STATUS_UNTIL = 10 # 持续的告之对面状态,直到没有流量往来,持续多少秒
|
15
13
|
BREAK_SEND_MISS = 10_000 # miss包个数上限,达到上限忽略要后面的段,可控碎片缓存
|
16
14
|
CONFUSE_UNTIL = 5 # 混淆前几个包
|
data/lib/girl/proxy.rb
CHANGED
@@ -24,19 +24,19 @@ require 'socket'
|
|
24
24
|
# tun-tund:
|
25
25
|
#
|
26
26
|
# Q>: 0 ctlmsg -> C: 2 heartbeat -> C: random char
|
27
|
-
# 3 a new src ->
|
28
|
-
# 4 paired ->
|
27
|
+
# 3 a new src -> Q>: src_id -> encoded destination address
|
28
|
+
# 4 paired -> Q>: src_id -> n: dst_port
|
29
29
|
# 5 dst status -> n: dst_port -> Q>Q>: biggest_dst_pack_id continue_src_pack_id
|
30
|
-
# 6 src status ->
|
31
|
-
# 7 miss ->
|
32
|
-
# 8 fin1 ->
|
30
|
+
# 6 src status -> Q>: src_id -> Q>Q>: biggest_src_pack_id continue_dst_pack_id
|
31
|
+
# 7 miss -> Q>: src_id/n: dst_port -> Q>Q>: pack_id_begin pack_id_end
|
32
|
+
# 8 fin1 -> Q>: src_id/n: dst_port -> Q>Q>: biggest_src_pack_id continue_dst_pack_id / biggest_dst_pack_id continue_src_pack_id
|
33
33
|
# 9 not use
|
34
|
-
# 10 fin2 ->
|
34
|
+
# 10 fin2 -> Q>: src_id/n: dst_port
|
35
35
|
# 11 not use
|
36
36
|
# 12 tund fin
|
37
37
|
# 13 tun fin
|
38
38
|
#
|
39
|
-
# Q>: 1+ pack_id ->
|
39
|
+
# Q>: 1+ pack_id -> Q>: src_id/n: dst_port -> traffic
|
40
40
|
#
|
41
41
|
# close logic
|
42
42
|
# ===========
|
data/lib/girl/proxy_worker.rb
CHANGED
@@ -109,12 +109,13 @@ module Girl
|
|
109
109
|
set_is_closing( @tun )
|
110
110
|
else
|
111
111
|
data = [ 0, HEARTBEAT, rand( 128 ) ].pack( 'Q>CC' )
|
112
|
+
# puts "debug1 #{ Time.new } heartbeat"
|
112
113
|
add_tun_ctlmsg( data )
|
113
114
|
|
114
|
-
@tun_info[ :src_exts ].each do |
|
115
|
+
@tun_info[ :src_exts ].each do | src_id, src_ext |
|
115
116
|
if src_ext[ :src ].closed? && ( now - src_ext[ :last_continue_at ] > EXPIRE_AFTER )
|
116
|
-
puts "p#{ Process.pid } #{ Time.new } expire
|
117
|
-
del_src_ext(
|
117
|
+
puts "p#{ Process.pid } #{ Time.new } expire src ext #{ src_id }"
|
118
|
+
del_src_ext( src_id )
|
118
119
|
end
|
119
120
|
end
|
120
121
|
end
|
@@ -165,10 +166,10 @@ module Girl
|
|
165
166
|
if @tun_info[ :src_exts ].any?
|
166
167
|
now = Time.new
|
167
168
|
|
168
|
-
@tun_info[ :src_exts ].each do |
|
169
|
+
@tun_info[ :src_exts ].each do | src_id, src_ext |
|
169
170
|
if src_ext[ :dst_port ] && ( now - src_ext[ :last_continue_at ] < SEND_STATUS_UNTIL )
|
170
171
|
# puts "debug2 ctl send status biggest #{ src_ext[ :biggest_pack_id ] } continue dst #{ src_ext[ :continue_dst_pack_id ] }"
|
171
|
-
data = [
|
172
|
+
data = [ 0, SOURCE_STATUS, src_id, src_ext[ :biggest_pack_id ], src_ext[ :continue_dst_pack_id ] ].pack( 'Q>CQ>Q>Q>' )
|
172
173
|
add_tun_ctlmsg( data )
|
173
174
|
need_trigger = true
|
174
175
|
end
|
@@ -179,6 +180,11 @@ module Girl
|
|
179
180
|
puts "p#{ Process.pid } #{ Time.new } resume tun"
|
180
181
|
@tun_info[ :paused ] = false
|
181
182
|
add_write( @tun )
|
183
|
+
|
184
|
+
@tun_info[ :src_exts ].each do | _, src_ext |
|
185
|
+
add_write( src_ext[ :src ] )
|
186
|
+
end
|
187
|
+
|
182
188
|
need_trigger = true
|
183
189
|
end
|
184
190
|
|
@@ -311,20 +317,20 @@ module Girl
|
|
311
317
|
def new_a_tun
|
312
318
|
tun = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
|
313
319
|
tun.bind( Socket.sockaddr_in( 0, '0.0.0.0' ) )
|
314
|
-
port = tun.local_address.
|
320
|
+
port = tun.local_address.ip_port
|
315
321
|
tun_info = {
|
316
322
|
port: port, # 端口
|
317
323
|
ctlmsg_rbuffs: [], # 还没配上tund,暂存的ctlmsg
|
318
324
|
ctlmsgs: [], # [ to_addr, data ]
|
319
|
-
wbuffs: [], # 写前缓存 [
|
320
|
-
caches: [], # 块读出缓存 [
|
325
|
+
wbuffs: [], # 写前缓存 [ src_id, data ]
|
326
|
+
caches: [], # 块读出缓存 [ src_id, data ]
|
321
327
|
chunks: [], # 块队列 filename
|
322
328
|
spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
|
323
329
|
tund_addr: nil, # tund地址
|
324
|
-
src_exts: {}, # src额外信息
|
325
|
-
|
330
|
+
src_exts: {}, # src额外信息 src_id => {}
|
331
|
+
src_ids: {}, # dst_port => src_id
|
326
332
|
paused: false, # 是否暂停写
|
327
|
-
resendings: [], # 重传队列 [
|
333
|
+
resendings: [], # 重传队列 [ src_id, pack_id ]
|
328
334
|
created_at: Time.new, # 创建时间
|
329
335
|
last_recv_at: nil, # 上一次收到流量的时间,过期关闭
|
330
336
|
is_closing: false # 是否准备关闭
|
@@ -363,7 +369,7 @@ module Girl
|
|
363
369
|
end
|
364
370
|
|
365
371
|
# puts "debug1 a new dst #{ dst.local_address.inspect }"
|
366
|
-
local_port = dst.local_address.
|
372
|
+
local_port = dst.local_address.ip_port
|
367
373
|
@dsts[ local_port ] = dst
|
368
374
|
@dst_infos[ dst ] = {
|
369
375
|
local_port: local_port, # 本地端口
|
@@ -424,14 +430,14 @@ module Girl
|
|
424
430
|
}
|
425
431
|
|
426
432
|
src_info = @src_infos[ src ]
|
427
|
-
|
428
|
-
@tun_info[ :src_exts ][
|
433
|
+
src_id = src_info[ :id ]
|
434
|
+
@tun_info[ :src_exts ][ src_id ] = src_ext
|
429
435
|
src_info[ :proxy_type ] = :tunnel
|
430
436
|
|
431
437
|
destination_port = src_info[ :destination_port ]
|
432
438
|
destination_domain = src_info[ :destination_domain ]
|
433
439
|
destination_domain_port = [ destination_domain, destination_port ].join( ':' )
|
434
|
-
data = [ [ 0, A_NEW_SOURCE ].pack( 'Q>
|
440
|
+
data = [ [ 0, A_NEW_SOURCE, src_id ].pack( 'Q>CQ>' ), @custom.encode( destination_domain_port ) ].join
|
435
441
|
loop_send_a_new_source( src_ext, data )
|
436
442
|
end
|
437
443
|
|
@@ -490,14 +496,14 @@ module Girl
|
|
490
496
|
##
|
491
497
|
# add tun wbuff
|
492
498
|
#
|
493
|
-
def add_tun_wbuff(
|
494
|
-
@tun_info[ :wbuffs ] << [
|
499
|
+
def add_tun_wbuff( src_id, data )
|
500
|
+
@tun_info[ :wbuffs ] << [ src_id, data ]
|
495
501
|
|
496
502
|
if @tun_info[ :wbuffs ].size >= WBUFFS_LIMIT
|
497
503
|
spring = @tun_info[ :chunks ].size > 0 ? ( @tun_info[ :spring ] + 1 ) : 0
|
498
504
|
filename = "#{ Process.pid }-#{ @tun_info[ :port ] }.#{ spring }"
|
499
505
|
chunk_path = File.join( @tun_chunk_dir, filename )
|
500
|
-
wbuffs = @tun_info[ :wbuffs ].map{ |
|
506
|
+
wbuffs = @tun_info[ :wbuffs ].map{ | _src_id, _data | [ [ _src_id, _data.bytesize ].pack( 'Q>n' ), _data ].join }
|
501
507
|
|
502
508
|
begin
|
503
509
|
IO.binwrite( chunk_path, wbuffs.join )
|
@@ -522,9 +528,9 @@ module Girl
|
|
522
528
|
src_info = @src_infos[ src ]
|
523
529
|
src_info[ :wbuff ] << data
|
524
530
|
|
525
|
-
if src_info[ :wbuff ].bytesize >=
|
531
|
+
if src_info[ :wbuff ].bytesize >= CHUNK_SIZE
|
526
532
|
spring = src_info[ :chunks ].size > 0 ? ( src_info[ :spring ] + 1 ) : 0
|
527
|
-
filename = "#{ Process.pid }-#{
|
533
|
+
filename = "#{ Process.pid }-#{ src_info[ :id ] }.#{ spring }"
|
528
534
|
chunk_path = File.join( @src_chunk_dir, filename )
|
529
535
|
|
530
536
|
begin
|
@@ -550,7 +556,7 @@ module Girl
|
|
550
556
|
dst_info = @dst_infos[ dst ]
|
551
557
|
dst_info[ :wbuff ] << data
|
552
558
|
|
553
|
-
if dst_info[ :wbuff ].bytesize >=
|
559
|
+
if dst_info[ :wbuff ].bytesize >= CHUNK_SIZE
|
554
560
|
spring = dst_info[ :chunks ].size > 0 ? ( dst_info[ :spring ] + 1 ) : 0
|
555
561
|
filename = "#{ Process.pid }-#{ dst_info[ :local_port ] }.#{ spring }"
|
556
562
|
chunk_path = File.join( @dst_chunk_dir, filename )
|
@@ -630,22 +636,22 @@ module Girl
|
|
630
636
|
end
|
631
637
|
end
|
632
638
|
|
633
|
-
|
639
|
+
src_id = src_info[ :id ]
|
634
640
|
|
635
641
|
if src_info[ :proxy_type ] == :tunnel
|
636
642
|
return if @tun.closed?
|
637
643
|
|
638
|
-
src_ext = @tun_info[ :src_exts ][
|
644
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
639
645
|
return if src_ext.nil? || src_ext[ :dst_port ].nil?
|
640
646
|
|
641
647
|
if src_ext[ :is_dst_closed ]
|
642
648
|
# puts "debug1 2-2. after close src -> dst closed ? yes -> del src ext -> send fin2"
|
643
|
-
del_src_ext(
|
644
|
-
data = [
|
649
|
+
del_src_ext( src_id )
|
650
|
+
data = [ 0, FIN2, src_id ].pack( 'Q>CQ>' )
|
645
651
|
add_tun_ctlmsg( data )
|
646
652
|
else
|
647
653
|
# puts "debug1 1-1. after close src -> dst closed ? no -> send fin1"
|
648
|
-
data = [
|
654
|
+
data = [ 0, FIN1, src_id, src_ext[ :biggest_pack_id ], src_ext[ :continue_dst_pack_id ] ].pack( 'Q>CQ>Q>Q>' )
|
649
655
|
add_tun_ctlmsg( data )
|
650
656
|
end
|
651
657
|
elsif src_info[ :proxy_type ] == :direct
|
@@ -702,11 +708,11 @@ module Girl
|
|
702
708
|
##
|
703
709
|
# del src ext
|
704
710
|
#
|
705
|
-
def del_src_ext(
|
706
|
-
src_ext = @tun_info[ :src_exts ].delete(
|
711
|
+
def del_src_ext( src_id )
|
712
|
+
src_ext = @tun_info[ :src_exts ].delete( src_id )
|
707
713
|
|
708
714
|
if src_ext
|
709
|
-
@tun_info[ :
|
715
|
+
@tun_info[ :src_ids ].delete( src_ext[ :dst_port ] )
|
710
716
|
end
|
711
717
|
end
|
712
718
|
|
@@ -762,7 +768,15 @@ module Girl
|
|
762
768
|
|
763
769
|
if data.empty?
|
764
770
|
if src_info[ :is_closing ]
|
765
|
-
|
771
|
+
if @tun.closed?
|
772
|
+
close_src( src )
|
773
|
+
elsif @tun_info[ :paused ]
|
774
|
+
@writes.delete( src )
|
775
|
+
elsif !@writes.include?( @tun )
|
776
|
+
# 转发光了,正式关闭
|
777
|
+
# puts "debug2 close src after tun empty"
|
778
|
+
close_src( src )
|
779
|
+
end
|
766
780
|
else
|
767
781
|
@writes.delete( src )
|
768
782
|
end
|
@@ -861,8 +875,8 @@ module Girl
|
|
861
875
|
|
862
876
|
# 重传
|
863
877
|
while @tun_info[ :resendings ].any?
|
864
|
-
|
865
|
-
src_ext = @tun_info[ :src_exts ][
|
878
|
+
src_id, pack_id = @tun_info[ :resendings ].first
|
879
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
866
880
|
|
867
881
|
if src_ext
|
868
882
|
data = src_ext[ :wmems ][ pack_id ]
|
@@ -893,7 +907,7 @@ module Girl
|
|
893
907
|
|
894
908
|
# 取写前
|
895
909
|
if @tun_info[ :caches ].any?
|
896
|
-
|
910
|
+
src_id, data = @tun_info[ :caches ].first
|
897
911
|
from = :caches
|
898
912
|
elsif @tun_info[ :chunks ].any?
|
899
913
|
path = File.join( @tun_chunk_dir, @tun_info[ :chunks ].shift )
|
@@ -910,24 +924,23 @@ module Girl
|
|
910
924
|
caches = []
|
911
925
|
|
912
926
|
until data.empty?
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
data = data[ ( 18 + pack_size )..-1 ]
|
927
|
+
_src_id, pack_size = data[ 0, 10 ].unpack( 'Q>n' )
|
928
|
+
caches << [ _src_id, data[ 10, pack_size ] ]
|
929
|
+
data = data[ ( 10 + pack_size )..-1 ]
|
917
930
|
end
|
918
931
|
|
919
932
|
@tun_info[ :caches ] = caches
|
920
|
-
|
933
|
+
src_id, data = caches.first
|
921
934
|
from = :caches
|
922
935
|
elsif @tun_info[ :wbuffs ].any?
|
923
|
-
|
936
|
+
src_id, data = @tun_info[ :wbuffs ].first
|
924
937
|
from = :wbuffs
|
925
938
|
else
|
926
939
|
@writes.delete( tun )
|
927
940
|
return
|
928
941
|
end
|
929
942
|
|
930
|
-
src_ext = @tun_info[ :src_exts ][
|
943
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
931
944
|
|
932
945
|
if src_ext
|
933
946
|
pack_id = src_ext[ :biggest_pack_id ] + 1
|
@@ -937,7 +950,7 @@ module Girl
|
|
937
950
|
# puts "debug1 encoded pack #{ pack_id }"
|
938
951
|
end
|
939
952
|
|
940
|
-
data = [ [ pack_id ].pack( 'Q>' ),
|
953
|
+
data = [ [ pack_id, src_id ].pack( 'Q>Q>' ), data ].join
|
941
954
|
|
942
955
|
begin
|
943
956
|
tun.sendmsg( data, 0, @tun_info[ :tund_addr ] )
|
@@ -972,10 +985,11 @@ module Girl
|
|
972
985
|
return
|
973
986
|
end
|
974
987
|
|
975
|
-
|
976
|
-
|
988
|
+
id = rand( ( 2 ** 64 ) - 1 ) + 1
|
989
|
+
# puts "debug1 accept a src #{ addrinfo.inspect } #{ id }"
|
990
|
+
|
977
991
|
@src_infos[ src ] = {
|
978
|
-
|
992
|
+
id: id, # id
|
979
993
|
proxy_proto: :uncheck, # :uncheck / :http / :socks5
|
980
994
|
proxy_type: :uncheck, # :uncheck / :checking / :direct / :tunnel / :negotiation
|
981
995
|
dst: nil, # :direct的场合,对应的dst
|
@@ -999,7 +1013,7 @@ module Girl
|
|
999
1013
|
#
|
1000
1014
|
def read_src( src )
|
1001
1015
|
begin
|
1002
|
-
data = src.read_nonblock(
|
1016
|
+
data = src.read_nonblock( PACK_SIZE )
|
1003
1017
|
rescue IO::WaitReadable, Errno::EINTR
|
1004
1018
|
return
|
1005
1019
|
rescue Exception => e
|
@@ -1129,8 +1143,8 @@ module Girl
|
|
1129
1143
|
puts "p#{ Process.pid } #{ Time.new } socks5 cmd #{ cmd } not implement"
|
1130
1144
|
end
|
1131
1145
|
when :tunnel
|
1132
|
-
|
1133
|
-
src_ext = @tun_info[ :src_exts ][
|
1146
|
+
src_id = src_info[ :id ]
|
1147
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1134
1148
|
|
1135
1149
|
unless src_ext
|
1136
1150
|
# puts "debug1 not found src ext"
|
@@ -1149,7 +1163,7 @@ module Girl
|
|
1149
1163
|
data, _ = sub_http_request( data )
|
1150
1164
|
end
|
1151
1165
|
|
1152
|
-
add_tun_wbuff(
|
1166
|
+
add_tun_wbuff( src_id, data )
|
1153
1167
|
else
|
1154
1168
|
# puts "debug1 remote dst not ready, save data to src rbuff"
|
1155
1169
|
src_info[ :rbuffs ] << data
|
@@ -1181,7 +1195,7 @@ module Girl
|
|
1181
1195
|
#
|
1182
1196
|
def read_dst( dst )
|
1183
1197
|
begin
|
1184
|
-
data = dst.read_nonblock(
|
1198
|
+
data = dst.read_nonblock( PACK_SIZE )
|
1185
1199
|
rescue IO::WaitReadable, Errno::EINTR
|
1186
1200
|
return
|
1187
1201
|
rescue Exception => e
|
@@ -1211,6 +1225,7 @@ module Girl
|
|
1211
1225
|
data, addrinfo, rflags, *controls = tun.recvmsg
|
1212
1226
|
from_addr = addrinfo.to_sockaddr
|
1213
1227
|
now = Time.new
|
1228
|
+
@tun_info[ :last_recv_at ] = now
|
1214
1229
|
pack_id = data[ 0, 8 ].unpack( 'Q>' ).first
|
1215
1230
|
|
1216
1231
|
if pack_id == 0
|
@@ -1220,7 +1235,6 @@ module Girl
|
|
1220
1235
|
when TUND_PORT
|
1221
1236
|
return if ( from_addr != @proxyd_addr ) || @tun_info[ :tund_addr ]
|
1222
1237
|
|
1223
|
-
@tun_info[ :last_recv_at ] = now
|
1224
1238
|
tund_port = data[ 9, 2 ].unpack( 'n' ).first
|
1225
1239
|
|
1226
1240
|
# puts "debug1 got tund port #{ tund_port }"
|
@@ -1236,18 +1250,15 @@ module Girl
|
|
1236
1250
|
when PAIRED
|
1237
1251
|
return if from_addr != @tun_info[ :tund_addr ]
|
1238
1252
|
|
1239
|
-
|
1240
|
-
dst_port = data[ 25, 2 ].unpack( 'n' ).first
|
1253
|
+
src_id, dst_port = data[ 9, 10 ].unpack( 'Q>n' )
|
1241
1254
|
|
1242
|
-
src_ext = @tun_info[ :src_exts ][
|
1255
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1243
1256
|
return if src_ext.nil? || src_ext[ :dst_port ]
|
1244
1257
|
|
1245
1258
|
src = src_ext[ :src ]
|
1246
1259
|
return if src.closed?
|
1247
1260
|
|
1248
|
-
|
1249
|
-
|
1250
|
-
# puts "debug1 got paired #{ Addrinfo.new( src_addr ).inspect } #{ dst_port }"
|
1261
|
+
# puts "debug1 got paired #{ src_id } #{ dst_port }"
|
1251
1262
|
|
1252
1263
|
if dst_port == 0
|
1253
1264
|
set_is_closing( src )
|
@@ -1255,7 +1266,7 @@ module Girl
|
|
1255
1266
|
end
|
1256
1267
|
|
1257
1268
|
src_ext[ :dst_port ] = dst_port
|
1258
|
-
@tun_info[ :
|
1269
|
+
@tun_info[ :src_ids ][ dst_port ] = src_id
|
1259
1270
|
|
1260
1271
|
src_info = @src_infos[ src ]
|
1261
1272
|
|
@@ -1271,7 +1282,7 @@ module Girl
|
|
1271
1282
|
# puts "debug1 add src rbuffs to tun wbuffs"
|
1272
1283
|
|
1273
1284
|
datas.each do | _data |
|
1274
|
-
add_tun_wbuff(
|
1285
|
+
add_tun_wbuff( src_id, _data )
|
1275
1286
|
end
|
1276
1287
|
end
|
1277
1288
|
elsif src_info[ :proxy_proto ] == :socks5
|
@@ -1282,14 +1293,13 @@ module Girl
|
|
1282
1293
|
|
1283
1294
|
dst_port, biggest_dst_pack_id, continue_src_pack_id = data[ 9, 18 ].unpack( 'nQ>Q>' )
|
1284
1295
|
|
1285
|
-
|
1286
|
-
return unless
|
1296
|
+
src_id = @tun_info[ :src_ids ][ dst_port ]
|
1297
|
+
return unless src_id
|
1287
1298
|
|
1288
|
-
src_ext = @tun_info[ :src_exts ][
|
1299
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1289
1300
|
return unless src_ext
|
1290
1301
|
|
1291
1302
|
# puts "debug2 got dest status"
|
1292
|
-
@tun_info[ :last_recv_at ] = now
|
1293
1303
|
|
1294
1304
|
# 更新对面发到几
|
1295
1305
|
if biggest_dst_pack_id > src_ext[ :biggest_dst_pack_id ]
|
@@ -1339,20 +1349,17 @@ module Girl
|
|
1339
1349
|
when MISS
|
1340
1350
|
return if from_addr != @tun_info[ :tund_addr ]
|
1341
1351
|
|
1342
|
-
|
1343
|
-
pack_id_begin, pack_id_end = data[ 25, 16 ].unpack( 'Q>Q>' )
|
1352
|
+
src_id, pack_id_begin, pack_id_end = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
|
1344
1353
|
|
1345
|
-
src_ext = @tun_info[ :src_exts ][
|
1354
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1346
1355
|
return unless src_ext
|
1347
1356
|
|
1348
|
-
@tun_info[ :last_recv_at ] = now
|
1349
|
-
|
1350
1357
|
( pack_id_begin..pack_id_end ).each do | pack_id |
|
1351
1358
|
send_at = src_ext[ :send_ats ][ pack_id ]
|
1352
1359
|
|
1353
1360
|
if send_at
|
1354
1361
|
break if now - send_at < STATUS_INTERVAL
|
1355
|
-
@tun_info[ :resendings ] << [
|
1362
|
+
@tun_info[ :resendings ] << [ src_id, pack_id ]
|
1356
1363
|
end
|
1357
1364
|
end
|
1358
1365
|
|
@@ -1362,14 +1369,12 @@ module Girl
|
|
1362
1369
|
|
1363
1370
|
dst_port, biggest_dst_pack_id, continue_src_pack_id = data[ 9, 18 ].unpack( 'nQ>Q>' )
|
1364
1371
|
|
1365
|
-
|
1366
|
-
return unless
|
1372
|
+
src_id = @tun_info[ :src_ids ][ dst_port ]
|
1373
|
+
return unless src_id
|
1367
1374
|
|
1368
|
-
src_ext = @tun_info[ :src_exts ][
|
1375
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1369
1376
|
return unless src_ext
|
1370
1377
|
|
1371
|
-
@tun_info[ :last_recv_at ] = now
|
1372
|
-
|
1373
1378
|
# puts "debug1 got fin1 #{ dst_port } biggest dst pack #{ biggest_dst_pack_id } completed src pack #{ continue_src_pack_id }"
|
1374
1379
|
src_ext[ :is_dst_closed ] = true
|
1375
1380
|
src_ext[ :biggest_dst_pack_id ] = biggest_dst_pack_id
|
@@ -1385,18 +1390,14 @@ module Girl
|
|
1385
1390
|
|
1386
1391
|
dst_port = data[ 9, 2 ].unpack( 'n' ).first
|
1387
1392
|
|
1388
|
-
|
1389
|
-
return unless
|
1390
|
-
|
1391
|
-
@tun_info[ :last_recv_at ] = now
|
1393
|
+
src_id = @tun_info[ :src_ids ][ dst_port ]
|
1394
|
+
return unless src_id
|
1392
1395
|
|
1393
1396
|
# puts "debug1 1-2. recv fin2 -> del src ext"
|
1394
|
-
del_src_ext(
|
1397
|
+
del_src_ext( src_id )
|
1395
1398
|
when TUND_FIN
|
1396
1399
|
return if from_addr != @tun_info[ :tund_addr ]
|
1397
1400
|
|
1398
|
-
@tun_info[ :last_recv_at ] = now
|
1399
|
-
|
1400
1401
|
puts "p#{ Process.pid } #{ Time.new } recv tund fin"
|
1401
1402
|
set_is_closing( tun )
|
1402
1403
|
end
|
@@ -1408,15 +1409,13 @@ module Girl
|
|
1408
1409
|
|
1409
1410
|
dst_port = data[ 8, 2 ].unpack( 'n' ).first
|
1410
1411
|
|
1411
|
-
|
1412
|
-
return unless
|
1412
|
+
src_id = @tun_info[ :src_ids ][ dst_port ]
|
1413
|
+
return unless src_id
|
1413
1414
|
|
1414
|
-
src_ext = @tun_info[ :src_exts ][
|
1415
|
+
src_ext = @tun_info[ :src_exts ][ src_id ]
|
1415
1416
|
return if src_ext.nil? || src_ext[ :src ].closed?
|
1416
1417
|
return if ( pack_id <= src_ext[ :continue_dst_pack_id ] ) || src_ext[ :pieces ].include?( pack_id )
|
1417
1418
|
|
1418
|
-
@tun_info[ :last_recv_at ] = now
|
1419
|
-
|
1420
1419
|
data = data[ 10..-1 ]
|
1421
1420
|
# puts "debug2 got pack #{ pack_id }"
|
1422
1421
|
|
data/lib/girl/proxyd_worker.rb
CHANGED
@@ -103,23 +103,27 @@ module Girl
|
|
103
103
|
is_expired = tund_info[ :last_recv_at ] ? ( now - tund_info[ :last_recv_at ] > EXPIRE_AFTER ) : ( now - tund_info[ :created_at ] > EXPIRE_NEW )
|
104
104
|
|
105
105
|
if is_expired
|
106
|
-
puts "p#{ Process.pid } #{ Time.new } expire tund"
|
106
|
+
puts "p#{ Process.pid } #{ Time.new } expire tund #{ tund_info[ :port ] }"
|
107
107
|
set_is_closing( tund )
|
108
|
-
need_trigger = true
|
109
108
|
else
|
109
|
+
data = [ 0, HEARTBEAT, rand( 128 ) ].pack( 'Q>CC' )
|
110
|
+
# puts "debug1 #{ Time.new } #{ tund_info[ :port ] } heartbeat"
|
111
|
+
add_tund_ctlmsg( tund, data )
|
112
|
+
|
110
113
|
tund_info[ :dst_exts ].each do | dst_local_port, dst_ext |
|
111
114
|
if dst_ext[ :dst ].closed? && ( now - dst_ext[ :last_continue_at ] > EXPIRE_AFTER )
|
112
|
-
puts "p#{ Process.pid } #{ Time.new } expire
|
115
|
+
puts "p#{ Process.pid } #{ Time.new } expire dst ext #{ dst_local_port }"
|
113
116
|
del_dst_ext( tund, dst_local_port )
|
114
|
-
need_trigger = true
|
115
117
|
end
|
116
118
|
end
|
117
119
|
end
|
120
|
+
|
121
|
+
need_trigger = true
|
118
122
|
end
|
119
123
|
end
|
120
124
|
|
121
125
|
@dst_infos.each do | dst, dst_info |
|
122
|
-
is_expired = dst_info[ :last_recv_at ]
|
126
|
+
is_expired = dst_info[ :last_recv_at ].nil? && ( now - dst_info[ :created_at ] > EXPIRE_NEW )
|
123
127
|
|
124
128
|
if is_expired
|
125
129
|
puts "p#{ Process.pid } #{ Time.new } expire dst"
|
@@ -167,6 +171,11 @@ module Girl
|
|
167
171
|
puts "p#{ Process.pid } #{ Time.new } resume tund"
|
168
172
|
tund_info[ :paused ] = false
|
169
173
|
add_write( tund )
|
174
|
+
|
175
|
+
tund_info[ :dst_exts ].each do | _, dst_ext |
|
176
|
+
add_write( dst_ext[ :dst ] )
|
177
|
+
end
|
178
|
+
|
170
179
|
need_trigger = true
|
171
180
|
end
|
172
181
|
end
|
@@ -183,7 +192,7 @@ module Girl
|
|
183
192
|
##
|
184
193
|
# resolve domain
|
185
194
|
#
|
186
|
-
def resolve_domain( tund,
|
195
|
+
def resolve_domain( tund, src_id, destination_domain_port )
|
187
196
|
resolv_cache = @resolv_caches[ destination_domain_port ]
|
188
197
|
|
189
198
|
if resolv_cache
|
@@ -191,7 +200,7 @@ module Girl
|
|
191
200
|
|
192
201
|
if Time.new - created_at < RESOLV_CACHE_EXPIRE
|
193
202
|
# puts "debug1 #{ destination_domain_port } hit resolv cache #{ Addrinfo.new( destination_addr ).inspect }"
|
194
|
-
deal_with_destination_addr( tund,
|
203
|
+
deal_with_destination_addr( tund, src_id, destination_addr )
|
195
204
|
return
|
196
205
|
end
|
197
206
|
|
@@ -215,7 +224,7 @@ module Girl
|
|
215
224
|
@resolv_caches[ destination_domain_port ] = [ destination_addr, Time.new ]
|
216
225
|
|
217
226
|
unless tund.closed?
|
218
|
-
if deal_with_destination_addr( tund,
|
227
|
+
if deal_with_destination_addr( tund, src_id, destination_addr )
|
219
228
|
next_tick
|
220
229
|
end
|
221
230
|
end
|
@@ -227,7 +236,7 @@ module Girl
|
|
227
236
|
##
|
228
237
|
# deal with destination addr
|
229
238
|
#
|
230
|
-
def deal_with_destination_addr( tund,
|
239
|
+
def deal_with_destination_addr( tund, src_id, destination_addr )
|
231
240
|
dst = Socket.new( Socket::AF_INET, Socket::SOCK_STREAM, 0 )
|
232
241
|
dst.setsockopt( Socket::SOL_TCP, Socket::TCP_NODELAY, 1 )
|
233
242
|
|
@@ -239,7 +248,7 @@ module Girl
|
|
239
248
|
return false
|
240
249
|
end
|
241
250
|
|
242
|
-
local_port = dst.local_address.
|
251
|
+
local_port = dst.local_address.ip_port
|
243
252
|
|
244
253
|
@dst_infos[ dst ] = {
|
245
254
|
local_port: local_port, # 本地端口
|
@@ -255,10 +264,10 @@ module Girl
|
|
255
264
|
add_read( dst, :dst )
|
256
265
|
|
257
266
|
tund_info = @tund_infos[ tund ]
|
258
|
-
tund_info[ :dst_local_ports ][
|
267
|
+
tund_info[ :dst_local_ports ][ src_id ] = local_port
|
259
268
|
tund_info[ :dst_exts ][ local_port ] = {
|
260
269
|
dst: dst, # dst
|
261
|
-
|
270
|
+
src_id: src_id, # 近端src id
|
262
271
|
wmems: {}, # 写后 pack_id => data
|
263
272
|
send_ats: {}, # 上一次发出时间 pack_id => send_at
|
264
273
|
biggest_pack_id: 0, # 发到几
|
@@ -270,7 +279,7 @@ module Girl
|
|
270
279
|
last_continue_at: Time.new # 创建,或者上一次收到连续流量,或者发出新包的时间
|
271
280
|
}
|
272
281
|
|
273
|
-
data = [
|
282
|
+
data = [ 0, PAIRED, src_id, local_port ].pack( 'Q>CQ>n' )
|
274
283
|
# puts "debug1 add ctlmsg paired #{ data.inspect }"
|
275
284
|
add_tund_ctlmsg( tund, data )
|
276
285
|
|
@@ -344,7 +353,7 @@ module Girl
|
|
344
353
|
dst_info = @dst_infos[ dst ]
|
345
354
|
dst_info[ :wbuff ] << data
|
346
355
|
|
347
|
-
if dst_info[ :wbuff ].bytesize >=
|
356
|
+
if dst_info[ :wbuff ].bytesize >= CHUNK_SIZE
|
348
357
|
spring = dst_info[ :chunks ].size > 0 ? ( dst_info[ :spring ] + 1 ) : 0
|
349
358
|
filename = "#{ Process.pid }-#{ dst_info[ :local_port ] }.#{ spring }"
|
350
359
|
chunk_path = File.join( @dst_chunk_dir, filename )
|
@@ -480,7 +489,7 @@ module Girl
|
|
480
489
|
dst_ext = tund_info[ :dst_exts ].delete( dst_local_port )
|
481
490
|
|
482
491
|
if dst_ext
|
483
|
-
tund_info[ :dst_local_ports ].delete( dst_ext[ :
|
492
|
+
tund_info[ :dst_local_ports ].delete( dst_ext[ :src_id ] )
|
484
493
|
end
|
485
494
|
end
|
486
495
|
|
@@ -556,7 +565,19 @@ module Girl
|
|
556
565
|
|
557
566
|
if data.empty?
|
558
567
|
if dst_info[ :is_closing ]
|
559
|
-
|
568
|
+
if dst_info[ :tund ].closed?
|
569
|
+
close_dst( dst )
|
570
|
+
else
|
571
|
+
tund_info = @tund_infos[ dst_info[ :tund ] ]
|
572
|
+
|
573
|
+
if tund_info[ :paused ]
|
574
|
+
@writes.delete( dst )
|
575
|
+
elsif !@writes.include?( dst_info[ :tund ] )
|
576
|
+
# 转发光了,正式关闭
|
577
|
+
# puts "debug2 close dst after tund empty"
|
578
|
+
close_dst( dst )
|
579
|
+
end
|
580
|
+
end
|
560
581
|
else
|
561
582
|
@writes.delete( dst )
|
562
583
|
end
|
@@ -724,7 +745,7 @@ module Girl
|
|
724
745
|
|
725
746
|
tund = Socket.new( Socket::AF_INET, Socket::SOCK_DGRAM, 0 )
|
726
747
|
tund.bind( Socket.sockaddr_in( 0, '0.0.0.0' ) )
|
727
|
-
port = tund.local_address.
|
748
|
+
port = tund.local_address.ip_port
|
728
749
|
|
729
750
|
@tunneling_tunds[ from_addr ] = tund
|
730
751
|
@tunds[ port ] = tund
|
@@ -736,9 +757,8 @@ module Girl
|
|
736
757
|
chunks: [], # 块队列 filename
|
737
758
|
spring: 0, # 块后缀,结块时,如果块队列不为空,则自增,为空,则置为0
|
738
759
|
tun_addr: from_addr, # tun地址
|
739
|
-
is_tunneled: false, # 是否已和tun打通
|
740
760
|
dst_exts: {}, # dst额外信息 dst_addr => {}
|
741
|
-
dst_local_ports: {}, #
|
761
|
+
dst_local_ports: {}, # src_id => dst_local_port
|
742
762
|
paused: false, # 是否暂停写
|
743
763
|
resendings: [], # 重传队列 [ dst_addr, pack_id ]
|
744
764
|
created_at: Time.new, # 创建时间
|
@@ -758,7 +778,7 @@ module Girl
|
|
758
778
|
#
|
759
779
|
def read_dst( dst )
|
760
780
|
begin
|
761
|
-
data = dst.read_nonblock(
|
781
|
+
data = dst.read_nonblock( PACK_SIZE )
|
762
782
|
rescue IO::WaitReadable, Errno::EINTR
|
763
783
|
return
|
764
784
|
rescue Exception => e
|
@@ -800,7 +820,7 @@ module Girl
|
|
800
820
|
end
|
801
821
|
end
|
802
822
|
|
803
|
-
tund_info[ :
|
823
|
+
tund_info[ :last_recv_at ] = now
|
804
824
|
pack_id = data[ 0, 8 ].unpack( 'Q>' ).first
|
805
825
|
|
806
826
|
if pack_id == 0
|
@@ -808,9 +828,9 @@ module Girl
|
|
808
828
|
|
809
829
|
case ctl_num
|
810
830
|
when A_NEW_SOURCE
|
811
|
-
|
812
|
-
dst_local_port = tund_info[ :dst_local_ports ][
|
813
|
-
# puts "debug1 got a new source #{
|
831
|
+
src_id = data[ 9, 8 ].unpack( 'Q>' ).first
|
832
|
+
dst_local_port = tund_info[ :dst_local_ports ][ src_id ]
|
833
|
+
# puts "debug1 got a new source #{ src_id }"
|
814
834
|
|
815
835
|
if dst_local_port
|
816
836
|
dst_ext = tund_info[ :dst_exts ][ dst_local_port ]
|
@@ -821,29 +841,25 @@ module Girl
|
|
821
841
|
end
|
822
842
|
|
823
843
|
# puts "debug1 readd ctlmsg paired #{ dst_local_port }"
|
824
|
-
data2 = [
|
844
|
+
data2 = [ 0, PAIRED, src_id, dst_local_port ].pack( 'Q>CQ>n' )
|
825
845
|
add_tund_ctlmsg( tund, data2 )
|
826
846
|
return
|
827
847
|
end
|
828
848
|
|
829
|
-
|
830
|
-
|
831
|
-
data = data[ 25..-1 ]
|
849
|
+
data = data[ 17..-1 ]
|
832
850
|
# puts "debug1 #{ data }"
|
833
851
|
destination_domain_port = @custom.decode( data )
|
834
|
-
resolve_domain( tund,
|
852
|
+
resolve_domain( tund, src_id, destination_domain_port )
|
835
853
|
when SOURCE_STATUS
|
836
|
-
|
837
|
-
biggest_src_pack_id, continue_dst_pack_id = data[ 25, 16 ].unpack( 'Q>Q>' )
|
854
|
+
src_id, biggest_src_pack_id, continue_dst_pack_id = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
|
838
855
|
|
839
|
-
dst_local_port = tund_info[ :dst_local_ports ][
|
856
|
+
dst_local_port = tund_info[ :dst_local_ports ][ src_id ]
|
840
857
|
return unless dst_local_port
|
841
858
|
|
842
859
|
dst_ext = tund_info[ :dst_exts ][ dst_local_port ]
|
843
860
|
return unless dst_ext
|
844
861
|
|
845
862
|
# puts "debug2 got source status"
|
846
|
-
tund_info[ :last_recv_at ] = now
|
847
863
|
|
848
864
|
# 更新对面发到几
|
849
865
|
if biggest_src_pack_id > dst_ext[ :biggest_src_pack_id ]
|
@@ -885,7 +901,7 @@ module Girl
|
|
885
901
|
break
|
886
902
|
end
|
887
903
|
|
888
|
-
data2 = [
|
904
|
+
data2 = [ 0, MISS, src_id, pack_id_begin, pack_id_end ].pack( 'Q>CQ>Q>Q>' )
|
889
905
|
add_tund_ctlmsg( tund, data2 )
|
890
906
|
pack_count += ( pack_id_end - pack_id_begin + 1 )
|
891
907
|
end
|
@@ -896,8 +912,6 @@ module Girl
|
|
896
912
|
dst_ext = tund_info[ :dst_exts ][ dst_local_port ]
|
897
913
|
return unless dst_ext
|
898
914
|
|
899
|
-
tund_info[ :last_recv_at ] = now
|
900
|
-
|
901
915
|
( pack_id_begin..pack_id_end ).each do | pack_id |
|
902
916
|
send_at = dst_ext[ :send_ats ][ pack_id ]
|
903
917
|
|
@@ -909,18 +923,15 @@ module Girl
|
|
909
923
|
|
910
924
|
add_write( tund )
|
911
925
|
when FIN1
|
912
|
-
|
913
|
-
biggest_src_pack_id, continue_dst_pack_id = data[ 25, 16 ].unpack( 'Q>Q>' )
|
926
|
+
src_id, biggest_src_pack_id, continue_dst_pack_id = data[ 9, 24 ].unpack( 'Q>Q>Q>' )
|
914
927
|
|
915
|
-
dst_local_port = tund_info[ :dst_local_ports ][
|
928
|
+
dst_local_port = tund_info[ :dst_local_ports ][ src_id ]
|
916
929
|
return unless dst_local_port
|
917
930
|
|
918
931
|
dst_ext = tund_info[ :dst_exts ][ dst_local_port ]
|
919
932
|
return unless dst_ext
|
920
933
|
|
921
|
-
|
922
|
-
|
923
|
-
# puts "debug1 got fin1 #{ Addrinfo.new( src_addr ).inspect } biggest src pack #{ biggest_src_pack_id } completed dst pack #{ continue_dst_pack_id }"
|
934
|
+
# puts "debug1 got fin1 #{ src_id } biggest src pack #{ biggest_src_pack_id } completed dst pack #{ continue_dst_pack_id }"
|
924
935
|
dst_ext[ :is_src_closed ] = true
|
925
936
|
dst_ext[ :biggest_src_pack_id ] = biggest_src_pack_id
|
926
937
|
release_wmems( dst_ext, continue_dst_pack_id )
|
@@ -931,36 +942,31 @@ module Girl
|
|
931
942
|
set_is_closing( dst_ext[ :dst ] )
|
932
943
|
end
|
933
944
|
when FIN2
|
934
|
-
|
945
|
+
src_id = data[ 9, 8 ].unpack( 'Q>' ).first
|
935
946
|
|
936
|
-
dst_local_port = tund_info[ :dst_local_ports ][
|
947
|
+
dst_local_port = tund_info[ :dst_local_ports ][ src_id ]
|
937
948
|
return unless dst_local_port
|
938
949
|
|
939
|
-
tund_info[ :last_recv_at ] = now
|
940
|
-
|
941
950
|
# puts "debug1 1-2. recv fin2 -> del dst ext"
|
942
951
|
del_dst_ext( tund, dst_local_port )
|
943
952
|
when TUN_FIN
|
944
953
|
puts "p#{ Process.pid } #{ Time.new } recv tun fin"
|
945
|
-
tund_info[ :last_recv_at ] = now
|
946
954
|
set_is_closing( tund )
|
947
955
|
end
|
948
956
|
|
949
957
|
return
|
950
958
|
end
|
951
959
|
|
952
|
-
|
960
|
+
src_id = data[ 8, 8 ].unpack( 'Q>' ).first
|
953
961
|
|
954
|
-
dst_local_port = tund_info[ :dst_local_ports ][
|
962
|
+
dst_local_port = tund_info[ :dst_local_ports ][ src_id ]
|
955
963
|
return unless dst_local_port
|
956
964
|
|
957
965
|
dst_ext = tund_info[ :dst_exts ][ dst_local_port ]
|
958
966
|
return if dst_ext.nil? || dst_ext[ :dst ].closed?
|
959
967
|
return if ( pack_id <= dst_ext[ :continue_src_pack_id ] ) || dst_ext[ :pieces ].include?( pack_id )
|
960
968
|
|
961
|
-
|
962
|
-
|
963
|
-
data = data[ 24..-1 ]
|
969
|
+
data = data[ 16..-1 ]
|
964
970
|
# puts "debug2 got pack #{ pack_id }"
|
965
971
|
|
966
972
|
if pack_id <= CONFUSE_UNTIL
|
data/lib/girl/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: girl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.67.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- takafan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-06-
|
11
|
+
date: 2020-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: while internet is evil, here's a girl.
|
14
14
|
email:
|