-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.xml
2309 lines (2305 loc) · 174 KB
/
index.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
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>firemiles 的个人博客</title><link>https://blog.firemiles.top/</link><description>记录工作,分享技术,积累成长</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>[email protected] (firemiles)</managingEditor><webMaster>[email protected] (firemiles)</webMaster><lastBuildDate>Sun, 22 May 2022 08:00:00 +0000</lastBuildDate><atom:link href="https://blog.firemiles.top/index.xml" rel="self" type="application/rss+xml"/><item><title>cgroup v2学习</title><link>https://blog.firemiles.top/2022/cgroupv2%E5%AD%A6%E4%B9%A0/</link><pubDate>Sun, 22 May 2022 08:00:00 +0000</pubDate><author>firemiles</author><guid>https://blog.firemiles.top/2022/cgroupv2%E5%AD%A6%E4%B9%A0/</guid><description><![CDATA[<h2 id="前言">前言</h2>
<p>这几年工作重心一直在容器网络方向,对虚拟化技术,容器隔离技术一直处于一知半解的状态,但是心里一直有着好奇,到底是怎样的技术,构建出了如今的容器化技术世界。项目中用到了cgroupV2,正好最近有时间,直接跳过cgroupV1学习了下cgroupV2的文档,这里分享内容主要来自<a href="https://arthurchiao.art/blog/cgroupv2-zh/" target="_blank" rel="noopener noreffer">官方权威文档中文翻译</a>。</p>
<h2 id="什么是cgroup">什么是cgroup</h2>
<p><code>cgroup</code> 是 “control group” 的缩写,并且首字母永远不大写。<code>cgroup</code> 是一种以 hierarchical (树形层级)方式组织进程的机制,以及在层级中以受控和可配置的方式分发系统资源。</p>
<ul>
<li>单数形式(cgroup)指这个特性,或用户 “cgroup controllers” 等术语中的修饰词</li>
<li>复数形式(cgroups) 显式地指多个cgroup</li>
</ul>
<p>cgroup 是 Linux 内核的一个功能,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。它最早由Google的工程师(主要是Paul Menage和Rohit Seth)在2006年发起,最早名称为进程容器(process containers)。在2007年时,因为在Linux内核中,容器(container)这个名词有许多不同的意义,为避免混乱,被重新命名为cgroup,并且被合并到2.6.24版的内核中去。</p>
<p>cgroups的一个设计目标是为不同的应用情况提供统一的接口,从控制单一进程(像nice)到作业系统层虚拟化(像OpenVZ,Linux-VServer,LXC)。cgroups 提供:</p>
<ul>
<li>资源限制:组可以被设置不超过设定的内存限制,包括虚拟内存。</li>
<li>优先级:一些组可能会得到大量的CPU或磁盘IO吞吐量。</li>
<li>结算:用来度量系统实际用了多少资源。</li>
<li>控制:冻结组或检查点和重启动。</li>
</ul>
<p>cgroup 主要由两部分组成:</p>
<ol>
<li><code>核心(core)</code>:主要负责层级化地组织进程;</li>
<li><code>控制器(controllers)</code>:大部分控制器负责cgroup层级中特定类型的系统资源分配,少部分utility控制器用于其他目的。</li>
</ol>
<h2 id="为什么会有cgroup">为什么会有cgroup</h2>
<p>在 Linux 里,一直依赖就有对进程进行分组的概念和需求,比如 <code>session group</code>, <code>progress group</code> 等,后来随着人们对这方面的需求越来越多,比如需要追踪一组进程的内存和IO 使用情况等,于是出现了cgroup,用来统一将进程进行分组,并在分组的基础上对进程进行监控和资源控制管理等。</p>
<p>在 <code>CentOS 7</code> 中,通过将cgroup层级系统与systemd单位树捆绑, 可以把资源管理设置从进程级别移至应用程序级别。默认情况下,systemd 会自动创建 <code>slice</code>、<code>scope</code>和<code>service</code>单位的层级,来为 cgroup 树提供统一结构,可以通过 <code>systemctl</code> 命令创建自定义slice进一步修改结构。</p>
<p></p>
<p>如上图所示,系统默认创建了3个顶级<code>slice</code>(<code>System</code>,<code>User</code>和<code>Machine</code>)。每个slice都会获得相同CPU使用时间,如果<code>user.slice</code>想要获得<code>100%</code>的CPU使用时间,而此时CPU比较空闲,那么<code>user.slice</code>就能如愿以偿。</p>
<h2 id="为什么会有cgroupv2">为什么会有cgroupV2</h2>
<p>cgroup v1 支持多个 hierarchy,每个hierarchy可以启用任意数量的controller。这种方式看上去高度灵活,但实际中并不是很有用。例如:</p>
<ol>
<li>utility 类型的 controller(例如freezer)本可用与多个hierarchy,而由于v1中每个controller只有一个实例,utility controller的作用就大打折扣;而hierarchy 一旦被populated之后,控制器就不能移动到其他hierarchy的事实,更是加剧了这个问题。</li>
<li>另一个问题是,关联到某个hierarchy的所有控制器,只能拥有相同的hierarchy视图。无法在controller粒度改变这种视图。</li>
</ol>
<p></p>
<p>在实际中,大部分hierarhchy都启动了所有控制器,而实际上只有联系非常紧密的控制器——例如cpu和cpuacct放到同一个hierarchy中才有意义。最终结果是:</p>
<ol>
<li>用户控件最后管理这多个非常类似的hierarchy</li>
<li>在执行hierarchy管理操作是,每个hierarchy上重复着相同的操作。</li>
</ol>
<h3 id="其他-cgroup-接口相关问题">其他 cgroup 接口相关问题</h3>
<p>v1的设计并没有前瞻性,因此后面引入了大量怪异特性和不一致性。</p>
<h2 id="cgroupv2的功能">cgroupV2的功能</h2>
<p>cgroup v2将多个hierarchy的方式变成了 unified hierarchy,并将所有的controller挂载到一个unified hierarchy。</p>
<p>当前kernel没有一处 cgroup v1版本,允许cgroup v1和v2共存,但是相同的controller不能同时mount到这两个不同的cgroup版本中。</p>
<blockquote>
<p>进程能否是同时属于cgroup v1和cgroup v2?可以的,查看 <code>/proc/$PID/cgroup</code> 会返现同时包含v1和v2 membership</p>
</blockquote>
<p>Linux 社区 cgroup 分享:</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/z7mgaWqiV90" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>
<h3 id="进程线程与-cgroup-关系">进程/线程与 cgroup 关系</h3>
<p>所有cgroup组成一个树形结构。</p>
<ul>
<li>系统中的每个进程都属于且只属于某一个cgroup;</li>
<li>一个进程的所有线程属于同一个cgroup;</li>
<li>创建子进程时,继承其父进程的cgroup;</li>
<li>一个进程可以迁移到其他cgroup;</li>
<li>迁移一个进程时,子进程(后台进程)不会自动跟着一起迁移;</li>
</ul>
<h3 id="控制器">控制器</h3>
<p>可以选择性地针对一个cgroup启用或禁用某些控制器;</p>
<p>控制器的所有行为是hierarchical的。</p>
<ul>
<li>如果一个cgroup启用了某个控制器,它的sub-hierarchy中所有进程都会受控制</li>
<li>如果在更接近的root的节点上设置了资源限制,那下面的sub-hierarchy是无法覆盖也,也就是sub-hierarchy受parent限制。</li>
</ul>
<h3 id="基础操作">基础操作</h3>
<h4 id="挂载">挂载</h4>
<div class="highlight"><div class="chroma">
<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">mount -t cgroup2 none <span class="nv">$MOUNT_POINT</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>cgroupv2 文件系统的magic number是<code>0x63677270</code>(“cgrp”)</p>
<ul>
<li>所有支持v2且未绑定到v1的控制器,会被自动绑定到v2的hierarchy,出现在root层级中。</li>
<li>v2中未使用的控制器也可以绑定到其他的hierarchies。</li>
</ul>
<p>这说明我们能以完全向后兼容的方式混用v2和v1 hierarchy。</p>
<h3 id="组织进程和线程">组织进程和线程</h3>
<h4 id="进程创建删除移动查看-cgroup">进程:创建/删除/移动/查看 cgroup</h4>
<p>初始状态下,只有root cgroup,所有进程都属于这个cgroup。</p>
<ul>
<li>创建sub-cgroup:只需要创建一个子目录
<div class="highlight"><div class="chroma">
<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">mkdir <span class="nv">$CGROUP_NAME</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<ul>
<li>
<p>将进程移动到指定cgroup:将PID写入到相应cgroup的cgroup.procs文件即可。</p>
<ul>
<li>每次<code>write(2)</code>只能迁移一个进程</li>
<li>如果进程有多个线程,那将任意线程PID写入到文件,都会将该进程的所有线程迁移到相应的cgroup。</li>
<li>如果进程fork出一个子进程,那子进程属于执行fork操作时父进程所属的cgroup。</li>
<li>进程退出(exit)后,仍然留在退出时它所属的cgroup,直到这个进程被收割(reap);</li>
<li>僵尸进程不会出现在cgroup.procs中,因此无法对僵尸进程执行迁移操作。</li>
</ul>
</li>
<li>
<p>删除cgroup/sub-cgroup</p>
<ul>
<li>如果一个cgroup 已经没有任何children或活进程,那直接删除对应的文件夹就删除该cgroup了</li>
<li>如果一个cgroup已经没有children,但是还有僵尸进程,也认为这个cgroup是空的,可以直接删除</li>
</ul>
</li>
<li>
<p>查看进程的cgroup信息:<code>cat /proc/$PID/cgroup</code> 会列出该进程的 cgroup membership。如果启用了v1,这个文件可能会包含多行,每个hierarchy一行。v2对应的行永远是0::$PATH格式:</p>
<div class="highlight"><div class="chroma">
<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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ cat /proc/<span class="nv">$$</span>/cgroup <span class="c1"># ubuntu 20.04 上的输出,$$ 是当前 shell 的进程 ID</span>
</span></span><span class="line"><span class="cl">12:devices:/user.slice
</span></span><span class="line"><span class="cl">11:freezer:/
</span></span><span class="line"><span class="cl">10:memory:/user.slice/user-1000.slice/session-1.scope
</span></span><span class="line"><span class="cl">9:hugetlb:/
</span></span><span class="line"><span class="cl">8:cpuset:/
</span></span><span class="line"><span class="cl">7:perf_event:/
</span></span><span class="line"><span class="cl">6:rdma:/
</span></span><span class="line"><span class="cl">5:pids:/user.slice/user-1000.slice/session-1.scope
</span></span><span class="line"><span class="cl">4:cpu,cpuacct:/user.slice
</span></span><span class="line"><span class="cl">3:blkio:/user.slice
</span></span><span class="line"><span class="cl">2:net_cls,net_prio:/
</span></span><span class="line"><span class="cl">1:name<span class="o">=</span>systemd:/user.slice/user-1000.slice/session-1.scope
</span></span><span class="line"><span class="cl">0::/user.slice/user-1000.slice/session-1.scope <span class="c1"># v2</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<h4 id="线程">线程</h4>
<ul>
<li>cgroup v2 的一部分控制器支持线程粒度的资源控制,这种控制器称为threaded controllers。
<ul>
<li>默认情况下,一个进程的所有线程属于同一个cgroup,</li>
<li>线程模型使我们能将不同线程放到subtree的不同位置,同时还能保持二者在同一个资源域(resource domain)内。</li>
</ul>
</li>
<li>不支持线程模型的控制器称为domain controllers。</li>
</ul>
<p>将cgroup改成threaded模式(单向/不可逆操作)
cgroup 创建之后都是domain cgroup,可以通过下面的命令将其改成threaded模式</p>
<div class="highlight"><div class="chroma">
<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"><span class="nb">echo</span> threaded > cgroup.type
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="控制器-1">控制器</h3>
<ul>
<li>CPU</li>
<li>Memory</li>
<li>IO</li>
<li>PID</li>
<li>Cpuset</li>
<li>Device Controller(基于cgroup BPF)</li>
<li>RDMA</li>
<li>HugeTLB</li>
<li>Misc
<ul>
<li>perf_event</li>
</ul>
</li>
</ul>
<h3 id="cgroup-命名控件cgroupns">cgroup 命名控件(cgroupns)</h3>
<p>容器环境中用cgroup和其他一些namespace来隔离进程,但是<code>/proc/$PID/cgroup</code> 文件可能会泄露潜在的系统层信息。例如:</p>
<div class="highlight"><div class="chroma">
<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">cat /proc/self/cgroup
</span></span><span class="line"><span class="cl">0::/batchjobs/container_id1 <span class="c1"># <-- cgroup 的绝对路径,属于系统层信息,不希望暴露给隔离的进程</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>因此引入了cgroup namespace。</p>
<h2 id="cgroupv2的实现">cgroupV2的实现</h2>
<h3 id="cgroup-控制接口">cgroup 控制接口</h3>
<p></p>
<p>Linux 使用了多种数据结构在内核中实现了cgroups的配置,关联了进程和cgroups节点,那么Linux又是如何让用户态进程使用cgroups的功能呢?Linux内核有一个很大的模块叫VFS(Virtual File System)。VFS能够把具体的文件系统细节隐藏起来,给用户态进程提供一个同一个为文件系统API接口。cgrpus与CFS之间衔接的部分称之为cgroup文件系统。</p>
<h2 id="cgroupv2的典型应用">cgroupV2的典型应用</h2>
<p>听说cgroup是从docker开始,也是容器技术带火了cgroup技术,这里简单介绍下容器如何使用cgroup。</p>
<p></p>
<p></p>
<p></p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/u8h0e84HxcE" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video"></iframe>
</div>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://www.wikiwand.com/zh-hans/Cgroups" target="_blank" rel="noopener noreffer">https://www.wikiwand.com/zh-hans/Cgroups</a></li>
<li><a href="https://arthurchiao.art/blog/cgroupv2-zh/" target="_blank" rel="noopener noreffer">https://arthurchiao.art/blog/cgroupv2-zh/</a></li>
<li><a href="https://segmentfault.com/a/1190000040980305" target="_blank" rel="noopener noreffer">https://segmentfault.com/a/1190000040980305</a></li>
<li><a href="https://juejin.cn/post/6844904110639054861" target="_blank" rel="noopener noreffer">https://juejin.cn/post/6844904110639054861</a></li>
</ul>
]]></description></item><item><title>WSL2安装内核头文件</title><link>https://blog.firemiles.top/2022/wsl2%E5%AE%89%E8%A3%85%E5%86%85%E6%A0%B8%E5%A4%B4%E6%96%87%E4%BB%B6/</link><pubDate>Sat, 23 Apr 2022 20:36:23 +0000</pubDate><author>firemiles</author><guid>https://blog.firemiles.top/2022/wsl2%E5%AE%89%E8%A3%85%E5%86%85%E6%A0%B8%E5%A4%B4%E6%96%87%E4%BB%B6/</guid><description><![CDATA[<h2 id="前言">前言</h2>
<p>一般 Linux 的发行版直接使用软件源就可以安装内核模块和内核头文件,但是WSL2作为一个特殊的版本,
大部分内核模块无法安装,也无法直接安装内核头文件,还有部分依赖内核头文件的工具也无法运行,本文主要介绍内核头文件的安装。</p>
<h2 id="安装">安装</h2>
<ul>
<li>
<p>第一步先去github上下载WSL2内核源码,仓库在 <a href="https://github.com/microsoft/WSL2-Linux-Kernel" target="_blank" rel="noopener noreffer">https://github.com/microsoft/WSL2-Linux-Kernel</a> 。下载当前运行的内核版本。这里以<code>4.19.121</code>为例</p>
</li>
<li>
<p>开始安装依赖</p>
</li>
</ul>
<div class="highlight"><div class="chroma">
<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-sh" data-lang="sh"><span class="line"><span class="cl">apt install libelf-dev build-essential pkg-config
</span></span><span class="line"><span class="cl">apt install bison build-essential flex libssl-dev libelf-dev bc
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>编译</li>
</ul>
<div class="highlight"><div class="chroma">
<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-sh" data-lang="sh"><span class="line"><span class="cl">tar -zvxf 4.19.121-microsoft-standard.tar.gz
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> WSL2-Linux-Kernel-4.19.121-microsoft-standard.tar.gz
</span></span><span class="line"><span class="cl">zcat /proc/config.gz > .config
</span></span><span class="line"><span class="cl">make -j <span class="k">$(</span>nproc<span class="k">)</span>
</span></span><span class="line"><span class="cl">make -j <span class="k">$(</span>nproc<span class="k">)</span> modules_install
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>编译过程中可能会出现一些报错,一般是还缺少部分依赖,通过Google可以找到解决办法。</p>
</blockquote>
<p>编译完成后,在 /lib/modules/ 会出现 4.19.121-microsoft-standard 目录,头文件就可以使用了,借助头文件,其他的内核模块也可以自行编译安装。</p>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://mashiro01.github.io/2020/08/24/wsl2%E4%B8%8B%E5%86%85%E6%A0%B8%E5%A4%B4%E6%96%87%E4%BB%B6/" target="_blank" rel="noopener noreffer">WSL2 下的 kernel header 的安装</a></li>
</ul>
]]></description></item><item><title>容器网络的怪问题</title><link>https://blog.firemiles.top/2020/%E5%AE%B9%E5%99%A8%E7%BD%91%E7%BB%9C%E7%9A%84%E6%80%AA%E9%97%AE%E9%A2%98/</link><pubDate>Sun, 19 Jul 2020 22:17:49 +0800</pubDate><author>firemiles</author><guid>https://blog.firemiles.top/2020/%E5%AE%B9%E5%99%A8%E7%BD%91%E7%BB%9C%E7%9A%84%E6%80%AA%E9%97%AE%E9%A2%98/</guid><description><![CDATA[<p>案例太多,一次写完有点太难为我,我就想到哪写到哪,保持更新。</p>
<h2 id="长连接报文重传失败">长连接报文重传失败</h2>
<p>有个客户来找,说他们的应用Server端日志里经常有长连接中断的消息,需要我们定位中断的原因。</p>
<p>接到这个问题时属实挠头,看了客户的错误日志,一般中断发生的频率极低,一天只出现个位数,我们尝试复现问题未果。但是客户就是上帝,接了问题单就要定位,我们的SRE比较有毅力,一连抓了3天的报文,根据报错日志,把有问题的报文挑了出来,我们进行分析。</p>
<p>通过wireshark工具分析TCP流,我们发现有问题的报文都是连接时间超过2小时没有报文,然后Server端突然发送了一个报文,但是客户端并没有收到这个报文,重发几次后连接就中断了。定位到这儿问题已经很明显了,客户没有使用TCP keepalive,导致连接长时间没报文,最后报文在发出去后在 VPC 中被丢了。</p>
<p>VPC 一般只会在安全组位置进行丢包,这很可能是安全组导致的丢包。查看了安全组规则,确实没有放通Server到Client端的规则,只放通了Client到Server的规则,也就是说只能由Client主动连接Server端,反之不行。</p>
<p>安全组的实现一般会使用连接跟踪表,当Client端发起连接,和Server端建立TCP连接时,连接跟踪表会记录这条连接,允许Client和Server在这条连接上传输数据。但是连接跟踪表是有超时时间的,一般就2小时,当两小时没有报文命中时,就老化掉该规则,这时Server端再发送报文时,就被安全组拒绝了。客户的问题应该就是这个原因。</p>
<p>这种情况一般建议客户开启TCP keepalive,这个问题并不是容器网络特有的问题,普通网络都会遇到。</p>
<h2 id="长连接出现连接拒绝">长连接出现连接拒绝</h2>
<p>容器网络还遇到一个客户来找我们,说他们POC测试的时候发现测试多轮大量长连接访问容器中的Server时会有一定几率出现Connect Refused错误。</p>
<p>接到问题后祭出tcpdump神器进行抓包,先简单描述下网络路径:</p>
<div class="highlight"><div class="chroma">
<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"> 访问NodePort DNAT成Pod IP SNAT成GW IP
</span></span><span class="line"><span class="cl">Client --------------> VM1 -----------> DNAT ------------> SNAT -------------> Server Pod
</span></span></code></pre></td></tr></table>
</div>
</div><p>我们抓两个位置的包 VM1 和 Server Pod,当出现Connect Refused时,Server Pod确实收到了报文,但是却回了一个RST报文。面对这种情况,头脑风暴分析了下可能的原因:</p>
<ol>
<li>nginx 有问题</li>
<li>连接数满了</li>
<li>连接被占用了</li>
</ol>
<ul>
<li>原因1不太可能</li>
<li>原因2我们查看了系统参数,连接数并没有达到上限,也排除</li>
<li>原因3我们在抓包的同时继续用netstat查看当前连接状态,发现当出现Connect Refusted报文时,果然连接仍然被占用着。</li>
</ul>
<p>为什么会连接冲突呢,问题肯定出在SNAT的过程中,SNAT挑选的源端口有问题。通过分析报文,突然发现这些长连接也有一个特点,连接建立后并没有数据的传输,只是一直占用的通道,看到这里心中已经对问题的根因有了大致的猜想,肯定和连接跟踪超时有关。查看了主机和容器的 <code>tcp_keepalive_time</code> 的值,果然主机配置了1800,容器是7200,不同的timeout值,使得主机在1800后释放了tcp连接,重新分配的源端口给新的连接,而容器并未释放,最终导致访问Server返回Connect Refused错误。</p>
<p>这种情况我们自然建议可以将主机的 <code>tcp_keepalive_time</code> 改回成 7200,和容器保持一致,基本避免了该错误的发生。</p>
]]></description></item><item><title>golang的reflect使用</title><link>https://blog.firemiles.top/2020/golang%E7%9A%84reflect%E4%BD%BF%E7%94%A8/</link><pubDate>Sun, 29 Mar 2020 09:30:51 +0000</pubDate><author>firemiles</author><guid>https://blog.firemiles.top/2020/golang%E7%9A%84reflect%E4%BD%BF%E7%94%A8/</guid><description><![CDATA[<div class="featured-image">
<img src="https://firemiles-blog.oss-cn-shanghai.aliyuncs.com/20200329093558.png" referrerpolicy="no-referrer">
</div><h2 id="reflect-介绍">reflect 介绍</h2>
<p>计算机科学中,反射(reflect) 指计算机程序在运行时(runtime)可以访问、检测和修改它本身状态或行为的一种能力。用比喻来说,反射就是程序在运行时能够“观察”并且修改自己的行为。</p>
<p>反射和内省(type introspection)不同,内省机制仅指程序在运行时对自身信息(称为元数据)的检测;反射机制不仅包括要能运行在对程序自身信息进行,还要能进一步根据这些信息改变程序状态或结构。</p>
<p>早期语言汇编包含反射能力,动态修改指令或对它们进行分析等等反射功能时很平常的。编程发展到如C语言等高抽象层次语言时,这种实践消失了,带有反射特性的高级编程语言要更晚出现。</p>
<p>golang 作为一个诞生较晚的现代语言,自然也支持反射能力。通过标准库 <code>reflect</code> 包我们可以使用 golang 提供的这个能力。</p>
<h2 id="golang-reflect-原则">Golang reflect 原则</h2>
<h3 id="interface-类型">interface 类型</h3>
<p>reflect 建立在类型系统上,我们先从 Go 的类型系统开始讲起。Go 是静态类型语言,所有的变量都有对应的静态类型,因此所有变量的类型在编译时就已经确定:<code>int</code>,<code>float32</code>,<code>*MyType</code>,<code>[]byte</code>等等。</p>
<p>如果我们定义</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">MyInt</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">i</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">j</span> <span class="nx">MyInt</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>那么 <code>i</code> 是 <code>int</code> 类型,而 <code>j</code> 是 <code>MyInt</code> 类型。两个变量拥有不同的静态类型,尽管它们的底层类型是一样的,但是它们之间的赋值必须通过显式的类型转换完成。</p>
<p>interface type 是一类非常重要的类型,它代表一些方法的集合。interface 变量能够存储所有实现了 interface 中的方法的变量。一个广为人知的例子是 <code>io.Reader</code> 和 <code>io.Writer</code>。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Reader is the interface that wraps the basic Read method.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Reader</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Read</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</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="c1">// Writer is the interface that wraps the basic Write method.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">type</span> <span class="nx">Writer</span> <span class="kd">interface</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nf">Write</span><span class="p">(</span><span class="nx">p</span> <span class="p">[]</span><span class="kt">byte</span><span class="p">)</span> <span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">err</span> <span class="kt">error</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><p>任何类型实现上述定义的 Read 或 Write 方法,我们认为该类型实现了 Reader 或 Writer 接口。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">r</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span>
</span></span><span class="line"><span class="cl"><span class="nx">r</span> <span class="p">=</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Stdin</span>
</span></span><span class="line"><span class="cl"><span class="nx">r</span> <span class="p">=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nf">NewReader</span><span class="p">(</span><span class="nx">r</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">r</span> <span class="p">=</span> <span class="nb">new</span><span class="p">(</span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// and so on
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>很重要的一点是,无论 <code>r</code> 的值是什么,<code>r</code> 的类型永远是 <code>io.Reader</code>,go 是静态类型,而 <code>r</code> 的静态类型是 <code>io.Reader</code> 。</p>
<p>空接口类型是一个十分重要的类型。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">interface</span><span class="p">{}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>它代表空方法的集合,它可以接收任意值,包括零值或有方法的值。</p>
<p>有些人可能认为Go的interface是动态类型,其实这是误解,它们是静态类型:interface 类型的变量永远是相同的静态类型,但是存储在interface中的值在运行时可以修改类型,该值要求实现interface定义的方法集合。</p>
<h3 id="interface-的细节">interface 的细节</h3>
<p>interface 类型的变量存储了一个对值:赋值给该interface的值;以及该值的类型。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">r</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span>
</span></span><span class="line"><span class="cl"><span class="nx">tty</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nf">OpenFile</span><span class="p">(</span><span class="s">"/dev/tty"</span><span class="p">,</span> <span class="nx">os</span><span class="p">.</span><span class="nx">O_RDWR</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">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="kc">nil</span><span class="p">,</span> <span class="nx">err</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">r</span> <span class="p">=</span> <span class="nx">tty</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>r</code> 存储了(value, type)对:(tty, *os.File)。需要注意 <code>*os.File</code> 实现了比 <code>Read</code> 更多的方法,但是使用 <code>Read</code> interface只能访问<code>Read</code> 中的方法。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">w</span> <span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span>
</span></span><span class="line"><span class="cl"><span class="nx">w</span> <span class="p">=</span> <span class="nx">r</span><span class="p">.(</span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>通过赋值给 <code>Write</code> 接口,我们可以访问 tty 中实现的 <code>Write</code> 中的方法。</p>
<p>我们还可以这样</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">empty</span> <span class="kd">interface</span><span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="nx">empty</span> <span class="p">=</span> <span class="nx">w</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>空的interface仍然包含了相同的pair, (tty, *os.File)。它的用处是:空的interface可以接收任意值并保留该值的所有信息。</p>
<p>这里不需要进行类型断言,因为 <code>Write</code> 接口满足空interface类型要求,而 <code>Read</code> 接口和 <code>Wirte</code> 接口的方法集并不匹配,我们需要显式进行类型断言,告诉编译器我们知道它们存储的值是满足接口的。</p>
<p>还有很重要的一点要记住:interface中存储的pair是(value, concrete type),并不是 (value, interface type)。所以interface不能保存interface 值。</p>
<h3 id="第一个法则-可以从-interface-值获取反射对象">第一个法则: 可以从 interface 值获取反射对象</h3>
<p>先说基本功能,反射是测试interface中存储的值和类型的机制。我们需要了解 <a href="https://golang.org/pkg/reflect/" target="_blank" rel="noopener noreffer">package reflect</a> 中的 <code>Type</code> 和 <code>Value</code>。这两个类型可以访问interface中的内容。<code>reflect.TypeOf</code> 和 <code>reflect.ValueOf</code> 用来从interface中获取 <code>reflect.Type</code> 和 <code>reflect.Value</code> 。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span> <span class="nx">main</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl"> <span class="s">"fmt"</span>
</span></span><span class="line"><span class="cl"> <span class="s">"reflect"</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kd">var</span> <span class="nx">x</span> <span class="kt">float64</span> <span class="p">=</span> <span class="mf">3.4</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"type:"</span><span class="p">,</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">TypeOf</span><span class="p">(</span><span class="nx">x</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="c1">// type: flot64
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>反射对象的第二个属性是 <code>Kind</code>, 它是底层类型,非静态类型,例如反射对象包含用户自定义类型的值:</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">MyInt</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">x</span> <span class="nx">MyInt</span> <span class="p">=</span> <span class="mi">7</span>
</span></span><span class="line"><span class="cl"><span class="nx">v</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// v.Kind() == reflect.Int
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>v</code> 的 <code>Kind</code> 是 <code>reflect.Int</code>,但是 x 的静态类型是 MyInt,不是int。</p>
<h3 id="第二个法则-可以从反射对象获取-interface-值">第二个法则: 可以从反射对象获取 interface 值</h3>
<p>获得一个<code>reflect.Value</code> 后,我们可以使用 <code>Interface()</code> 方法恢复 interface 值。该方法将type和value打包放入interface表达式中,并将它返回。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// Interface returns v's value as an interface{}.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="kd">func</span> <span class="p">(</span><span class="nx">v</span> <span class="nx">Value</span><span class="p">)</span> <span class="nf">Interface</span><span class="p">()</span> <span class="kd">interface</span><span class="p">{}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>之后你可以这么处理</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">y</span> <span class="o">:=</span> <span class="nx">v</span><span class="p">.</span><span class="nf">Interface</span><span class="p">().(</span><span class="kt">float64</span><span class="p">)</span> <span class="c1">// y will have type float64.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">y</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>我们还可以简化,<code>fmt.Println</code> 的参数是空 interface,他们在方法内部会做解包操作,因此我们只需要直接将interface传入。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nf">Interface</span><span class="p">())</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="第三个法则想要修改反射对象对象的指必须是settable的">第三个法则:想要修改反射对象,对象的指必须是settable的</h3>
<p>第三法则比较难懂,要结合法则一进行理解。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">x</span> <span class="kt">float64</span> <span class="p">=</span> <span class="mf">3.4</span>
</span></span><span class="line"><span class="cl"><span class="nx">v</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">v</span><span class="p">.</span><span class="nf">SetFloat</span><span class="p">(</span><span class="mf">7.1</span><span class="p">)</span> <span class="c1">// Error: will panic.
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>结果会 panic 并输出信息。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">panic: reflect.Value.SetFloat using unaddressable value
</span></span></code></pre></td></tr></table>
</div>
</div><p>错误的原因是 v 不是 addressable 的,Settable 是 reflect.Value 的一个属性,不是所有的 reflect.Value 都有该属性。</p>
<p><code>CanSet</code> 方法输出 settable 属性</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">x</span> <span class="kt">float64</span> <span class="p">=</span> <span class="mf">3.4</span>
</span></span><span class="line"><span class="cl"><span class="nx">v</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"settability of v:"</span><span class="p">,</span> <span class="nx">v</span><span class="p">.</span><span class="nf">CanSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="c1">// settability of v: false
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Settablity 是一个 bit 类似 addressability,但是更严格。这个属性表述反射对象可以修改创建该反射对象的原始变量的值。Settability 由反射对象是否持有原始对象决定。</p>
<p>当我们使用</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">x</span> <span class="kt">float64</span> <span class="p">=</span> <span class="mf">3.4</span>
</span></span><span class="line"><span class="cl"><span class="nx">v</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>反射对象只持有了 x 的 copy,并不是原始对象,因此</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">v</span><span class="p">.</span><span class="nf">SetFloat</span><span class="p">(</span><span class="mf">7.1</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>如果允许运行成功,那么它修改的也不是x,而是反射对象内部的一个值,这会让开发者产生疑惑。所以这并不被允许,settability 属性就是用来避免该问题。</p>
<p>如果我们想要修改原始对象,那么我们可以使用指针:</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">var</span> <span class="nx">x</span> <span class="kt">float64</span> <span class="p">=</span> <span class="mf">3.4</span>
</span></span><span class="line"><span class="cl"><span class="nx">p</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="o">&</span><span class="nx">x</span><span class="p">)</span> <span class="c1">// Note: take the address of x.
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"type of p:"</span><span class="p">,</span> <span class="nx">p</span><span class="p">.</span><span class="nf">Type</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"settability of p:"</span><span class="p">,</span> <span class="nx">p</span><span class="p">.</span><span class="nf">CanSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="c1">// type of p: *float64
</span></span></span><span class="line"><span class="cl"><span class="c1">// settability of p: false
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>这里 p 也是不可设置的,但是我们并不想修改 p,我们想要修改 p 指向的对象。我们可以使用 <code>Elem</code> 方法获取 p 指向的对象,该方法获取指针指向的元素,并保存成 <code>reflect.Value</code> 类型返回。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">v</span> <span class="o">:=</span> <span class="nx">p</span><span class="p">.</span><span class="nf">Elem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"settability of v:"</span><span class="p">,</span> <span class="nx">v</span><span class="p">.</span><span class="nf">CanSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="c1">// settability of v: true
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>在这里我们可以修改 x 了。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">v</span><span class="p">.</span><span class="nf">SetFloat</span><span class="p">(</span><span class="mf">7.1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">v</span><span class="p">.</span><span class="nf">Interface</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 7.1
</span></span></span><span class="line"><span class="cl"><span class="c1">// 7.1
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="结构体">结构体</h3>
<p>在我们前面的例子中,<code>v</code> 并不是指针本身,而是它派生出来的对象。这种方法的一个使用场景是修改结构体的字段。只有我们拥有结构体的地址,我们才可以修改它的字段。</p>
<p>下面是一个分析结构体值的例子。我们使用结构体变量的地址构建一个反射对象,这样我们可以通过该对象对它进行修改。</p>
<div class="highlight"><div class="chroma">
<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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">type</span> <span class="nx">T</span> <span class="kd">struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">A</span> <span class="kt">int</span>
</span></span><span class="line"><span class="cl"> <span class="nx">B</span> <span class="kt">string</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">t</span> <span class="o">:=</span> <span class="nx">T</span><span class="p">{</span><span class="mi">23</span><span class="p">,</span> <span class="s">"skidoo"</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="nx">s</span> <span class="o">:=</span> <span class="nx">reflect</span><span class="p">.</span><span class="nf">ValueOf</span><span class="p">(</span><span class="o">&</span><span class="nx">t</span><span class="p">).</span><span class="nf">Elem</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nx">typeOfT</span> <span class="o">:=</span> <span class="nx">s</span><span class="p">.</span><span class="nf">Type</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p"><</span> <span class="nx">s</span><span class="p">.</span><span class="nf">NumField</span><span class="p">();</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nx">f</span> <span class="o">:=</span> <span class="nx">s</span><span class="p">.</span><span class="nf">Field</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Printf</span><span class="p">(</span><span class="s">"%d: %s %s = %v\n"</span><span class="p">,</span> <span class="nx">i</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="nx">typeOfT</span><span class="p">.</span><span class="nf">Field</span><span class="p">(</span><span class="nx">i</span><span class="p">).</span><span class="nx">Name</span><span class="p">,</span> <span class="nx">f</span><span class="p">.</span><span class="nf">Type</span><span class="p">(),</span> <span class="nx">f</span><span class="p">.</span><span class="nf">Interface</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="c1">// 0: A int = 23
</span></span></span><span class="line"><span class="cl"><span class="c1">// 1: B string = skidoo
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>因为 <code>s</code> 是 settable 的,所以我们可以用它修改结构体字段。</p>
<div class="highlight"><div class="chroma">
<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-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">s</span><span class="p">.</span><span class="nf">Field</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="nf">SetInt</span><span class="p">(</span><span class="mi">77</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">s</span><span class="p">.</span><span class="nf">Field</span><span class="p">(</span><span class="mi">1</span><span class="p">).</span><span class="nf">SetString</span><span class="p">(</span><span class="s">"Sunset Strip"</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"t is now"</span><span class="p">,</span> <span class="nx">t</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">// t is now {77 Sunset Strip}
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="总结">总结</h3>
<p>再重复一遍三法则:</p>
<ol>
<li>可以从interface值获取反射对象</li>
<li>可以从反射对象获取interface值</li>
<li>想要修改反射对象,对像必须是settable</li>
</ol>
<p>一旦你理解这三个反射原则,那么Go就变得更容易使用了。反射是一个需要小心使用的强大工具,可以帮助你的代码减少不必要的重复。</p>
<p>下一次我将介绍 <code>encoding/json</code> 的实现,真正深入学习反射的使用。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://www.wikiwand.com/zh-hans/%E5%8F%8D%E5%B0%84_%28%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6%29" target="_blank" rel="noopener noreffer">https://www.wikiwand.com/zh-hans/%E5%8F%8D%E5%B0%84_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)</a></li>
<li><a href="https://golang.org/pkg/reflect/" target="_blank" rel="noopener noreffer">https://golang.org/pkg/reflect/</a></li>
<li><a href="https://blog.golang.org/laws-of-reflection" target="_blank" rel="noopener noreffer">https://blog.golang.org/laws-of-reflection</a></li>
<li><a href="https://colobu.com/2018/02/27/go-addressable/" target="_blank" rel="noopener noreffer">https://colobu.com/2018/02/27/go-addressable/</a></li>
<li><a href="https://studygolang.com/articles/938" target="_blank" rel="noopener noreffer">https://studygolang.com/articles/938</a></li>
<li><a href="https://research.swtch.com/interfaces" target="_blank" rel="noopener noreffer">Go Data Structures: Interfaces</a></li>
<li><a href="https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/" target="_blank" rel="noopener noreffer">https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-reflect/</a></li>
</ul>]]></description></item><item><title>DHCP协议</title><link>https://blog.firemiles.top/2020/dhcp%E5%8D%8F%E8%AE%AE/</link><pubDate>Mon, 24 Feb 2020 23:41:33 +0000</pubDate><author>firemiles</author><guid>https://blog.firemiles.top/2020/dhcp%E5%8D%8F%E8%AE%AE/</guid><description><![CDATA[<div class="featured-image">
<img src="https://firemiles-blog.oss-cn-shanghai.aliyuncs.com/20200224234329.png" referrerpolicy="no-referrer">
</div><h2 id="协议说明">协议说明</h2>
<p>DHCP 协议(动态主机设定协定)是一个用于局域网的网络协议,位于TCP/IP的应用层,使用UDP协议工作,主要有两个用途:</p>
<ul>
<li>用于内部网络和网络服务商自动分配IP给用户</li>
<li>用于内部网络管理员对所有电脑作中央管理</li>
</ul>
<p>DHCP 用一台或一组DHCP服务器来管理网络参数的分配,这种分配方式具有容错性。即使在一个仅拥有少量机器的网络中,DHCP仍然是有用的,因为一台机器可以不造成任何影响的被加入到网路中。</p>
<p>甚至对于很少改变地址的服务器,DHCP仍然被建议用来设置它们的地址。如果服务器需要被重新分配地址,尽可能不改变之前的配置。对于一些设备,如路由器和防火墙,则不应该使用DHCP。</p>
<p>DHCP 于 1993 年10月成为标准协议,前身是 BOOTP (Bootstrap Protocol, 引导程序协议),DHCP 被设计成向前兼容 BOOTP 协议。当前的DHCP定义可以在<a href="https://tools.ietf.org/html/rfc2131" target="_blank" rel="noopener noreffer">RFC 2131</a>找到,而基于IPv6的建议标准(DHCPv6)可以在<a href="https://tools.ietf.org/html/rfc3315" target="_blank" rel="noopener noreffer">RFC3315</a>中找到。</p>
<h2 id="协议结构">协议结构</h2>
<p></p>
<ul>
<li>Op:消息操作代码,既可以是引导请求(BOOTREQUEST 1)也可以是引导答复(BOOTREPLY 2)</li>
<li>Htype:硬件地址类型</li>
<li>Hlen:硬件地址长度</li>
<li>Hops:客户端设置为0,relay agent使用</li>
<li>Xid:处理ID</li>
<li>Secs:从获取到IP地址或者续约过程开始到现在所消耗的时间</li>
<li>Flags:标记</li>
<li>Ciaddr:客户机IP地址</li>
<li>Yiaddr:“你的”(客户机)IP地址</li>
<li>Siaddr:在bootstrap中使用的下一台服务器的IP地址</li>
<li>Giaddr:用于导入的接替代理IP地址</li>
<li>Chaddr:客户机硬件</li>
<li>Sname:任意服务器主机名称,空终止符</li>
<li>File:DHCP发现协议中的引导文件名、空终止符、属名或者空,DHCP供应协议中的受限目录路径名</li>
<li>Options:可选参数字段。参考定义选择列表中的选择文件</li>
</ul>
<h2 id="协议时序">协议时序</h2>
<div class="highlight"><div class="chroma">
<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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">sequenceDiagram
</span></span><span class="line"><span class="cl"> participant Server(not selected)
</span></span><span class="line"><span class="cl"> participant Client
</span></span><span class="line"><span class="cl"> participant Server(selected)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> Note over Client: Begins initialization
</span></span><span class="line"><span class="cl"> Client ->>+ Server(selected): DHCPDISCOVER
</span></span><span class="line"><span class="cl"> Client ->>+ Server(not selected): DHCPDISCOVER
</span></span><span class="line"><span class="cl"> Note over Server(selected): Determines configuration
</span></span><span class="line"><span class="cl"> Server(selected) ->>- Client: DHCPOFFER
</span></span><span class="line"><span class="cl"> Note over Server(not selected): Determines configuration
</span></span><span class="line"><span class="cl"> Server(not selected) ->>- Client: DHCPOFFER
</span></span><span class="line"><span class="cl"> Note over Client: Collects replies, Selects configuration
</span></span><span class="line"><span class="cl"> Client ->>+ Server(selected): DHCPREQUEST
</span></span><span class="line"><span class="cl"> Client ->> Server(not selected): DHCPREQUEST
</span></span><span class="line"><span class="cl"> Note over Server(selected): Commits configuration
</span></span><span class="line"><span class="cl"> Server(selected) ->>- Client: DHCPACK
</span></span><span class="line"><span class="cl"> Note over Client: Initialization complete
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> Note over Client: Graceful shutdown
</span></span><span class="line"><span class="cl"> Client ->> Server(selected): DHCPRELEASE
</span></span><span class="line"><span class="cl"> Note over Server(selected): Discards lease
</span></span></code></pre></td></tr></table>
</div>
</div><p>DHCP 客户端发送 DHCPDISCOVER 广播报文请求 DHCP 网络配置;</p>
<p>Server 端收到后应答 DHCPOFFER 配置客户端;客户端选择一个 Server 以及它提供的网络参数,填写 <code>server identifier</code> option 后广播 DHCPREQUEST 报文;</p>
<p>Server 接收到 DHCPREQUEST 报文,没被选中的 Sever 通过该报文确认 client 没有使用自己提供的配置,被选中的 Server 持久化客户端binding配置,并应答 DHCPACK 报文,DHCPACK 中的配置不能和之前的 DHCPOFFER 冲突,并且 Server 在该阶段并不检查提供的网络地址;</p>
<p>客户端接收到 DHCPACK 后,应该对参数作最后的检查(例如ARP检查获得的网络地址),并记录lease时间。如果客户端发现网络地址已经被占用了,客户端需要发送 DHCPDECLINE 消息给 Server 并等待至少10s后重启配置流程。</p>
<h2 id="dhcp-协议状态转移图">DHCP 协议状态转移图</h2>
<p></p>
<h2 id="dhcp-常用-option-字段">DHCP 常用 Option 字段</h2>
<h3 id="subnet-mask">Subnet Mask</h3>
<p>客户端子网掩码,如果subnet mask option和router option同时指定,subnet mask必须第一个。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Subnet Mask
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+
</span></span><span class="line"><span class="cl">| 1 | 4 | m1 | m2 | m3 | m4 |
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="router-option">Router Option</h3>
<p>指定客户端子网中的路由器IP列表。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Address 1 Address 2
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span><span class="line"><span class="cl">| 3 | n | a1 | a2 | a3 | a4 | a1 | a2 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="domain-name-server-option">Domain Name Server Option</h3>
<p>为客户端指定DNS列表。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Address 1 Address 2
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span><span class="line"><span class="cl">| 6 | n | a1 | a2 | a3 | a4 | a1 | a2 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="host-name-option">Host Name Option</h3>
<p>指定客户端名字。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Host Name
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span><span class="line"><span class="cl">| 12 | n | h1 | h2 | h3 | h4 | h5 | h6 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+--
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="domain-name">Domain Name</h3>
<p>指定客户端hostname的在DNS中记录的域名。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Domain Name
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+--
</span></span><span class="line"><span class="cl">| 15 | n | d1 | d2 | d3 | d4 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+--
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="interface-mtu-option">Interface MTU Option</h3>
<p>网卡MTU。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len MTU
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+
</span></span><span class="line"><span class="cl">| 26 | 2 | m1 | m2 |
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="static-route-option">Static Route Option</h3>
<p>指定客户端需要添加的静态路由。default route(0.0.0.0) 在静态路由中使用是非法的。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Destination 1 Router 1
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
</span></span><span class="line"><span class="cl">| 33 | n | d1 | d2 | d3 | d4 | r1 | r2 | r3 | r4 |
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
</span></span><span class="line"><span class="cl"> Destination 2 Router 2
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+---
</span></span><span class="line"><span class="cl">| d1 | d2 | d3 | d4 | r1 | r2 | r3 | r4 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+-----+-----+---
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="classless-route-option">Classless Route Option</h3>
<p>支持CIDR目的路由,如果客户端不支持,忽略该配置;如果客户端支持,当static route 和 classless route 同时配置时,忽略static route option。如果Server同时返回 classless route和router option,忽略router option。</p>
<p>很多客户端可能不支持该选项,Server 最好同时发送 classless route和router option,在 classless route和router option中同时指定默认网关。</p>
<p>当客户端请求classless route,同时请求static routes或routers option时,Server只需要发送classless route,不要发送static routes和routers option。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Destination 1 Router 1
</span></span><span class="line"><span class="cl">+-----+---+----+-----+----+----+----+----+----+
</span></span><span class="line"><span class="cl">| 121 | n | d1 | ... | dN | r1 | r2 | r3 | r4 |
</span></span><span class="line"><span class="cl">+-----+---+----+-----+----+----+----+----+----+
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Destination 2 Router 2
</span></span><span class="line"><span class="cl">+----+-----+----+----+----+----+----+
</span></span><span class="line"><span class="cl">| d1 | ... | dN | r1 | r2 | r3 | r4 |
</span></span><span class="line"><span class="cl">+----+-----+----+----+----+----+----+
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="server-identifier">Server Identifier</h3>
<p>DHCPOFFER 和 DHCPREQUEST 中用于指定 DHCP Server。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Address
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+
</span></span><span class="line"><span class="cl">| 54 | 4 | a1 | a2 | a3 | a4 |
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+-----+
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="client-identifier">Client-identifier</h3>
<p>客户端默认用硬件地址来作为ID,但是也可以用这个值作为ID,Server 可以使用这个id来存取客户端的配置。服务器可以用域名,虚拟机用机器UUID来设置该值。</p>
<div class="highlight"><div class="chroma">
<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-txt" data-lang="txt"><span class="line"><span class="cl">Code Len Type Client-Identifier
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+---
</span></span><span class="line"><span class="cl">| 61 | n | t1 | i1 | i2 | ...
</span></span><span class="line"><span class="cl">+-----+-----+-----+-----+-----+---
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://www.wikiwand.com/zh-hans/%E5%8A%A8%E6%80%81%E4%B8%BB%E6%9C%BA%E8%AE%BE%E7%BD%AE%E5%8D%8F%E8%AE%AE" target="_blank" rel="noopener noreffer">DHCP WIKI</a></li>
<li><a href="https://tools.ietf.org/html/rfc2131" target="_blank" rel="noopener noreffer"><strong>RFC2131</strong> Dynamic Host Configuration Protocol</a></li>
<li><a href="https://tools.ietf.org/html/rfc3315" target="_blank" rel="noopener noreffer"><strong>RFC3315</strong> Dynamic Host Configuration Protocol for IPv6 (DHCPv6)</a></li>
<li><a href="https://tools.ietf.org/html/rfc951" target="_blank" rel="noopener noreffer"><strong>RFC951</strong> BOOTSTRAP PROTOCOL (BOOTP)</a></li>
<li><a href="https://tools.ietf.org/html/rfc1542" target="_blank" rel="noopener noreffer"><strong>RFC1542</strong> Clarifications and Extensions for the Bootstrap Protocol</a></li>
<li><a href="https://tools.ietf.org/html/rfc1533" target="_blank" rel="noopener noreffer"><strong>RFC1533</strong> DHCP Options and BOOTP Vendor Extensions</a></li>
<li><a href="https://tools.ietf.org/html/rfc3442" target="_blank" rel="noopener noreffer"><strong>RFC3442</strong> The Classless Static Route Option for Dynamic Host Configuration Protocol (DHCP) version 4</a></li>
</ul>
<h2 id="附录">附录</h2>
<p>状态图源码</p>
<div class="highlight"><div class="chroma">
<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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-txt" data-lang="txt"><span class="line"><span class="cl">stateDiagram
</span></span><span class="line"><span class="cl"> [*] --> INIT
</span></span><span class="line"><span class="cl"> INIT --> SELECTING: -/Send DHCPDISCOVER
</span></span><span class="line"><span class="cl"> SELECTING --> SELECTING: DHCPOFFER/Collect replies
</span></span><span class="line"><span class="cl"> SELECTING --> REQUESTING: Select offerk/send DHCPREQUEST
</span></span><span class="line"><span class="cl"> REQUESTING --> REQUESTING: DHCPOFFER/Discard
</span></span><span class="line"><span class="cl"> REQUESTING --> INIT: DHCPNAK/Discard offer, DHCPACK (not accept.)/Send DHCPDECLINE
</span></span><span class="line"><span class="cl"> REQUESTING --> BOUND: DHCPACK/Record lease, set timers T1, T2