-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
271 lines (130 loc) · 164 KB
/
local-search.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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>【Linux】依赖Netfilter Hook编写自定义内核模块</title>
<link href="/2021/12/25/linux/kernel-module/"/>
<url>/2021/12/25/linux/kernel-module/</url>
<content type="html"><![CDATA[<h1 id="1-什么是内核模块"><a href="#1-什么是内核模块" class="headerlink" title="1.什么是内核模块"></a>1.什么是内核模块</h1><p>模块是可以根据需要加载和卸载到内核中的代码片段,通过动态挂载的方式可以扩展内核的功能,而无需重启系统。 例如,一种类型的模块是设备驱动程序,它允许内核访问连接到系统的硬件。如果没有内核模块,我们必须修改内核代码,将新功能代码片段兼容添加到内核源码中,然后重新编译内核镜像。<br>除了会让内核越来越大之外,还有一个缺点,即每次我们想要新功能时都需要我们重建和重启内核,并且一旦代码质量过低就会导致内核崩溃的严重事故。</p><blockquote><p>查看系统中的内核模块可以使用<code>lsmod</code>命令查看</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220825.png" alt="lsmod"></p><h1 id="2-编写一个简单的内核模块"><a href="#2-编写一个简单的内核模块" class="headerlink" title="2.编写一个简单的内核模块"></a>2.编写一个简单的内核模块</h1><p>下面代码段就是最简单的内核模块,当内核模块挂载成功,通过<code>printk()</code>打印出<code>Hello World!!!</code>,当内核模块卸载成功,通过<code>printk()</code>打印出<code>I am dead.</code>,键入<code>dmesg</code>就可以内核日志。</p><blockquote><p>编写内核模块还要关注当前的内核版本,不同版本某些函数的定义不同,会影响编译是否成功</p></blockquote><p>运行环境:</p><ul><li>Linux版本: CentOS 8.2</li><li>Kernel版本: 4.18.0-348.2.1.el8_5.x86_64</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/module.h></span> <span class="hljs-comment">// included for all kernel modules</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/kernel.h></span> <span class="hljs-comment">// included for KERN_INFO</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/init.h></span> <span class="hljs-comment">// included for __init and __exit macros</span></span><br><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> __init <span class="hljs-title function_">hello_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> printk(KERN_INFO <span class="hljs-string">"Hello World!!!\n"</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; <span class="hljs-comment">// Non-zero return means that the module couldn't be loaded.</span><br>}<br><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> __exit <span class="hljs-title function_">hello_cleanup</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> printk(KERN_INFO <span class="hljs-string">"I am dead.\n"</span>);<br>}<br><br>module_init(hello_init);<br>module_exit(hello_cleanup);<br></code></pre></td></tr></table></figure><ul><li><p><code>module_init()</code>和<code>module_exit()</code>分别为内核模块固定的初始化函数和退出清理函数</p></li><li><p><code>hello_init</code>和<code>hello_cleanup</code>分别为需要被加载的自定义初始化函数和退出函数,内部就是扩展功能代码,能够被加载到内核模块框架</p></li><li><p><code>printk()</code>是内核的日志记录函数,用于记录信息或发出警告,每个<code>printk()</code>都可以带有一个日志优先级,一共有8个优先级,内核有宏,可以在<code>linux/kernel.h</code>中查看宏定义。 如果未指定优先级,则将使用默认优先级<strong>DEFAULT_MESSAGE_LOGLEVEL</strong></p></li></ul><h1 id="3-如何编译内核模块"><a href="#3-如何编译内核模块" class="headerlink" title="3.如何编译内核模块"></a>3.如何编译内核模块</h1><p>编译内核模块需要依赖<strong>Makefile</strong>文件,编译成功后会输出一个后缀名为<code>ko</code>的文件,该文件就是我们编写的内核模块</p><ul><li><p>通过<code>insmod xxx.ko</code>挂载</p></li><li><p>通过<code>rmmod xxx</code>卸载</p></li></ul><p>Makefile文件格式如下:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 如果涉及多个文件,就添加多行并指定文件名称</span></span><br>obj-m += hello.o <br><br>all:<br> make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules<br><br>clean:<br> make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean<br></code></pre></td></tr></table></figure><p>保存文件后和内核模块c文件放置在同目录下,然后通过<code>make</code>命令就可以编译出内核模块</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221035.png" alt="编译模块"></p><p>挂载和卸载内核模块的效果:</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220510.png" alt="模块效果"></p><h1 id="4-解决编译过程的问题"><a href="#4-解决编译过程的问题" class="headerlink" title="4.解决编译过程的问题"></a>4.解决编译过程的问题</h1><p>执行<code>make</code>编译后出现<code>make: *** /lib/modules/4.18.0-348.2.1.el8_5.x86_64/build/: No such file or directory. Stop.</code> </p><p>该问题是系统没有安装内核开发包,可以看下<code>/usr/src/kernels/</code>,如果该目录下是空的,则可以说明没有安装内核开发包</p><p>安装内核开发包的命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 查找yum源支持的版本</span></span><br>yum list |grep kernel <br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 安装开发包</span></span><br>yum install kernel-devel.x86_64<br></code></pre></td></tr></table></figure><p>安装完成后,在<code>/lib/modules/4.18.0-348.2.1.el8_5.x86_64</code>目录下,通过<code>ls -l</code>查看<code>build</code>文件的链接,<br>如果没有指向<code>build -> ../../../../usr/src/kernels/4.18.0-348.2.1.el8_5.x86_64/</code>的链接则需要重新创建一个软链接</p><p>操作命令如下:<br><code>ln -s ../../../../usr/src/kernels/4.18.0-348.2.1.el8_5.x86_64/ build</code> </p><p>创建软链接完成后,重新编译就可以</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220542.png" alt="查看软链接"></p><h1 id="5-Netfilter-Hook"><a href="#5-Netfilter-Hook" class="headerlink" title="5.Netfilter Hook"></a>5.Netfilter Hook</h1><p>Netfiler框架是Linux防火墙的内核实现,常见的iptables和firewalld本质都是调用Netfilter来实现流量包的处理,另外还提供了hook机制来实现功能扩展,在内核里,每个网络命名空间(network namespace)都给<code>ipv4</code>、<code>ipv6</code>、<code>arp</code>、<code>bridge</code>、<code>decnet</code>等维持一个netfilter钩子列表<code>(struct nf_hook_entries *hooks[5])</code></p><p>netfilter提供5个hook点:</p><ul><li><code>NF_INET_PRE_ROUTING</code>:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测), 源地址转换在此点进行;</li><li><code>NF_INET_LOCAL_IN</code>:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;</li><li><code>NF_INET_FORWARD</code>:转发的包通过此检测点,FORWARD包过滤在此点进行;</li><li><code>NF_INET_LOCAL_OUT</code>:所有马上要通过网卡出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行;</li><li><code>NF_INET_POST_ROUTING</code>:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行。</li></ul><p>通过这些hook点,可以让我们注册回调函数,扩展流量包处理功能,实现类似NAT、包过滤、追踪记录等,包的走向路径图可以参考下图:</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220614.png" alt="netfilter"></p><h1 id="6-编写Netfiler-Hook函数"><a href="#6-编写Netfiler-Hook函数" class="headerlink" title="6.编写Netfiler Hook函数"></a>6.编写Netfiler Hook函数</h1><blockquote><p>目标:基于netfilter hook在<code>prerouting</code>和<code>postrouting</code>打印出当前的目的ip和目的port</p></blockquote><p>思路:</p><ol><li>编写hook函数</li><li>将hook函数注册到hook options结构体</li><li>调用<code>nf_register_net_hook</code>和<code>nf_unregister_net_hook</code>完成hook函数的加载和卸载</li></ol><p>Kernel版本为4.18的nf_hook函数定义为:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-keyword">typedef</span> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">nf_hookfn</span><span class="hljs-params">(<span class="hljs-type">void</span> *priv,</span><br><span class="hljs-params"> <span class="hljs-keyword">struct</span> sk_buff *skb,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> nf_hook_state *state)</span>;<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_ops</span> {</span><br> <span class="hljs-comment">/* User fills in from here down. */</span><br> nf_hookfn *hook;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">net_device</span> *<span class="hljs-title">dev</span>;</span><br> <span class="hljs-type">void</span> *priv;<br> <span class="hljs-type">u_int8_t</span> pf;<br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> hooknum;<br> <span class="hljs-comment">/* Hooks are ordered in ascending priority. */</span><br> <span class="hljs-type">int</span> priority;<br>};<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_state</span> {</span><br> <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> hook;<br> <span class="hljs-type">u_int8_t</span> pf;<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">net_device</span> *<span class="hljs-title">in</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">net_device</span> *<span class="hljs-title">out</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">sock</span> *<span class="hljs-title">sk</span>;</span><br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">net</span> *<span class="hljs-title">net</span>;</span><br> <span class="hljs-type">int</span> (*okfn)(<span class="hljs-keyword">struct</span> net *, <span class="hljs-keyword">struct</span> sock *, <span class="hljs-keyword">struct</span> sk_buff *);<br>};<br></code></pre></td></tr></table></figure><p>关注<code>nf_hook_ops</code>结构体的参数</p><ul><li>nf_hookfn: 自定义的nfhook函数</li><li>pf: 协议簇,主要种类列表<ul><li>PF_INET:ipv4协议</li><li>PF_INET6: ipv6协议</li><li>PF_ARP: arp协议</li><li>PF_BRIDGE: 二层网桥协议</li></ul></li><li>hooknum: 指定加载自定义函数的hook点位置</li><li>priority: 执行优先级<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">nf_ip_hook_priorities</span> {</span><br>NF_IP_PRI_FIRST = INT_MIN,<br>NF_IP_PRI_RAW_BEFORE_DEFRAG = <span class="hljs-number">-450</span>,<br>NF_IP_PRI_CONNTRACK_DEFRAG = <span class="hljs-number">-400</span>,<br>NF_IP_PRI_RAW = <span class="hljs-number">-300</span>,<br>NF_IP_PRI_SELINUX_FIRST = <span class="hljs-number">-225</span>,<br>NF_IP_PRI_CONNTRACK = <span class="hljs-number">-200</span>,<br>NF_IP_PRI_MANGLE = <span class="hljs-number">-150</span>,<br>NF_IP_PRI_NAT_DST = <span class="hljs-number">-100</span>,<br>NF_IP_PRI_FILTER = <span class="hljs-number">0</span>,<br>NF_IP_PRI_SECURITY = <span class="hljs-number">50</span>,<br>NF_IP_PRI_NAT_SRC = <span class="hljs-number">100</span>,<br>NF_IP_PRI_SELINUX_LAST = <span class="hljs-number">225</span>,<br>NF_IP_PRI_CONNTRACK_HELPER = <span class="hljs-number">300</span>,<br>NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,<br>NF_IP_PRI_LAST = INT_MAX,<br>};<br></code></pre></td></tr></table></figure></li></ul><blockquote><p>注意:上面列举的hook点和hook优先级都是指定ip层(三层),如果涉及到bridge还有二层的hook点和hook优先级</p></blockquote><p>涉及bridge的hook点和优先级</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">/* Bridge Hooks */</span><br><span class="hljs-comment">/* After promisc drops, checksum checks. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_PRE_ROUTING0</span><br><span class="hljs-comment">/* If the packet is destined for this box. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_LOCAL_IN1</span><br><span class="hljs-comment">/* If the packet is destined for another interface. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_FORWARD2</span><br><span class="hljs-comment">/* Packets coming from a local process. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_LOCAL_OUT3</span><br><span class="hljs-comment">/* Packets about to hit the wire. */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_POST_ROUTING4</span><br><span class="hljs-comment">/* Not really a hook, but used for the ebtables broute table */</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_BROUTING5</span><br><span class="hljs-meta">#<span class="hljs-keyword">define</span> NF_BR_NUMHOOKS6</span><br><br><span class="hljs-comment">// 优先级</span><br><span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">nf_br_hook_priorities</span> {</span><br>NF_BR_PRI_FIRST = INT_MIN,<br>NF_BR_PRI_NAT_DST_BRIDGED = <span class="hljs-number">-300</span>,<br>NF_BR_PRI_FILTER_BRIDGED = <span class="hljs-number">-200</span>,<br>NF_BR_PRI_BRNF = <span class="hljs-number">0</span>,<br>NF_BR_PRI_NAT_DST_OTHER = <span class="hljs-number">100</span>,<br>NF_BR_PRI_FILTER_OTHER = <span class="hljs-number">200</span>,<br>NF_BR_PRI_NAT_SRC = <span class="hljs-number">300</span>,<br>NF_BR_PRI_LAST = INT_MAX,<br>};<br></code></pre></td></tr></table></figure><ul><li>netfilter二层和三层之间的路径走向</li></ul><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220641.png" alt="netfiter路径"></p><h2 id="6-1-编写hook函数"><a href="#6-1-编写hook函数" class="headerlink" title="6.1 编写hook函数"></a>6.1 编写hook函数</h2><p><strong>prerouting hook 函数</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// prerouting hook function</span><br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">pre_hookfn</span><span class="hljs-params">(<span class="hljs-type">void</span> *priv,</span><br><span class="hljs-params"> <span class="hljs-keyword">struct</span> sk_buff *skb,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> nf_hook_state *state)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">iphdr</span> *<span class="hljs-title">iph</span> =</span> ip_hdr(skb);<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tcphdr</span> *<span class="hljs-title">tcph</span> =</span> tcp_hdr(skb);<br> printk(KERN_INFO <span class="hljs-string">"prerouting dest ip:port"</span>,ip->daddr,ntohs(tcph->dest));<br> <span class="hljs-keyword">return</span> NF_ACCEPT;<br>}<br></code></pre></td></tr></table></figure><p>**postrouting hook 函数 **</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">// postrouting hook function</span><br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">post_hookfn</span><span class="hljs-params">(<span class="hljs-type">void</span> *priv,</span><br><span class="hljs-params"> <span class="hljs-keyword">struct</span> sk_buff *skb,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> nf_hook_state *state)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">iphdr</span> *<span class="hljs-title">iph</span> =</span> ip_hdr(skb);<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tcphdr</span> *<span class="hljs-title">tcph</span> =</span> tcp_hdr(skb);<br> printk(KERN_INFO <span class="hljs-string">"postrouting dest ip:port"</span>,ip->daddr,ntohs(tcph->dest));<br> <span class="hljs-keyword">return</span> NF_ACCEPT;<br>}<br></code></pre></td></tr></table></figure><h2 id="6-2-注册option"><a href="#6-2-注册option" class="headerlink" title="6.2 注册option"></a>6.2 注册option</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_ops</span> <span class="hljs-title">in_nfho</span>;</span> <span class="hljs-comment">//net filter hook option struct</span><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_ops</span> <span class="hljs-title">out_nfho</span>;</span> <span class="hljs-comment">//net filter hook option struct</span><br><br><span class="hljs-comment">// nfhook init function</span><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">nf_init_fn</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> in_nfho.hook = pre_hookfn;<br> in_nfho.pf = PF_INET;<br> in_nfho.hooknum = NF_INET_PRE_ROUTING;<br> in_nfho.priority = NF_IP_PRI_FIRST;<br><br> out_nfho.hook = post_hookfn;<br> out_nfho.pf = PF_INET;<br> out_nfho.hooknum = NF_INET_POST_ROUTING;<br> out_nfho.priority = NF_IP_PRI_FIRST;<br><br> nf_register_net_hook(&init_net, &in_nfho);<br> nf_register_net_hook(&init_net, &out_nfho);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="6-3-完整代码程序"><a href="#6-3-完整代码程序" class="headerlink" title="6.3 完整代码程序"></a>6.3 完整代码程序</h2><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/module.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/kernel.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/ip.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/tcp.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><linux/netfilter_ipv4.h></span></span><br><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_ops</span> <span class="hljs-title">in_nfho</span>;</span> <span class="hljs-comment">//net filter hook option struct</span><br><span class="hljs-type">static</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">nf_hook_ops</span> <span class="hljs-title">out_nfho</span>;</span> <span class="hljs-comment">//net filter hook option struct</span><br><br><span class="hljs-comment">// prerouting hook function</span><br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">pre_hookfn</span><span class="hljs-params">(<span class="hljs-type">void</span> *priv,</span><br><span class="hljs-params"> <span class="hljs-keyword">struct</span> sk_buff *skb,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> nf_hook_state *state)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">iphdr</span> *<span class="hljs-title">iph</span> =</span> ip_hdr(skb);<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tcphdr</span> *<span class="hljs-title">tcph</span> =</span> tcp_hdr(skb);<br> printk(KERN_INFO <span class="hljs-string">"prerouting dest %pI4:%d\n"</span>,&iph->daddr,ntohs(tcph->dest));<br> <span class="hljs-keyword">return</span> NF_ACCEPT;<br>}<br><br><span class="hljs-comment">// postrouting hook function</span><br><span class="hljs-type">unsigned</span> <span class="hljs-type">int</span> <span class="hljs-title function_">post_hookfn</span><span class="hljs-params">(<span class="hljs-type">void</span> *priv,</span><br><span class="hljs-params"> <span class="hljs-keyword">struct</span> sk_buff *skb,</span><br><span class="hljs-params"> <span class="hljs-type">const</span> <span class="hljs-keyword">struct</span> nf_hook_state *state)</span><br>{<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">iphdr</span> *<span class="hljs-title">iph</span> =</span> ip_hdr(skb);<br> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">tcphdr</span> *<span class="hljs-title">tcph</span> =</span> tcp_hdr(skb);<br> <span class="hljs-comment">// iph->saddr = in_aton("1.1.1.1"); </span><br> printk(<span class="hljs-string">"postrouting dest %pI4:%d\n"</span>,&iph->daddr,ntohs(tcph->dest));<br> <span class="hljs-keyword">return</span> NF_ACCEPT;<br>}<br><br><span class="hljs-comment">// nfhook init function</span><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">nf_init_fn</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> in_nfho.hook = pre_hookfn;<br> in_nfho.pf = PF_INET;<br> in_nfho.hooknum = NF_INET_PRE_ROUTING;<br> in_nfho.priority = NF_IP_PRI_FIRST;<br><br> out_nfho.hook = post_hookfn;<br> out_nfho.pf = PF_INET;<br> out_nfho.hooknum = NF_INET_POST_ROUTING;<br> out_nfho.priority = NF_IP_PRI_FIRST;<br><br> nf_register_net_hook(&init_net, &in_nfho);<br> nf_register_net_hook(&init_net, &out_nfho);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br><br><span class="hljs-type">static</span> <span class="hljs-type">int</span> <span class="hljs-title function_">nfhook_init</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br> printk(<span class="hljs-string">"[+] Register tcp kernel module!\n"</span>);<br> <span class="hljs-keyword">return</span> nf_init_fn();<br>}<br><br><span class="hljs-type">static</span> <span class="hljs-type">void</span> <span class="hljs-title function_">nfhook_exit</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span><br>{<br><br> nf_unregister_net_hook(&init_net, &in_nfho);<br> nf_unregister_net_hook(&init_net, &out_nfho);<br> printk(KERN_INFO <span class="hljs-string">"[+] Unregister tcp kernel module!\n"</span>);<br>}<br><br>module_init(nfhook_init);<br>module_exit(nfhook_exit);<br></code></pre></td></tr></table></figure><p>挂载效果:</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328220703.png" alt="挂载效果"></p>]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>内核模块</tag>
</tags>
</entry>
<entry>
<title>【生产力】搭建个人云图床+PicGo管理</title>
<link href="/2021/11/21/tools/cloud-picture/"/>
<url>/2021/11/21/tools/cloud-picture/</url>
<content type="html"><![CDATA[<ul><li><p>写博客的时候常常需要上传图片,一般都需要一个称为图床的地方来存放图片</p></li><li><p>那么如果自己搞个免费的图床服务呢?利用Github和Gitee是一个好出路</p></li><li><p>除了提供存放的地方,我们还需要图床管理工具PicGo来帮助我们提高生产力</p></li></ul><span id="more"></span><h1 id="1-什么是图床"><a href="#1-什么是图床" class="headerlink" title="1.什么是图床"></a>1.什么是图床</h1><p>图床是一个存放图片并提供访问链接的地方,那么自然也可以是一个仓库,所以我们可以使用Github创建个人仓库来存放图片,然后通过Github服务器提供访问链接就可以实现自己的个人图床了,缺点就是Github在国内的访问速度有限,图片加载速度会比较慢,所以存放的地方可以从Github切换到Gitee或者直接使用七牛云,基本原理都是一样的。</p><p>另一个需要操作的地方就是需要生成服务访问令牌,保证我们能够远程操作存放图片的仓库,管理我们的图床的数据,然后就可以依赖一款叫<code>PicGo</code>的图床管理软件实现对图床的远程管理。</p><blockquote><p>当然,如果不想折腾,可以使用收费的图床服务,一般都会提供客户端管理工具或者web管理页面和存放服务,现买先用,免费的图床也有一个很受欢迎的就是<a href="https://sm.ms/">SM.MS</a>,注册用户后就能在提供的web管理页面进行图片上传,也提供了二次开发的API,也是不错的选择。</p></blockquote><h1 id="2-什么是PicGo"><a href="#2-什么是PicGo" class="headerlink" title="2.什么是PicGo"></a>2.什么是PicGo</h1><p>PicGo是一个用于快速上传图片并获取图片 URL 链接的工具,支持多种图床例如Github、Gitee、七牛云、腾讯云、又拍云、阿里云、sm.ms等等,并且支持插件扩展功能,甚至可以直接搭配Typora编辑器进行图片复制上传。</p><p>具体的介绍请参见Github仓库:<a href="https://github.com/Molunerfinn/PicGo">PicGo Github</a></p><p>软件下载地址:<a href="https://github.com/Molunerfinn/PicGo/releases">PicGo Releases</a></p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221335.png" alt="PicGo"></p><h1 id="3-利用Github搭建图床"><a href="#3-利用Github搭建图床" class="headerlink" title="3.利用Github搭建图床"></a>3.利用Github搭建图床</h1><p>首先,在Github中创建个人仓库,权限选用public</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221356.png" alt="创建Github仓库"></p><p>申请token,【Settings】-【Developer settings】-【Personal access tokens】-【Generate new token】,填写好描述,勾选【repo】,然后点击【Generate token】生成一个Token,注意这个Token只会显示一次,自己先保存下来,或者等后面配置好PicGo后再关闭此网页</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221428.png" alt="申请token"></p><p>另外,还要介绍一个Github图床访问加速的内容就是我们可以<strong>jsDelivr</strong>进行CDN加速,使用方法非常简单,即把图片链接地址的域名改为<strong>jsDelivr CDN</strong>的域名(cdn.jsdelivr.net/gh/)就可以。替换格式如下:</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs html">原图片访问地址:<br>https://github.com/<span class="hljs-tag"><<span class="hljs-name">你的github用户名</span>></span>/<span class="hljs-tag"><<span class="hljs-name">你的图床仓库名</span>></span>/图片的路径<br><br>CDN加速访问地址:<br>https://cdn.jsdelivr.net/gh/<span class="hljs-tag"><<span class="hljs-name">你的github用户名</span>></span>/<span class="hljs-tag"><<span class="hljs-name">你的图床仓库名</span>></span>/图片的路径<br></code></pre></td></tr></table></figure><p>那么,最后就是配置PicGo实现远程图片管理了,打开PicGo的【图床设置】—【Github图床】,填入上面准备好的配置信息,确定后就可以到【上传区】进行图片上传了,上传成功后能在【相册】中看到图片,同时登陆Github页面也能看到对应的图片数据。</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221451.png" alt="PicGo配置"></p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221514.png" alt="上传图片"></p><h1 id="3-利用Gitee搭建图床"><a href="#3-利用Gitee搭建图床" class="headerlink" title="3.利用Gitee搭建图床"></a>3.利用Gitee搭建图床</h1><p>Gitee的操作过程和Github是相似的,首先还是需要创建一个公开的仓库存放图片</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221702.png" alt="创建仓库"></p><p>创建成功后,申请token,点击【右上角头像】—【设置】—【私人令牌】—【生成新令牌】—【勾选project】—【提交】,然后复制保存好token,配置好PicGo再关闭就可以。</p><p><img src="C:/Users/Joyo/AppData/Roaming/Typora/typora-user-images/image-20211121144502255.png" alt="申请令牌"></p><p>接下来就是到PicGo中配置,默认不支持Gitee,需要安装扩展插件,打开【PicGo】—【插件设置】—【搜索gitee】—【安装gitee-uploader】,然后就会在图床列表中出现gitee的选项,点击进去就可以开始配置</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221539.png" alt="gitee插件"></p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221609.png" alt="Gitee配置"></p><p>最后上传图片,可以在gitee的页面查看到对应的图片,同时gitee仓库也有对应的数据。</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221636.png" alt="查看图片"></p><h1 id="5-Typora配置图床"><a href="#5-Typora配置图床" class="headerlink" title="5.Typora配置图床"></a>5.Typora配置图床</h1><p>Typora是一个Markdown编辑器,编辑内容时支持上传截图和图片,但是默认都是上传到本地,修改一下设置即可,【偏好设置】—【图像】,其中【插入图片时…】,选中<strong>上传图片</strong>,勾选<strong>对本地位置的图片应用上述规则</strong>和<strong>插入时自动转义图片URL</strong>,【上传服务设定】,上传服务选择使用<strong>PicGo app</strong>,PicGo路径选择本地安装路径,最后点击【验证图片上传选项】判断是否可以成功。成功后再到编辑器上编辑内容并上传图片就会直接上传到PicGo设置好的默认图床了。</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221724.png" alt="Typora设置"></p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20220328221748.png" alt="验证上传"></p>]]></content>
<categories>
<category>生产力</category>
</categories>
<tags>
<tag>图床</tag>
</tags>
</entry>
<entry>
<title>【计算机网络】区分tcp分段和ip分片</title>
<link href="/2021/09/16/network/tcp-ip-frame/"/>
<url>/2021/09/16/network/tcp-ip-frame/</url>
<content type="html"><![CDATA[<ul><li><p>tcp分段和ip分片很多人搞不清楚,事实上两者代表的内容并不相同</p></li><li><p>既然有了tcp分段为什么还要ip分片呢?tcp协议在网络上还会出现ip分片吗?</p></li><li><p>什么是链路MTU?什么又是MSS?MTU和MSS都是有什么关联?分段和分片究竟有没有可能同时发生?</p></li></ul><span id="more"></span><h1 id="1-什么是TCP分段?"><a href="#1-什么是TCP分段?" class="headerlink" title="1.什么是TCP分段?"></a>1.什么是TCP分段?</h1><p>在学习TCP/IP协议时都知道,TCP协议是基于连接和流的传输协议,当需要传输的TCP报文很大的时候就会发生分段的操作,所谓TCP分段就是将需要传输的大TCP包切割成n个适合链路传输的小段,然后这些小段就称之为TCP分段,这些TCP分段达到目的端可以进行重组,重组后就会构成原来的大TCP包。</p><p>既然都指定了是TCP分段,那么很明显,TCP分段的操作发生于传输层,分段的操作只会在TCP协议中发生,同样为传输层的协议,UDP协议不会出现分段操作,只会出现ip分片的行为,具体原因可以见下文介绍</p><h1 id="2-什么是IP分片"><a href="#2-什么是IP分片" class="headerlink" title="2.什么是IP分片"></a>2.什么是IP分片</h1><p>IP分片和分段行为不同,虽然也是对大数据包切割为n个小片,但是IP分片是工作在网络层,TCP协议和UDP协议的数据包在传输到网络层的时候均有可能发生IP分片的操作,但是IP分片和IP重组会影响数据传输的性能,所以一般情况基于TCP协议工作都会尝试避免IP分片。</p><p>这里你一定开始有了几个问题?</p><ul><li><p>什么情况会发生TCP分段和IP分片呢?</p></li><li><p>TCP协议如何避免IP分片?分段和分片会在TCP协议下同时工作吗?</p></li><li><p>UDP协议为什么没有分段操作,只有分片操作呢?</p></li></ul><p>那么,带着问题继续了解下面的内容,能找到你想要的答案!</p><h1 id="3-什么是MTU"><a href="#3-什么是MTU" class="headerlink" title="3.什么是MTU?"></a>3.什么是MTU?</h1><p>最大传输单元(Maximum Transmission Unit),即MTU,为数据链路层的最大载荷上限(即IP数据报最大长度),每段链路的MTU可能都不相同,一条端到端路径的MTU由这条路径上MTU最小的那段链路的MTU决定。</p><p>MTU是链路层中的网络对数据帧的一个限制,例如在以太网中,MTU通常为1500字节,如果采用巨帧(Jumbo Frame)时可以达到9000字节。所谓的MTU,是二层协议的一个限制,对不同的二层协议可能有不同的值,只有二层协议为以太网(Ethernet)时,MTU一般才取1500字节,注意它不是物理链路介质的限制,<strong>只有工作在二层的设备才需要指定MTU的值,如网卡、转发设备端口(统称为网络接口)等,通过同一段线缆直连的通信端口或网卡,其MTU值一定相同</strong>。</p><p>一个IP数据报在以太网中传输,如果它的长度大于当前链路MTU值,就要进行分片传输(这里指IP层分片),保证每片数据报的长度都不超过MTU。另外需要明确一点的是,分片传输的IP数据报不一定按序到达,乱序的IP分片根据IP首部中的信息,能让这些数据报片按序重组。IP数据报的分片与重组是在网络上层完成的。</p><h1 id="4-什么是MSS"><a href="#4-什么是MSS" class="headerlink" title="4.什么是MSS?"></a>4.什么是MSS?</h1><p>最大报文段长度(Maximum Segment Size),即MSS,为TCP传输层的最大载荷上限(即应用层数据最大长度),<strong>TCP三次握手期间通过TCP首部选项中的MSS字段通知对端,通常一条TCP连接的MSS取通信双方较小的那一个MSS值</strong>,与MTU的换算关系为:</p><p><code>MTU = MSS + TCP首部长度 + IP首部长度</code></p><p>故在以太网中(网络层以IPv4为例):</p><p><code>MSS = 以太网MTU - TCP首部长度 - IPv4首部长度 = 1500 - 20 - 20 = 1460字节</code></p><p>如果未指定MSS大小时默认值为<code>536字节</code>,这是因为在Internet中标准的MTU值为<code>576字节</code>,<code>576字节MTU = TCP首部长度20字节 + IPv4首部长度20字节 + 536</code>字节MSS。</p><p>一个应用程序如果要发送超过MSS大小的数据,就要进行分段传输(这里指TCP分段),使得每个报文段长度都不超过MSS。分片传输的TCP报文段也是不一定按序到达,但TCP协议实现了可靠的机制保证乱序的处理,即利用报文段序列号在接收缓冲区进行数据重排以实现重组。TCP分段的重组是在TCP传输层完成的。</p><p>正常情况下,IP分片的重组会影响传输的性能,所以在TCP协议下通过保证 <code>MSS<链路最小MTU</code>来避免分片的发生,当MSS总是比MTU小的话,说明每一个分段都可以直接通过传输,但是当MSS>链路最小MTU,超过了传输载荷上线,所以就需要对TCP分段进行IP分片操作,也就是需要对当前的TCP分段切割为多个适合链路MTU传输的IP分片,所以说,分段和分片是会在TCP协议下同时工作的,除非你能保证在TCP三次握手协商的MSS恒小于链路最小MTU</p><h1 id="5-分段和分片的发生时机"><a href="#5-分段和分片的发生时机" class="headerlink" title="5.分段和分片的发生时机"></a>5.分段和分片的发生时机</h1><p>由上面可知,分段会发生在面向连接、提供可靠传输服务而具备各种复杂机制的TCP协议,但是UDP是简单至极的传输协议,属于不可靠的协议,UDP协议并不会自行分段,也没有所谓的握手操作,当然也就是没有所谓的MSS大小的协商,UDP协议传输数据是如果最终的IP数据报的长度超过了MTU时,就直接交付网络层执行IP分片。同样,(没有分段功能的)ICMP数据在网络层中同样会出现IP分片的情况。</p><p>IP分片不仅会发生在在使用UDP、ICMP等没有分段功能的传输层协议的数据发送方,更还会发生在传输途中,甚至有可能都会发生,这是因为原本的大数据报被分片后很可能会经过不同MTU大小的链路,一旦链路MTU大于当前IP分片大小,则需要在当前转发设备(如路由器)中再次分片,但是各个分片只有到达目的地后才会在其网络层重组,而不是像其他网络协议,在下一跳就要进行重组。</p><ul><li><p>分段操作可以说只会发生在TCP协议,其他的协议当IP数据包长度超过MTU,就会触发IP分片的发生</p></li><li><p>TCP分段协商的MSS小于链路最小MTU的时候,TCP分段到达网络层也会发生IP分片的操作</p></li></ul><p>再次强调的点:</p><blockquote><p>在发送端进行TCP分段后就一定不会在IP层进行分片,因为MSS本身就是基于MTU推导而来,TCP层分段满足了MSS限制,也就满足了MTU的物理限制。</p></blockquote><blockquote><p>但在TCP分段发生后仍然可能发生IP分片,这是因为TCP分段仅满足了通信两端的MTU要求,传输路径上如经过MTU值比该MTU值更小的链路,那么在转发分片到该条链路的设备中仍会以更小的MTU值作为依据再次分片。</p></blockquote><blockquote><p>当然如果两个通信主机直连,那么TCP连接协商得到的MTU值(两者网卡MTU较小值)就是端到端的路径MTU值,故发送端只要做了TCP分段,则在整个通信过程中一定不会发生IP分片。</p></blockquote><p>例如下面展示的TCP三次握手:</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20210917212914.png" alt="TCP三次握手过程"></p><p>客户端的网卡MTU设置为1500,服务端网卡MTU设置为1480,中间设备的网卡mtu均设置为1200,当客户端和服务端建立TCP连接的时候,通过三次握手建立连接,建立连接的过程通过wireshake抓包可知:</p><ul><li><p>客户端发起SYN包告诉自己可以接受的<code>MSS=1460</code>,即1500-20TCP报文头-20IP报文头</p></li><li><p>服务端响应SYN+ACK包也告诉自己可以接受的<code>MSS=1440</code>,即1480-20TCP报文头-20IP报文头</p></li><li><p>这个属于MSS协商的过程,MSS最终就会取最小的MSS作为连接彼此之间传输的大小,也就是TCP分段大小携带的数据为1440为准</p></li><li><p>但是这个MSS=1440明显大于中间设备的MTU,所以,当出现大包传输的时候,<strong>虽然在C/S两段均只会出现TCP分段,但是在传输到中间设备的时候,通过比较MTU发现1440>1200,所以此时的TCP分段就会在中间设备发生IP分片,一个1440的TCP分段需要被切割为两个IP分片</strong>,然后进一步传输到目的端进行IP分片重组才能还原四层报文头,因为IP分片只有首片能访问到传输层的报文头,比如源和目的端口信息。</p></li></ul><blockquote><p>理论上的tcp分段在该拓补中是会发生IP分片的,但是当你实际搭建拓补并尝试抓包观察,你会发现mss的协商并不如上面所示,最终的mss大小也不是1440,而是1160,这是为什么呢?</p></blockquote><blockquote><p>其实,这属于mss的自我动态调整,是由Path MTU Discovery(路径mtu发现)机制提供的,它帮助我们发现初始协商的mss比链路中的mtu还大,因此,PMTU Discovery帮我们重新调整了合适的mss,保证mss < pmtu,避免了IP分片的发生,如果想要复现上面的实验,那你可以直接将Path MTU Discovery关闭,下一篇介绍如何搭建网络拓补使tcp分段基础下出现IP分片。因此,tcp分段和IP分片是有可能同时发生的!!!</p></blockquote><h1 id="6-TCP和UDP的分片对比"><a href="#6-TCP和UDP的分片对比" class="headerlink" title="6.TCP和UDP的分片对比"></a>6.TCP和UDP的分片对比</h1><p>TCP和UDP均为传输层的协议,最大的不同就是TCP是可靠的协议,具备超时重传等保证可靠传输的机制,而UDP是不可靠的协议,也没有所谓的握手和挥手的操作,因此UDP协议自然也没有TCP协议在三次握手时的MSS协商过程,当UDP传输的数据超过MTU的时候,完全交付网络层直接进行IP分片。</p><p>另外,IP分片的传输过程是可能发生丢失的,当IP分片出现丢失的时候,不会重传丢失的IP片,而是会重传一整组的IP分片,这也是IP分片被诟病性能差的原因之一,TCP协议上层实现了超时重传机制,当某一个TCP分段包因为IP分片丢失,可以通过TCP协议要求重传IP分片,从而保证数据可靠性传输。</p><p>对于UDP,用UDP协议发送64k的数据,那么如果网络发生了波动,丢失了某个IP分片,它不会反馈丢失了哪个分片给发送方的能力,这就意味着:64k的数据全都丢失了,如果需要重传,就得再次完整的传递这64K数据,因此一般使用UDP协议传输也会避免传输巨大数据,特意控制下单个包体的大小,从而提高传输效率。防止不可靠的性质导致频繁重传。</p><h1 id="7-IP报文解析"><a href="#7-IP报文解析" class="headerlink" title="7.IP报文解析"></a>7.IP报文解析</h1><p>当IP数据报的长度超过帧的MTU时,会被分片传输。分片可能发生在发送端,也可能发生在中转路由器上,而且可能在传输过程中被多次分片,但只有在最终的目标机器上,这些分片才会被内核中的IP模块重新组装。</p><p>IP头部中的三个字段给IP的分片和重组提供了足够的信息:<strong>数据报标识、标志和片偏移</strong>。</p><p>一个数据报的每个分片都具有自己的IP头部,它们具有相同的标识值,但具有不同的片偏移。并且除了最后一个分片外,其他分片都将设置MF标识。此外,每个分片的IP头部的总长度字段将被设置为该分片的长度。</p><blockquote><p>IPv4头部结构如下图,其长度通常为20字节,除非含有可变长的选项部分。</p></blockquote><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20210928232651.png" alt="IPV4的IP报文"></p><ul><li><p><strong>4位版本号(version)</strong>:指定IP协议的版本,对IPv4来说,值=4。</p></li><li><p><strong>4位头部长度(header length)</strong>:标志该IP头部有多少个32bit(4字节)。英文4位最大能表示15,所以IP头部最长位为60字节。</p></li><li><p><strong>8位服务类型(Type Of Service,TOS)</strong>:包括一个3位的优先权限字段(现在已经被忽略),4位的TOS字段和1位保留字段(必须置0)。4位的TOS字段分别表示:最小延时、最大吞吐量、最高可靠性和最小费用。一种最多有一个能置为1,应用程序应该根据实际需求来设置它。比如像ssh和telent这样的登录程序需要的是最小延时的服务,而文件传输程序ftp则需要最大吞吐量的服务。</p></li><li><p><strong>16位总长度(total length)</strong>:是指整个IP数据报的长度,以字节为单位,以此IP数据报的最大长度为65535字节。但由于MTU的限制,长度超过MTU的数据报将被分片传输,所以实际传输的IP数据报(或分片)的长度都远远没有达到这个最大值。接下来的3个字段则描述了如何实现分片。</p></li><li><p><strong>16位标志(identification)</strong>:唯一标志主机发送的每一个数据报。其初始值由系统随机生成,每发送一个数据报,其值就加1。该值在数据分片时,被复制到每个分片中,因此同一个数据报的所有分片都具有相同的标识值。</p></li><li><p>3位标识字段的第一位保留。第二位<code>Don't Fragement,DF</code>表示“禁止分片”。如果设置了这个位,IP模块将不对数据报进行分片。在这种情况下,如果IP数据报长度超过MTU的话,IP模块将丢弃该数据报并返回一个ICMP差错报文。第三位<code>More Fragment,MF</code>表示“更多分片”。除数据报的最后一个分片外,其他分片都要把它置1。</p></li><li><p><strong>13位分片偏移(fragmentation offset)</strong>:分片相对原始IP数据报开始处(仅指数据部分)的偏移。实际的偏移值时该值左移3位(乘8)后得到的。由于这个原因,除了最后一个IP分片外,每个IP分片的数据部分的长度必须是8的整数倍(这样才能保障后面的IP分片拥有一个合适的偏移值)。</p></li><li><p><strong>8位生存时间(Time To Live,TTL)</strong>:数据报到达目的地之前允许经过的路由器跳数。TTL值被发送端设置(常见的值是64).数据报在转发过程中没经过一个路由,该值就被路由器减1。当TTL值减为0时,路由器将丢弃数据报,并向源端发送一个ICMP差错报文。TTL值可以防止数据报陷入路由循环。</p></li><li><p><strong>8位协议(protocol)</strong>:区分上层协议。<code>/etc/protocls</code>文件定义了所有上层协议对应的protocol字段数值。其中,<code>ICMP=1,TCP=6,UDP=17</code>。</p></li><li><p><strong>16位头部校验和(header checksum)</strong>:由发送端填充,接收端对其使用CRC算法以检验IP数据报头部(注意,仅检验头部)在传输过程中是否损坏。<br>32位源端IP地址和目的端IP地址用来标识数据报的发送端和接收端。一般情况下,这两个地址在整个数据报的传递中保持不变,而不论它中间经过多少个中转路由器。</p></li><li><p><strong>选项字段(option)</strong>:可变长的可选信息。这部分最多包含40字节,因为IP头部最长是60个字节(包含前面讨论的20个字节的固定部分)。可用的IP选项包括:</p><ul><li><p>记录路由(record route):告诉数据报途径的所有的路由器都将自己的IP地址填入IP头部的选项部分,这个我们可以跟踪数据报的传递路径。<br>时间戳(timestamp),告诉每个路由器都将数据报被转发时的时间(或时间与IP地址对)填入IP头部选项部分,这个就可以测量途径路由之间的数据报传输的时间。</p></li><li><p>松散源路由选择(loose source routing),指定一个路由器IP地址列表,数据报发送过程中必须经过其中所有的路由器。</p></li><li><p>严格源路由选择(strict source routing),和松散源路由选择类似,不过数据报只经过被指定的路由器。</p></li></ul></li></ul><p>我们也可以直接最简单的ICMP协议对IP分片的报文进行进一步的了解:</p><p>以太网的MTU是1500字节,所以携带的IP数据报的数据部分最多是1480字节(IP头部占用20字节)。考虑用IP数据报封装一个长度为1481字节的ICMP报文(包括8字节的ICMP头部,所以其数据部分长度为1473字节),则该数据报在使用以太网传输时必须被分片,如下图所示:</p><p><img src="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20210917215624.png" alt="ICMP网络层报文"></p><p>IP分片的报文规律:</p><ul><li><p>同一组IP分片具备相同的<code>identification</code>,是一个无符号的16位整型,可表示65535个id,不代表唯一id</p></li><li><p>同一组的IP分片只有首片可以访问到传输层的报文头,中间片和尾片只有IP头</p></li><li><p>IP分片的区分规律:</p><ul><li><p><strong>非IP分片</strong>:<code>DF=1,MF=0,offset=0</code></p></li><li><p><strong>首片</strong>:<code>DF=0,MF=1,offset=0</code></p></li><li><p><strong>中间片</strong>:<code>DF=0,MF=1,offset为对应的偏移值</code></p></li><li><p><strong>尾片</strong>:<code>DF=0,MF=0,offset为对应的偏移值</code></p></li></ul></li></ul>]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>网络协议</tag>
</tags>
</entry>
<entry>
<title>【计算机网络】使用VirtualBox和namespace搭建桥接环境</title>
<link href="/2021/05/23/network/virtualbox-bridge/"/>
<url>/2021/05/23/network/virtualbox-bridge/</url>
<content type="html"><![CDATA[<ul><li><p>在工作中遇到需要使用Virtualbox基于Bridge搭建桥接环境,由此做个记录</p></li><li><p>主要是Virtualbox实现bridge桥接还需要一些黑手段,跟Vmware的LAN Segment还有些许不同</p></li><li><p>另外,基于namespace在一台虚拟机上搭建桥接环境的方法也是一种不错的方案</p></li></ul><span id="more"></span><p><img src="https://i.loli.net/2021/06/02/LbACyUODlzq7K5S.png" alt="本文大纲"></p><h1 id="1-桥接拓补介绍"><a href="#1-桥接拓补介绍" class="headerlink" title="1.桥接拓补介绍"></a>1.桥接拓补介绍</h1><p><img src="https://i.loli.net/2021/05/23/IiKh9P6Nmz4QTkv.png" alt="image.png"></p><p>如上图,使用Virtualbox创建三台Linux主机,然后主机A和主机C分别连接于主机B</p><p>主机B上创建bridge设备绑定无ip的双网卡,然后确保主机A和主机C能够网络互通</p><p>主机A和主机C的网卡分配1.1.1.0/24同一网段下的ip</p><h1 id="2-VirtualBox上分配网卡"><a href="#2-VirtualBox上分配网卡" class="headerlink" title="2.VirtualBox上分配网卡"></a>2.VirtualBox上分配网卡</h1><p>VirtualBox有一种虚拟网络叫做Internal Network,能提供内部的私有网络</p><p>1.首先,在VirtualBox中新建三台基于CentOS8的Linux主机,分配的网卡如下:</p><p>主机A</p><ul><li>名称:centos-1</li><li>网卡:enp0s3(桥接模式==>管理网卡,方便本地ssh连接)和enp0s8(内部网络模式==>界面名称为<code>intnet-1</code>)</li></ul><p>主机B</p><ul><li>名称:centos-2</li><li>网卡:enp0s3(桥接模式==>管理网卡,方便本地ssh连接)、enp0s8(内部网络模式==>界面名称为<code>intnet-1</code>)、enp0s9(内部网络模式==>界面名称为<code>intnet-2</code>)</li></ul><p>主机C</p><ul><li>名称:centos-3</li><li>网卡:enp0s3(桥接模式==>管理网卡,方便本地ssh连接)和enp0s8(内部网络模式==>界面名称为<code>intnet-2</code>)</li></ul><blockquote><p>注意:主机A和C到主机B的内部网络网卡,对应的界面名称在Virtualbox的选定要匹配起来,流量才能到达对应的网卡。</p></blockquote><p>2.登录Linux主机并分配网卡ip如下,管理网卡在此就省略不写,只要保证能够在本地ssh连接即可</p><p>主机A</p><p><code>ifconfig enp0s8 1.1.1.10/24</code></p><p>主机C</p><p><code>ifconfig enp0s8 1.1.1.20/24</code></p><p>主机B</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs linux">ip link add dev br0 type bridge<br>ip link set dev enp0s8 master br0<br>ip link set dev enp0s9 master br0<br>ifconfig br0 up<br></code></pre></td></tr></table></figure><p>3.验证网络连通</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">主机A:ping 1.1.1.20 ==> 不通<br>主机B:ping 1.1.1.10 ==> 不通<br></code></pre></td></tr></table></figure><p>此时在主机A和主机C分别进行互ping,发现并没有ping通,那这是什么问题呢?</p><h1 id="3-原理探究"><a href="#3-原理探究" class="headerlink" title="3.原理探究"></a>3.原理探究</h1><p>经过上面的问题,搜索相关的资料发现Virtualbox的独特性,<a href="https://blog.csdn.net/dog250/article/details/102972031">参考链接传送门</a></p><a href="https://blog.csdn.net/dog250/article/details/102972031," title="https://cdn.jsdelivr.net/gh/littlejoyo/cdn/img/20210614225658.png" target="">对Virtualbox注册型交换机解释,</a><p>总的来说,和VMWare的LAN Segment内部有一个虚拟学习型交换机(早期是广播式Hub) 不同, VirtualBox的Internal Network内部的交换机不是学习型的,而是注册型的!</p><p>所谓的注册型交换机就是,只有注册过的Mac地址,该交换机才会帮你转发!这类交换机只有两种转发策略</p><ul><li>对于广播帧,所有的端口都发一份。</li><li>对于单播帧,仅向注册该目标Mac地址的端口发送。</li></ul><p>那么交换机(内部网络网卡)是如何注册Mac地址的呢?或者说,从哪里可以看到一个虚拟机的网卡注册了哪个Mac呢?</p><p><img src="https://i.loli.net/2021/05/23/g1adHIfn2W3LtvA.png" alt="如何查看mac地址"></p><p>上面的实验不通的原因:H1 ping H2时,经由Linux Bridge,ARP请求可以广播到H2,此时H2收到的ARP广播中,源MAC自然是H1的MAC地址,当H2回复ARP Reply时,目标MAC为H1的MAC地址,而H1的MAC地址没有注册在intnet2的虚拟交换机中,所以intnet2的交换机不会转发这个帧。最终导致请求响应无法到达H1,因此而不通。</p><h1 id="4-如何注册virtualbox的网卡"><a href="#4-如何注册virtualbox的网卡" class="headerlink" title="4.如何注册virtualbox的网卡"></a>4.如何注册virtualbox的网卡</h1><p>注册步骤:</p><p>1.将centos-1上的intnet-1对应的MAC地址注册在centos-2上的intnet-2</p><p>2.将centos-3上的intnet-1对应的MAC地址注册在centos-2上的intnet-1</p><p>3.centos-2主机启动后,通过ifconfig命令将intnet-1和intnet-2对应enp0s9,enp0s10网卡MAC改成其他,防止冲突即可。</p><p><img src="https://i.loli.net/2021/05/29/EtKTkeacpdgGSAq.png" alt="image.png"></p><p>例如我的mac地址替换情况如下:</p><p>centos-1</p><ul><li>intnet-1 :<strong>080027431935</strong></li></ul><p>centos-2 </p><ul><li>intnet-1 :<strong>080027CADC26(原mac) –> 0800279DD011</strong></li><li>intnet-2 :<strong>0800270083A6(原mac) –> 080027431935</strong></li></ul><p>centos-3</p><ul><li>intnet-2 :<strong>0800279DD011</strong></li></ul><p><strong>centos-2替换后的地址如下图:</strong></p><p><img src="https://i.loli.net/2021/05/29/1vPRZmz6XYNydr9.png" alt="centos-2的网络配置"></p><blockquote><p>新版的virtualbox中mac地址拦可以直接在文本框,复制粘贴进行修改,旧版的有些不支持,呈现灰色状态,这个时候如果想修改可以使用<code>VBoxManage</code>命令指定特定网卡的MAC地址</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">VBoxManage modifyvm "centos-2" --macaddress4 080027431935<br>VBoxManage modifyvm "centos-2" --macaddress3 0800279DD011<br></code></pre></td></tr></table></figure><blockquote><p>最后再说明第三步的作用,之所以需要这一步,是防止地址冲突导致Bridge的转发表中毒</p></blockquote><ul><li>mac地址<code>080027431935</code>可以从centos-1的intnet-1学习到</li><li>mac地址<code>080027431935</code>被配置在自己的enp0s9上</li></ul><p>这会导致bridge的转发出现无法工作的错误,所以必须把centos-2的enp0s8,enp0s9这两个网卡的mac地址改成别的,而这个修改动作VirtualBox的Internal Network交换机并不知道(只有新的网卡激活或者重新注册新的MAC会改变转发表,即点击那个小按钮或者执行VBoxManage命令改变MAC地址),所以并不影响bridge的转发表。</p><blockquote><p>启动centos-2后的命令操作如下:</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 修改网卡的mac地址</span></span><br>ifconfig enp0s8 hw ether 08:00:27:9D:D0:12 <br>ifconfig enp0s9 hw ether 08:00:27:43:19:36<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 新增bridge设备,并加入网卡</span></span><br>ip link add dev br0 type bridge<br>ip link set dev enp0s8 master br0<br>ip link set dev enp0s9 master br0<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 启动网桥</span></span><br>ifconfig br0 up<br></code></pre></td></tr></table></figure><h1 id="5-验证桥接网络"><a href="#5-验证桥接网络" class="headerlink" title="5.验证桥接网络"></a>5.验证桥接网络</h1><p>此时再在centos-1和centos-3上进行互ping就无问题了,网络通了</p><p><img src="https://i.loli.net/2021/05/29/ivnVd43RGIuoX8E.png" alt="确保网络已通"></p><p>在centos-2中利用tcpdump成功抓取br0网口的网络包</p><p><img src="https://i.loli.net/2021/05/29/udkFYy9X8UAa5jo.png" alt="网桥抓包情况"></p><h1 id="6-了解namespace"><a href="#6-了解namespace" class="headerlink" title="6.了解namespace"></a>6.了解namespace</h1><h2 id="6-1-什么是namespace?"><a href="#6-1-什么是namespace?" class="headerlink" title="6.1 什么是namespace?"></a>6.1 什么是namespace?</h2><p>学习过docker底层实现的可能了解过docker是基于namespace和cgroup实现的,其中namespace 是 Linux 内核用来隔离内核资源的方式。</p><p>通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,他们之间保持着不可见的状态,也就是感觉不到对方的存在,但是实际却存在同一个Linux系统上</p><p>总结一下就是,Linux namespaces 是对全局系统资源的一种封装隔离,使得处于不同 namespace 的进程拥有独立的全局系统资源,改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程,对其他 namespace 中的进程没有影响</p><blockquote><p>注意:对于每个 network namespace 来说,它会有自己独立的网卡、路由表、ARP 表、iptables 等和网络相关的资源。</p></blockquote><h2 id="6-2-namespace的基本操作"><a href="#6-2-namespace的基本操作" class="headerlink" title="6.2 namespace的基本操作"></a>6.2 namespace的基本操作</h2><blockquote><p>基于来自于 iproute2 安装包的 <code>ip</code> 命令可以实现对namespace的操作</p></blockquote><ul><li>创建一个新的namespace,并命名为foo</li></ul><p><code>ip netns add foo</code></p><ul><li>查看Linux系统下存在的namespace列表</li></ul><p><code>ip netns ls</code></p><ul><li>在namespace中执行Linux命令</li></ul><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">ip netns exec foo `Linux 命令`<br><br>ip netns exec foo ip addr<br><br>ip netns exec foo ls -l<br></code></pre></td></tr></table></figure><blockquote><p>上面的方面都是在namespace外通过命令指定来执行,每一句都带有<code>ip netns exec foo</code>,有没有方法在namespace里面直接执行命令呢?答案是肯定的,通过末尾增加<code>bash</code>命令即可进入</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 通过指定bash命令进入namespace</span></span><br>ip netns exec foo bash<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 直接执行Linux命令</span></span><br>ip addr<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 退出namespace</span></span><br>exit<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 通过修改 bash 的前缀信息可以区分当前处在哪一个namespace</span></span><br>ip netns exec foo /bin/bash --rcfile <(echo "PS1=\"namespace foo> \"")<br></code></pre></td></tr></table></figure><ul><li>删除namespace</li></ul><p><code>ip netns del foo</code></p><h2 id="6-3-namespace的通信"><a href="#6-3-namespace的通信" class="headerlink" title="6.3 namespace的通信"></a>6.3 namespace的通信</h2><p>默认情况下,namespace是不能和主机网络,也不能和其他namespace通信的</p><p>每个 namespace 在创建的时候会自动创建一个<code>lo</code>的网卡,它的作用和Linux系统中默认看到的<code>lo</code>一样,都是为了实现<code>loopback</code>通信。如果希望 lo 能工作,不要忘记启用它:<br><code>ip netns exec net1 ip link set lo up</code></p><p>每个namespace都实现了网络的隔离,如果要使他们彼此之间通信起来就需要用Linux <code>veth peer</code>,可以把<code>veth peer</code>当做是全双工的管道,从一个方向发送的网络数据,可以使两个namespace实现网络数据的通信,<code>veth peer</code>也就是Linux下的虚拟网卡,namespace基于虚拟网卡实现了彼此之间的通信</p><p>可以使用<code>ip link add type veth</code>来创建一对<code>veth peer</code>出来,需要记住的是<code>veth peer</code> 无法单独存在,删除其中一个,另一个也会自动消失。另外,创建<code>veth peer</code>的时候也指定它们的名字,比如<code>ip link add dev vethfoo type veth peer name vethbar</code>创建出来的两个名字就是 <code>vethfoo</code>和<code>vethbar</code></p><p>创建了虚拟网卡后,接下来就是分别将虚拟网卡分配给对应的namespace实现网卡之间的通信,通过使用<code>ip link set 虚拟网卡名称 netns 命名空间名称</code>来实现,比如:<code>ip link set vethfoo netns foo</code></p><p>分配好网卡后再配置网卡ip地址和生成路由表就可以实现namespace之间的通信了,接下来看一个案例,如何实现两个namespace之间的通信</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 创建两个namespace</span></span><br>ip netns add foo <br>ip netns add bar<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 创建虚拟网卡对</span></span><br>ip link add dev vethfoo type veth peer name vethbar<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 为namespace分配网卡</span></span><br>ip link set vethfoo netns foo<br>ip link set vethbar netns bar<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 为namespace中的虚拟网卡分配ip地址并启动网卡</span></span><br>ip netns exec foo ip link set vethfoo up<br>ip netns exec foo ip addr add 1.1.1.10/24 dev vethfoo<br>ip netns exec bar ip link set vethbar up<br>ip netns exec bar ip addr add 1.1.1.20/24 dev vethbar<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 启动lo网卡</span></span><br>ip netns exec foo ip link set lo up<br>ip netns exec bar ip link set lo up<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 最后通过ping验证网络是否连通</span></span><br>ip netns exec foo ping 1.1.1.20 <br></code></pre></td></tr></table></figure><h1 id="7-使用bridge连接不同的namespace"><a href="#7-使用bridge连接不同的namespace" class="headerlink" title="7.使用bridge连接不同的namespace"></a>7.使用bridge连接不同的namespace</h1><p>通过上面掌握到的namespace知识,结合桥接的网络拓补,发现通过来实现bridge来实现多个namespace之间的通信也是无问题的,有了上面的知识铺垫,下面就直接写出具体的命令实现</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 创建三个namespace</span></span><br>ip netns add ns1<br>ip netns add ns2<br>ip netns add ns3<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 启动各个namespace的lo网卡</span></span><br>ip netns exec ns1 ip link set lo up<br>ip netns exec ns2 ip link set lo up<br>ip netns exec ns3 ip link set lo up<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 创建两对虚拟网卡对</span></span><br>ip link add dev vetha type veth peer name vethb1<br>ip link add dev vethc type veth peer name vethb2<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 分配虚拟网卡给对应的namespace</span></span><br>ip link set vetha netns ns1<br>ip link set vethb1 netns ns2<br>ip link set vethb2 netns ns2<br>ip link set vethc netns ns3<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 配置ns1和ns3的网卡ip并启动网卡</span></span><br>ip netns exec ns1 ip link set dev vetha up<br>ip netns exec ns3 ip link set dev vethc up<br>ip netns exec ns1 ip addr add 1.1.1.10/24 dev vetha<br>ip netns exec ns3 ip addr add 1.1.1.20/24 dev vethc<br><span class="hljs-meta prompt_"></span><br><span class="hljs-meta prompt_">#</span><span class="language-bash"><span class="hljs-comment"># 配置ns2中的bridge和绑定网卡,最后启动网卡</span></span><br>ip netns exec ns2 ip link add dev br0 type bridge<br>ip netns exec ns2 ip link set dev vethb1 master br0<br>ip netns exec ns2 ip link set dev vethb2 master br0<br>ip netns exec ns2 ip link set dev vethb1 up<br>ip netns exec ns2 ip link set dev vethb2 up<br>ip netns exec ns2 ip link set dev br0 up<br></code></pre></td></tr></table></figure><p>上面的命令集合基于namespace成功搭建了桥接的网络拓补,接下来就是在ns1和ns3进行互ping查看网络是否已经连通</p><h1 id="8-验证namespace桥接网络效果"><a href="#8-验证namespace桥接网络效果" class="headerlink" title="8.验证namespace桥接网络效果"></a>8.验证namespace桥接网络效果</h1><blockquote><p>配置后查看各个namespace里面的网卡情况</p></blockquote><ul><li>ns1和ns3的网卡情况</li></ul><p><img src="https://i.loli.net/2021/06/01/ktuISRBxbJUYaj8.png" alt="ns1和ns3的网卡情况"></p><ul><li>ns2的网卡情况</li></ul><p><img src="https://i.loli.net/2021/06/01/jhkqgbSTvM175mZ.png" alt="ns2的网卡情况"></p><blockquote><p>ns1和ns3互ping均成功</p></blockquote><p><img src="https://i.loli.net/2021/06/01/LuXFrEoDim5Gx7c.png" alt="ns1和ns3互ping均成功"></p>]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>网络</tag>
<tag>虚拟网络</tag>
</tags>
</entry>
<entry>
<title>【计算机网络】:一次性理清Virtualbox虚拟机网络模型</title>
<link href="/2021/03/13/network/virtoolbox-network/"/>
<url>/2021/03/13/network/virtoolbox-network/</url>
<content type="html"><![CDATA[<ul><li><p>平时我们基于VirtualBox或者VMware软件可以随意创建虚拟机</p></li><li><p>虚拟机的网络模型有多种,不理清它们之间的区别很容易出现网络不通的头痛问题</p></li><li><p>通过分析各种虚拟网络模型,解决虚拟机平台和物理机的网络连接问题</p></li></ul><span id="more"></span><p><img src="https://i.loli.net/2021/03/28/gvbq2rjDwyQCmlK.png" alt="image.png"></p><h1 id="1-VirrtualBox和VMware简单对比"><a href="#1-VirrtualBox和VMware简单对比" class="headerlink" title="1.VirrtualBox和VMware简单对比"></a>1.VirrtualBox和VMware简单对比</h1><p>两者都是优秀的虚拟机平台,我们可以通过它们创建虚拟机来安装不同环境的操作系统</p><p>主要区别对比:</p><ol><li><p>VirtualBox是开源免费软件,下载和安装比较方便,VMware功能更加强大,但是需要注册码</p></li><li><p>VirtualBox相对于VMware安装和配置更加简单,运行内存占用也比较小</p></li><li><p>VirtualBox由于是开源免费,社区相对更加活跃,但总体两者出现问题都能找到解决方案</p></li></ol><p>综上,本次主要采用VirtualBox来创建虚拟机进行演示,安装的操作系统镜像是Linux CentOS8.2版本</p><h1 id="2-常见的网络模型"><a href="#2-常见的网络模型" class="headerlink" title="2.常见的网络模型"></a>2.常见的网络模型</h1><p>主要有下面四种网络模型</p><ul><li>桥接(Bridge Adapter)</li><li>NAT</li><li>主机网络(Host-only Adapter)</li><li>内部网络(Internal)</li></ul><blockquote><p>VirtualBox包含了以上的四种网络模型,VMware只有前三种</p></blockquote><h1 id="3-桥接网络"><a href="#3-桥接网络" class="headerlink" title="3.桥接网络"></a>3.桥接网络</h1><h2 id="3-1-主要原理"><a href="#3-1-主要原理" class="headerlink" title="3.1 主要原理"></a>3.1 主要原理</h2><p>桥接网络模型依赖虚拟交换机(Linux bridge)将虚拟机和物理机连接起来,它们之间处在同一个二层网络</p><p>虚拟机和物理机的ip处在相同网段之下,举例,比如都处在192.168.100.x的网段之下,物理机ip为<strong>192.168.100.10</strong>,虚拟机ip为<strong>192.168.100.20</strong></p><blockquote><p>桥接网络的网络连通总结:</p></blockquote><ul><li><p>虚拟机之间彼此互通</p></li><li><p>虚拟机和物理机彼此互通</p></li><li><p>只要物理机能上外网,虚拟机也能上外网</p></li></ul><h2 id="3-2-网络原理图"><a href="#3-2-网络原理图" class="headerlink" title="3.2 网络原理图"></a>3.2 网络原理图</h2><p><img src="https://i.loli.net/2021/03/13/enFEHXuoCqVtiLg.png" alt="image.png"></p><p>如上图所示,物理主机内创建了三台虚拟机,然后通过<strong>bridge虚拟设备</strong>将物理网卡和虚拟网卡进行连接,虚拟机和物理机的ip都处在同一网段(192.168.100.x)之内</p><p>虚拟机借助物理机网卡实现外网的访问,物理机也可以ping通虚拟机,并且虚拟机之间网络是连通的,彼此之间可以进行访问</p><h2 id="3-3-配置方式"><a href="#3-3-配置方式" class="headerlink" title="3.3 配置方式"></a>3.3 配置方式</h2><p><img src="https://i.loli.net/2021/03/13/k6YSAvVEcCIrfQG.png" alt="image.png"></p><p>找到创建好的虚拟机,【设置】-【网络】,连接方式选择“桥接网卡”,界面名称选择正确的外网网卡,然后勾选“启动网络连接”即可</p><p>外网网卡名称可以在网络适配器查看是否匹配对应,例如就需要选择这个无线网卡虚拟机才能上外网</p><p><img src="https://i.loli.net/2021/03/13/qpkxshVfUSHdwE1.png" alt="image.png"></p><p><strong>验证网络连通情况</strong></p><ul><li>启动CentOS系统的虚拟机,输入<code>ip addr</code>查看网络设备地址,我这里虚拟网卡分配到的地址是<strong>192.168.123.21</strong></li></ul><p><img src="https://i.loli.net/2021/03/13/m1RFHyzesuwPGd9.png" alt="image.png"></p><p>查看本地的网卡地址是否也是属于<strong>192.168.123.x</strong>的网段下,打开cmd输入<code>ipconfig</code>命令查看,物理网卡的地址为<strong>192.168.123.67</strong>,符合理论</p><p><img src="https://i.loli.net/2021/03/13/btCkXow15dNKze2.png" alt="image.png"></p><p>通过ping命令验证物理机和虚拟机彼此之间互ping的情况,结果是双方都可以ping通</p><p><fancybox><img src="https://i.loli.net/2021/03/13/3A9T6dZ2lLQfhnJ.png" alt="image.png"></fancybox></p><blockquote><p>插入一个问题:可能会出现虚拟机ping主机不通的情况,一般是因为防火墙的问题,比如我物理机是win10的系统,就出现防火墙设置问题,问题主要是因为win10的防火墙没有打开<strong>ICMPv4-In</strong>这个规则,解决如下:</p></blockquote><p>1.依次打开【控制面板】-【Windows Definder防火墙】-【高级设置】-【入站规则】-【按配置文件筛选】-【按公有配置文件筛选】</p><p>2.找到并启用文件类型为“公用”的“文件和打印共享(回显请求 – ICMPv4-In)”规则,此时就可以ping通了</p><p><fancybox><img src="https://i.loli.net/2021/03/13/hoHsLpAQzgqYVbE.png" alt="image.png"></fancybox></p><p>3.最后是验证虚拟机之间是否可以彼此互ping,另起一台虚拟机,同样的方式设置为桥接模式,我这里分配到为<strong>192.168.123.52</strong>,如下所示,彼此之间也能ping通</p><p><fancybox><img src="https://i.loli.net/2021/03/13/zMThkCocxLGqjfW.png" alt="image.png"></fancybox></p><h1 id="4-NAT模式"><a href="#4-NAT模式" class="headerlink" title="4.NAT模式"></a>4.NAT模式</h1><h2 id="4-1-主要原理"><a href="#4-1-主要原理" class="headerlink" title="4.1 主要原理"></a>4.1 主要原理</h2><p>NAT网络模型基于NAT协议(Network Address Translation,网络地址转换),主要功能是能将IP数据报头中的IP地址转换为另一个IP地址的过程。</p><p>在实际应用中,NAT主要用于实现私有网络访问公共网络的功能。</p><p>NAT网络模型使得虚拟机的私有ip可以通过虚拟NAT设备转化为物理主机的ip,然后通过物理网卡实现外网的访问</p><p>需要注意的是Virtualbox中的NAT网络模型分为了<code>NAT</code>和<code>NAT网络</code>,两者的区别主要有:</p><ul><li><p><code>NAT</code>下的虚拟机之间是互相隔离的,彼此之间不能进行通信,也就是不能彼此互ping,因为它们各自分配着独立的虚拟NAT设备</p></li><li><p><code>NAT网络</code>下的虚拟机之间因为共享同一台虚拟NAT设备,彼此互通</p></li></ul><p>再回过来总结下NAT网络模型和物理主机的连通情况:</p><ul><li><p>虚拟机可以ping通物理机,但是物理主机无法ping通虚拟机</p></li><li><p>如果物理主机可以上外网,那么虚拟机就可以上外网</p></li></ul><blockquote><p>VirtualBox的NAT模型下,主机ping不通虚拟机,但是VMware经过实验验证是可以ping通的,既然无法ping通,那不是代表无法使主机ssh连接到虚拟机吗?在Virtualbox下有什么方法解决吗?方法还是有的:</p></blockquote><p><strong>方法一:端口映射</strong></p><p>虚拟机采用<code>NAT网络</code>配置,虚拟机的ip相对于宿主机是隐藏的。想要使得宿主机对虚拟机进行访问,需要配置端口映射</p><p>通过将宿主机的一个端口(localhost:port)与虚拟机的ssh端口进行连接,宿主机通过访问宿主机端口(localhost:port)实现对虚拟机ssh端口(ip:22)的访问</p><p>选择<code>NAT网络</code>模式,然后打开virtualbox的【全局设定】-【网络】,添加新的NAT网络,然后点击【端口映射】,然后新增端口映射的配置</p><p><img src="https://i.loli.net/2021/03/16/PQR9ZgxLbaplf7M.png" alt="image.png"></p><p>例如,这里我添加<strong>10.0.2.6</strong>的<strong>22</strong>端口映射到本地的5666端口,<strong>10.0.2.15</strong>的<strong>22</strong>端口映射到本地的<strong>5667</strong>端口</p><p><img src="https://i.loli.net/2021/03/16/9w41ongU2TZtLhb.png" alt="image.png"></p><p>确认好配置后,记得勾选NAT网络的启动网络选项,然后就可以实现主机ssh连接到虚拟机了</p><p><img src="https://i.loli.net/2021/03/16/eEhjSPgKtvsydQV.png" alt="image.png"></p><p><strong>方法二:新建主机网络</strong></p><p>通过给虚拟机添加新的网卡,然后选择模式为主机网络(Host-only Adapter),因为它能实现物理主机对虚拟机的访问,留在后面介绍。</p><h2 id="4-2-网络原理图"><a href="#4-2-网络原理图" class="headerlink" title="4.2 网络原理图"></a>4.2 网络原理图</h2><h3 id="4-2-1-NAT"><a href="#4-2-1-NAT" class="headerlink" title="4.2.1 NAT"></a>4.2.1 NAT</h3><p><img src="https://i.loli.net/2021/03/17/tIc7C9DZ8LygzXo.png" alt="image.png"></p><p>如上图,每台虚拟机都连接着独立的虚拟NAT设备,虚拟机之间彼此都是独立的,彼此之间不能进行通信</p><p>唯一的通信道路就是借着NAT设备和利用物理网卡来实现对外网的访问,也就是只要物理机能够上外网,那么虚拟机也就能上外网</p><h3 id="4-2-2-NAT网络"><a href="#4-2-2-NAT网络" class="headerlink" title="4.2.2 NAT网络"></a>4.2.2 NAT网络</h3><p><img src="https://i.loli.net/2021/03/17/X1rzsuQU8MAgVT3.png" alt="image.png"></p><p>如上图,NAT网络模式下虚拟机之间共享同一台虚拟NAT设备,也就是彼此之间都连接在同一网段下,彼此之间是可以实现通信的</p><p>与物理主机的通信方式就和NAT是一样的,通过物理网卡来实现对外网的通信 </p><h2 id="4-3-配置方式"><a href="#4-3-配置方式" class="headerlink" title="4.3 配置方式"></a>4.3 配置方式</h2><h3 id="4-3-1-NAT"><a href="#4-3-1-NAT" class="headerlink" title="4.3.1 NAT"></a>4.3.1 NAT</h3><p><code>NAT</code>模式的配置还是比较简单的,只需要启动网卡并选择模式为NAT即可,启动后会发现已经给虚拟机分配好ip</p><p><img src="https://i.loli.net/2021/03/18/lsPTRCKMAJS95Yw.png" alt="image.png"></p><p>查看ip,并尝试ping物理主机和外网,观察网络连通情况,如图</p><p><fancybox><img src="https://i.loli.net/2021/03/18/rysBbNQ7Uk93pq1.png" alt="image.png"></fancybox></p><p>另起一台虚拟机,新增网卡并配置为NAT模式,尝试两台虚拟机进行互ping,结果是彼此之间都无法ping通</p><p><fancybox><img src="https://i.loli.net/2021/03/18/P1DdmIcteuWkC8o.png" alt="image.png"></fancybox></p><h3 id="4-3-2-NAT网络"><a href="#4-3-2-NAT网络" class="headerlink" title="4.3.2 NAT网络"></a>4.3.2 NAT网络</h3><p>由上面的原理可知,<code>NAT网络</code>模式下是共享同一台虚拟NAT设备,所以需要新建一台共享的NAT设备,【全局设定】-【网络】-【添加新的NAT网络】</p><p>然后创建虚拟机的时候,新增网卡并选择<code>NAT网络</code>模式,界面名称选择刚才新建的<code>NatNetWork</code>即可</p><p><img src="https://i.loli.net/2021/03/18/dRrjEgsXPZkJBFv.png" alt="image.png"></p><p>网络情况基本上跟上面保持一致,ping主机和外网都没问题</p><p><img src="https://i.loli.net/2021/03/18/uUWR28VCDgJ59KQ.png" alt="image.png"></p><p>唯一的区别,另起一台虚拟机,同样模型选<code>NAT网络</code>,然后彼此之间这一次是可以ping通的</p><p><fancybox><img src="https://i.loli.net/2021/03/18/ghfyrQZ9E5GTW3M.png" alt="image.png"></fancybox></p><h1 id="5-主机网络"><a href="#5-主机网络" class="headerlink" title="5.主机网络"></a>5.主机网络</h1><h2 id="5-1-主要原理"><a href="#5-1-主要原理" class="headerlink" title="5.1 主要原理"></a>5.1 主要原理</h2><p>主机网络(Host-only Adapter)是指只限于主机内部访问的网络,虚拟机之间彼此互通,虚拟机与主机之间彼此互通。但是默认情况下虚拟机不能访问外网</p><p>主机网络模型会在主机中模拟出一块虚拟网卡供虚拟机使用,然后基于该网络下的所有虚拟机都会连接到这块网卡上</p><p>这块网卡可以在网络适配器中查看,具体的ip和网关可以在上面进行自定义设置</p><p><img src="https://i.loli.net/2021/03/18/ITbkDJ2SY1qU4Gu.png" alt="image.png"></p><p>主机网络的网卡是支持随意增删的,打开virtualbox的【管理】——【主机网络管理器】创建新的网卡如下,相应会在本地的网络设配器中新增对应的网卡</p><p>需要注意的是对应的ip段需要关注,新建的虚拟机需要处在同一网段下才能进行网络通信</p><p><img src="https://i.loli.net/2021/03/19/S1PT7qGhc8o5x2H.png" alt="image.png"></p><blockquote><p>总结一下,主机网络的网络连通情况</p></blockquote><ul><li><p>主机和虚拟主机彼此可以实现互通</p></li><li><p>虚拟机之间彼此互通</p></li><li><p>通过进一步配置,只要主机能上网,虚拟机也能上网</p></li></ul><h2 id="5-2-网络原理图"><a href="#5-2-网络原理图" class="headerlink" title="5.2 网络原理图"></a>5.2 网络原理图</h2><p><img src="https://i.loli.net/2021/03/19/WtRaE8fwuShJx1b.png" alt="image.png"></p><ul><li>主机网络下的虚拟机都会连接在全局虚拟网卡,然后它们的ip地址属于同一网段下,虚拟机借助物理网卡可以实现外网的访问</li></ul><h2 id="5-3-配置方式"><a href="#5-3-配置方式" class="headerlink" title="5.3 配置方式"></a>5.3 配置方式</h2><p>新建虚拟机,新增网卡并选择【仅主机(Host-Only网络)】,界面名称选择对应的虚拟网卡,然后记住该网卡分配的ip段,比如我这里的虚拟网卡ip段是<strong>192.168.137.1/24</strong></p><p><img src="https://i.loli.net/2021/03/19/7e1Yl5h63QSMHxy.png" alt="image.png"></p><p>启动虚拟机后查看虚拟机分配到的ip地址是<strong>192.168.137.56</strong>,尝试虚拟机和物理机主彼此互ping,都能ping成功,如果出现了<strong>主机能ping虚拟机,但是虚拟机无法ping主机</strong>的问题,还是跟上面桥接网络一样关闭windows的防火墙来解决</p><p><fancybox><img src="https://i.loli.net/2021/03/19/xBnQ6dhyleREptI.png" alt="image.png"></fancybox></p><p>需要注意的是,上面虚拟机ping外网也成功,但是你可能会遇到无法ping通外网的问题,如何解决呢?</p><blockquote><p>主机网络如何配置可以让虚拟机访问外网?</p></blockquote><p>打开【网络连接】,然后找到物理主机的上网网卡,右键属性,再选【共享】,勾选允许其他网络用户连接,然后【家庭网络连接】项目,选择共享的网卡为上面配置的主机网络的网卡,最后确定和重启虚拟机就可以实现外网的访问</p><p><img src="https://i.loli.net/2021/03/19/as1e6VpPRYtgbFo.png" alt="image.png"></p><h1 id="6-内部网络"><a href="#6-内部网络" class="headerlink" title="6.内部网络"></a>6.内部网络</h1><ul><li><p>内部网络模式是相对比较简单的一种,虚拟机与外部环境完全断开,一般是用来做内部环境搭建测试</p></li><li><p>通过配置相同网段,允许虚拟机之间互相访问,如果彼此之间的网段并不一致,是无法互相访问的</p></li><li><p>在后面,会再输出一篇关于<strong>利用bridge虚拟设备实现虚拟机通信</strong>的文章,到时候就会用到该模式的网卡搭建实验环境</p></li></ul><p><img src="https://i.loli.net/2021/03/19/arG1emVoSpkl65W.png" alt="image.png"></p><blockquote><p>这里你可能有一个问题,主机无法访问内部网络,那有没有方法实现主机对虚拟机进行ssh,因为如果在内部网络下有多台虚拟机,操作起来实在不够方便</p></blockquote><ul><li>很简单,虚拟机是支持配置多网卡的,可以新增一张网卡,然后使用上面其他任意一种方式来实现物理主机对虚拟机的访问,那么不就可以实现ssh连接了吗?</li></ul>]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>网络</tag>
<tag>虚拟网络</tag>
</tags>
</entry>
<entry>
<title>【计算机网络】:OSI七层模型和各种协议介绍</title>
<link href="/2020/04/10/network/network-osi/"/>
<url>/2020/04/10/network/network-osi/</url>
<content type="html"><![CDATA[<ul><li><p>了解OSI模型可以更清晰掌握数据在网络互联的各个层次的传输过程</p></li><li><p>OSI七层模型也是面试热点问题,从中可以引申出大量的相关知识</p></li><li><p>另外,各层次的代表协议和硬件、TCP四层模型你是否都掌握了解呢?</p></li></ul><span id="more"></span><blockquote><p>Github issues:<a href="https://github.com/littlejoyo/Blog/issues/">https://github.com/littlejoyo/Blog/issues/</a></p></blockquote><h1 id="1-什么是OSI七层模型?"><a href="#1-什么是OSI七层模型?" class="headerlink" title="1.什么是OSI七层模型?"></a>1.什么是OSI七层模型?</h1><blockquote><p>维基百科介绍OSI模型:</p></blockquote><p>开放式系统互联模型(英语:Open System Interconnection Model,缩写:OSI;简称为OSI模型)是一种概念模型,由国际标准化组织提出,一个试图使各种计算机在世界范围内互连为网络的标准框架。定义于ISO/IEC 7498-1。</p><p><strong>概念不重要,最重要是理解好各层次的含义和对应的典型协议应用。</strong></p><p>OSI七层协议自上而下分为:</p><ul><li><p>应用层</p></li><li><p>表示层</p></li><li><p>会话层</p></li><li><p>传输层</p></li><li><p>网络层</p></li><li><p>数据链路层</p></li><li><p>物理层</p></li></ul><h1 id="2-OSI模型各层介绍"><a href="#2-OSI模型各层介绍" class="headerlink" title="2.OSI模型各层介绍"></a>2.OSI模型各层介绍</h1><p><img src="https://i.loli.net/2020/04/11/jC4gQM79mev2kGY.png" alt="osi"></p><h2 id="物理层"><a href="#物理层" class="headerlink" title="物理层"></a>物理层</h2><ul><li><p>物理层通过媒介传输比特,确定机械及电气规范,如集线器;</p></li><li><p>作用:为上层协议提供了一个传输数据的物理媒体</p></li><li><p>协议数据单元(PDU):<strong>比特Bit</strong></p></li><li><p>硬件代表:网卡,网线,集线器,中继器,调制解调器</p></li></ul><h2 id="数据链路层"><a href="#数据链路层" class="headerlink" title="数据链路层"></a>数据链路层</h2><ul><li><p>数据链路层在不可靠的物理介质上提供可靠的传输。</p></li><li><p>该层的作用包括:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等</p></li><li><p>协议数据单元(PDU):<strong>帧Frame</strong></p></li><li><p>硬件代表:网桥,交换机</p></li></ul><h2 id="网络层"><a href="#网络层" class="headerlink" title="网络层"></a>网络层</h2><ul><li><p>这层对端到端的包传输进行定义,定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。</p></li><li><p>为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。</p></li><li><p>作用:网络层负责对子网间的数据包进行路由选择。还可以实现拥塞控制、网际互连等功能。</p></li><li><p>协议数据单元(PDU):<strong>数据包packet</strong></p></li><li><p>硬件代表:路由器</p></li></ul><blockquote><p>网络层通过寻址(寻址地址)来建立两个节点之间的连接,我们的电脑连接上网络后都会有一个IP地址,我们可以通过IP地址来确定不同的计算机是否在同一个子网路。当我们的电脑连接上网络后就有两种地址:<strong>物理地址(MAC)</strong>和<strong>网络地址(IP地址)</strong>,网络上的计算机要通信,必须要知道通信的计算机“在哪里”, 首先通过网络地址来判断是否处于同一个子网络,然后再对物理地址(MAC)地址进行处理,从而来确定要通信计算机的位置。</p></blockquote><h2 id="传输层"><a href="#传输层" class="headerlink" title="传输层"></a>传输层</h2><ul><li><p>通过网络层让我们建立主机与主机的通信,而传输层的功能就是建立端口到端口的通信通道,这样就可以实现程序之间的数据通信。</p></li><li><p>作用:提供可靠和尽力而为的传输;</p></li><li><p>协议数据单元(PDU):<strong>段Fragment</strong></p></li><li><p>代表:网关</p></li></ul><blockquote><p>通过MAC和IP地址,帮助我们找到互联网上任意两台主机来建立通信。然而这里有一个问题,找到主机后,主机上有很多程序都需要用到网络,比如说你在一边听歌和使用QQ聊天, 当网络上发送来一个数据包时, 该怎么知道它是表示聊天的内容还是歌曲的内容的, 这时候就需要一个参数来表示这个数据包是发送给那个程序(进程)来使用的,这个参数我们就叫做<strong>端口号</strong>,主机上用端口号来标识不同的程序(进程),端口是0到65535之间的一个整数,<strong>0到1023的端口被系统占用,用户只能选择大于1023的端口</strong>。</p></blockquote><h2 id="会话层"><a href="#会话层" class="headerlink" title="会话层"></a>会话层</h2><ul><li><p>会话层定义了如何开始、控制和结束一个会话,以便在只完成连续消息的一部分时可以通知应用,从而使表示层看到的数据是连续的,在某些情况下,如果表示层收到了所有的数据,则用数据代表表示层。示例:RPC,SQL等。</p></li><li><p>作用:控制会话,建立管理终止应用程序会话;</p></li><li><p>协议数据单元(PDU):<strong>会话协议数据单元SPDU</strong></p></li><li><p>代表:SSH、RPC(远程过程调用)、SQL、RTCP(实时传输控制协议)</p></li></ul><h2 id="表示层"><a href="#表示层" class="headerlink" title="表示层"></a>表示层</h2><ul><li><p>表示层定义了数据格式及加密。例如,FTP允许你选择以二进制或ASII格式传输。如果选择二进制,那么发送方和接收方不改变文件的内容。如果选择ASII格式,发送方将把文本从发送方的字符集转换成标准的ASII后发送数据。在接收方将标准的ASII转换成接收方计算机的字符集。</p></li><li><p>作用:对数据提供加密、压缩、格式转换服务</p></li><li><p>协议数据单元(PDU):<strong>表示协议数据单元PPDU</strong></p></li><li><p>代表:ASCII, JPEG. PNG, MP3, WAV, AVI</p></li></ul><h2 id="应用层"><a href="#应用层" class="headerlink" title="应用层"></a>应用层</h2><ul><li><p>应用层是模型中的最顶层,是用户与网络的接口,该层通过应用程序来完成网络用户的应用需求。该层的数据放在TCP数据包的数据部分,该层定义了一个很重要的协议——Http协议,一般的Web开发都是基于应用层的开发。</p></li><li><p>作用:控制应用程序,为应用程序提供网络服务。</p></li><li><p>协议数据单元(PDU):<strong>应用协议数据单元APDU</strong></p></li><li><p>代表:Telnet, HTTP, FTP, SMTP</p></li></ul><h1 id="3-TCP-IP四层模型"><a href="#3-TCP-IP四层模型" class="headerlink" title="3.TCP/IP四层模型"></a>3.TCP/IP四层模型</h1><p>TCP/IP协议簇是一个四层协议系统,可以说是OSI模型的简化,结构如下图:</p><p><img src="https://i.loli.net/2020/04/11/XI89dtubHhYyOcU.png" alt="TCP/IP"></p><p><strong>应用层:</strong></p><p>应用程序间沟通的层,如简单电子邮件传输(SMTP)、文件传输协议(FTP)、网络远程访问协议(Telnet)等。 </p><p><strong>传输层:</strong></p><p>提供了节点间的数据传送服务,如传输控制协议(TCP)、用户数据报协议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。</p><p><strong>网络层:</strong></p><p>负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收),如网际协议(IP)。</p><p><strong>网络接口层:</strong></p><p>对实际的网络媒体的管理,定义如何使用实际网络(如Ethernet、Serial Line等)来传送数据。</p><h2 id="数据解包和装包"><a href="#数据解包和装包" class="headerlink" title="数据解包和装包"></a>数据解包和装包</h2><p><img src="https://i.loli.net/2020/04/11/UiKHAZr4qMQYkF2.png" alt="数据解包和装包"></p><p><img src="https://i.loli.net/2020/04/11/Mq2c8VCTgURXPY6.png" alt="xxx"></p><h1 id="4-OSI七层模型各层代表协议"><a href="#4-OSI七层模型各层代表协议" class="headerlink" title="4.OSI七层模型各层代表协议"></a>4.OSI七层模型各层代表协议</h1><ul><li><p>物理层:RJ45、CLOCK、IEEE802.3 </p></li><li><p>数据链路:PPP、FR、HDLC、VLAN、MAC </p></li><li><p>网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP</p></li><li><p>传输层:TCP、UDP、SPX</p></li><li><p>会话层:NFS、SQL、NETBIOS、RPC</p></li><li><p>表示层:JPEG、MPEG、ASII、MP4</p></li><li><p>应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS</p></li></ul><h1 id="5-TCP-IP协议介绍"><a href="#5-TCP-IP协议介绍" class="headerlink" title="5.TCP/IP协议介绍"></a>5.TCP/IP协议介绍</h1><blockquote><p>TCP、UDP、IP协议在此不介绍,另起文章细究。</p></blockquote><h2 id="ICMP协议"><a href="#ICMP协议" class="headerlink" title="ICMP协议"></a>ICMP协议</h2><ul><li><p>ICMP协议,即因特网控制报文协议。</p></li><li><p>它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息。</p></li><li><p>属于网络层协议,ping命令就是ICMP协议的应用。</p></li></ul><blockquote><p>ping程序是用来探测主机到主机之间是否可通信,如果不能ping到某台主机,表明不能和这台主机建立连接。ping使用的是ICMP协议,它发送icmp回送请求消息给目的主机。ICMP协议规定:目的主机必须返回ICMP回送应答消息给源主机。如果源主机在一定时间内收到应答,则认为主机可达。</p></blockquote><h2 id="NAT协议"><a href="#NAT协议" class="headerlink" title="NAT协议"></a>NAT协议</h2><ul><li><p>网络地址转换属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术。</p></li><li><p>NAT将私有IP地址通过边界路由转换成外网IP地址,在边界路由的NAT地址转换表记录下这个转换映射记录,当外部数据返回时,路由使用NAT技术查询NAT转换表,再将目标地址替换成内网用户IP地址。</p></li><li><p>NAT协议属于网络层协议。</p></li></ul><blockquote><p>基于IPv4的IP地址只有32位,最多只有42.9亿个地址,还要去掉保留地址、组播地址,能用的地址只有36亿左右,但是当下有数以万亿的主机,没有这么多IP地址可以对应,虽然现在有了IPv6,但是当下IPv4还是主流,利用IPv4怎么满足这么多主机的IP地址呢?答案就是NAT,NAT技术使公司、机构以及个人产生以及局域网,然后在各个局域网的边界WAN端口使用一个或多个公网的IPv4进行一对多转换</p></blockquote><h2 id="DHCP协议"><a href="#DHCP协议" class="headerlink" title="DHCP协议"></a>DHCP协议</h2><ul><li><p>动态主机配置协议,DHCP(Dynamic Host Configuration Protcol)</p></li><li><p>一种让系统得以连接到网络上,并获取所需要的配置参数手段,基于UDP协议工作。</p></li><li><p>具体用途:给内部网络或网络服务供应商自动分配IP地址,给用户或者内部网络管理员作为对所有计算机作中央管理的手段。</p></li><li><p>DHCP协议属于网络层协议。</p></li></ul><blockquote><p>一个主机如何获取IP地址?有两种获取方法,一种是静态配置,就是从网络管理员获取一个给定的IP地址,也叫硬编码,属于静态IP,还有一种就是动态配置IP地址,也就四基于DHCP协议进行动态IP配置。</p></blockquote><h2 id="ARP协议"><a href="#ARP协议" class="headerlink" title="ARP协议"></a>ARP协议</h2><ul><li>地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议。</li></ul><p><strong>工作流程:</strong></p><ol><li><p>通过DNS域名解析到主机的IP地址,应用程序发送请求通过TCP或者UDP使用得到的IP建立连接。</p></li><li><p>当主机知道IP地址,并把数据报发过去的过程之前,主机在链路层实际上是要知道目标主机的物理地址的,每台主机的物理地址又称MAC地址是全球唯一的。在这一步,就需要我们的ARP协议。</p></li><li><p>首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。</p></li><li><p>当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机 IP地址,源主机MAC地址,目的主机的IP 地址。</p></li><li><p>当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。</p></li><li><p>源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。<br>广播发送ARP请求,单播发送ARP响应。</p></li><li><p>得到了目的主机的硬件地址后,就包含着IP数据报的以太网数据帧就可以正常发送了。</p></li></ol><h2 id="RARP协议"><a href="#RARP协议" class="headerlink" title="RARP协议"></a>RARP协议</h2><ul><li><p>RARP协议是逆地址解析协议,作用是完成硬件地址到IP地址的映射,</p></li><li><p>主要用于无盘工作站(没有磁盘驱动器的系统),因为给无盘工作站配置的IP地址不能保存。</p></li></ul><p><strong>工作流程:</strong></p><ol><li><p>在网络中配置一台RARP服务器,里面保存着IP地址和MAC地址的映射关系</p></li><li><p>当无盘工作站启动后,就封装一个RARP数据包,里面有其MAC地址,然后广播到网络上去</p></li><li><p>当服务器收到请求包后,就查找对应的MAC地址的IP地址装入响应报文中发回给请求者。</p></li><li><p>因为需要广播请求报文,因此RARP只能用于具有广播能力的网络。</p></li></ol><h2 id="FTP协议"><a href="#FTP协议" class="headerlink" title="FTP协议"></a>FTP协议</h2><ul><li><p>文件传输协议(File Transfer Protocol,FTP)是一种提供网络之间共享文件的协议,它可以在计算机之间可靠、高效地传送文件。在传输时,传输双方的操作系统、磁盘文件系统类型可以不同。</p></li><li><p>FTP 协议允许 TCP/IP 网络上的两台计算机之间进行文件传输。而 FTP 服务是基于 FTP 协议的文件传输服务。</p></li><li><p>工作时,一台计算机上运行 FTP 客户端应用程序,另一台计算机上需要运行 FTP 服务器端程序。只有拥有了 FTP 服务,客户端才能进行文件传输。</p></li></ul><h3 id="TFTP协议"><a href="#TFTP协议" class="headerlink" title="TFTP协议"></a>TFTP协议</h3><ul><li><p>TFTP协议,即简单文件传输协议(Trivial File Transfer Protocol,TFTP)</p></li><li><p>该协议是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议</p></li><li><p>与 FTP 协议不同的是,TFTP 传输文件时不需要用户进行登录。提供不复杂、开销不大的文件传输服务,它只能从文件服务器上下载或上传文件,不能列出目录。</p></li><li><p>属于应用层的协议, TFTP 基于 UDP 协议进行文件传输。</p></li></ul><h2 id="HTTP协议"><a href="#HTTP协议" class="headerlink" title="HTTP协议"></a>HTTP协议</h2><ul><li><p>超文本传输协议,是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。</p></li><li><p>属于应用层的协议,基于TCP/IP协议实现,后面另起文章进行细究介绍</p></li></ul><h2 id="DNS协议"><a href="#DNS协议" class="headerlink" title="DNS协议"></a>DNS协议</h2><ul><li><p>DNS(Domain Name System)服务是和 HTTP 协议一样位于应用层的协议。它提供域名到 IP 地址之间的解析服务。</p></li><li><p>计算机既可以被赋予 IP 地址,也可以被赋予主机名和域名。用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP地址访问。因为与 IP 地址的一组纯数字相比,用字母配合数字的表示形式来指定计算机名更符合人类的记忆习惯。</p></li><li><p>为了解决上述的问题,DNS 服务应运而生。DNS 协议提供通过域名查找 IP 地址,或逆向从 IP 地址反查域名的服务。</p></li></ul><h2 id="Telnet-协议"><a href="#Telnet-协议" class="headerlink" title="Telnet 协议"></a>Telnet 协议</h2><ul><li><p>Telnet 协议是 Internet 远程登录服务的标准协议和主要方式,它为用户提供了在本地计算机上远程管理主机的能力。</p></li><li><p>使用者在自己的电脑上使用 Telnet 程序连接到服务器,然后在 Telnet 程序中输入命令,这些命令将会在服务器上运行,就像直接在服务器的控制台上输入一样。</p></li></ul><p><img src="https://i.loli.net/2020/04/11/wP9dVET7ZK4LGzQ.png" alt="Telnet"></p><p>其中,工作流程分为 6 个步骤,每个步骤含义如下:</p><ol><li><p>Telnet 客户端通过 TCP 协议的三次握手与 Telnet 服务器建立连接。</p></li><li><p>建立连接后,需要通过用户名和密码才能远程登录到服务器。因此,服务器要求客户端提供用户名和密码。</p></li><li><p>Telnet 客户端输入用户名和密码,尝试登录服务器。</p></li><li><p>成功登录后客户端向服务器发送要执行的命令。</p></li><li><p>服务器收到客户端发来的执行命令,开始执行命令,并将结果返回给客户端。</p></li><li><p>客户端不再需要远程执行命令,将向服务器发送 TCP 断开数据包,用于撤销连接。</p></li></ol><h2 id="SNMP-协议"><a href="#SNMP-协议" class="headerlink" title="SNMP 协议"></a>SNMP 协议</h2><ul><li><p>简单网络管理协议(Simple Network Management Protocol,SNMP)是由互联网工程任务组定义的一套网络管理协议。该协议是基于简单网关监视协议(Simple Gateway Monitor Protocol,SGMP)制定的。</p></li><li><p>Telnet 协议可以用于连接远程计算机,并进行管理与控制,如远程执行命令。这种情况下,执行的命令有一定的局限性,如只能执行远程主机上支持的命令。由于网络设备越来越多,网络规模越来越大,管理这些设备也越来越重要。远程管理网络的需求日益迫切,SNMP 应运而生。</p></li><li><p>SNMP 协议能够帮助网络管理员提高网络管理效率,及时发现和解决网络问题,对网络增长做好规划。网络管理员还可以通过 SNMP 协议,接收网络节点的通知消息和警告事件报告等,从而获知网络出现的问题。</p></li></ul><p>工作流程如图所示:</p><p><img src="https://i.loli.net/2020/04/11/mhE3NRS7COiYqVn.png" alt="snmp"></p><p>上图中展现了管理员通过 NMS 获取网关监控数据的工作流程,其中涉及了一些 SNMP 协议的关键信息。</p><p>为了方便理解,下面先介绍这些信息的作用及含义。</p><p><strong>MIB(管理信息库):</strong></p><p>任何一个被管理的设备都表示成一个对象,并称为被管理的对象。而 MIB 就是被管理对象的集合。它定义了被管理对象的一系列属性,如对象的名称、对象的访问权限和对象的数据类型等。每个设备都有自己的 MIB。MIB 是一种树状数据库,MIB 管理的对象,就是树的端节点,每个节点都有唯一位置和唯一名字。</p><p><strong>SNMP 代理:</strong></p><p>是一种嵌入在被管理设备中的网络管理软件模块,主要用来控制本地机器的管理信息。它还负责将管理信息转换成 SNMP 兼容的格式,传递给 NMS。</p><p>上图所示的工作流程分为以下 4 个步骤:</p><ol><li><p>当管理员查询被管理设备中的对象的相关值时,首先通过网络管理站 NMS 中的 MIB 找到相关对象。</p></li><li><p>网络管理站 NMS 向 SNMP 代理申请 MIB 中定义对象的相关值。</p></li><li><p>SNMP 代理在自己的 MIB 库中进行查找。</p></li><li><p>SNMP 代理将找到的对象相关值返回给网络管理站 NMS。</p></li></ol><h1 id="6-硬件介绍"><a href="#6-硬件介绍" class="headerlink" title="6.硬件介绍"></a>6.硬件介绍</h1><h2 id="中继器"><a href="#中继器" class="headerlink" title="中继器"></a>中继器</h2><ul><li><p>中继器是局域网环境下用来延长网络距离的最简单最廉价的网络互联设备</p></li><li><p>操作在OSI的物理层,中继器对在线路上的信号具有放大再生的功能,用于扩展局域网网段的长度(仅用于连接相同的局域网网段)。</p></li><li><p>中继器是连接网络线路的一种装置,常用于两个网络节点之间物理信号的双向转发工作。</p></li><li><p>中继器主要完成物理层的功能,负责在两个节点的物理层上按位传递信息,完成信号的复制、调整和放大功能,以此来延长网络的长度。也就说类似生活中变电的变压器功能。</p></li></ul><h2 id="网桥"><a href="#网桥" class="headerlink" title="网桥"></a>网桥</h2><ul><li><p>网桥不是真实的物质东西,而是一个作用域的代名词,网桥(Bridge)像一个聪明的中继器。</p></li><li><p>中继器从一个网络电缆里接收信号, 放大它们,将其送入下一个电缆,然而网桥将网络的多个网段在数据链路层连接起来。</p></li><li><p>相比较而言,网桥对从关卡上传下来的信息更敏锐一些。网桥是一种对帧进行转发的技术,根据MAC分区块,可隔离碰撞。</p></li><li><p>网桥也叫桥接器,是连接两个局域网的一种存储/转发设备,它能将一个大的LAN分割为多个网段,或将两个以上的LAN互联为一个逻辑LAN,使LAN上的所有用户都可访问服务器。</p></li></ul><h2 id="交换机"><a href="#交换机" class="headerlink" title="交换机"></a>交换机</h2><ul><li><p>在计算机网络系统中,交换机是针对共享工作模式的弱点而推出的。</p></li><li><p>交换机拥有一条高带宽的背部总线和内部交换矩阵。交换机的所有的端口都挂接在这条背部总线上,当控制电路收到数据包以后,处理端口会查找内存中的地址对照表以确定目的MAC(网卡的硬件地址)的NIC(网卡)挂接在哪个端口上,通过内部 交换矩阵迅速将数据包传送到目的端口。</p></li><li><p>目的MAC若不存在,交换机才广播到所有的端口,接收端口回应后交换机会“学习”新的地址,并把它添加入内部地址表中。</p></li><li><p>交换机工作于OSI参考模型的第二层,即数据链路层。交换机内部的CPU会在每个端口成功连接时,通过ARP协议学习它的MAC地址,保存成一张 ARP表。在今后的通讯中,发往该MAC地址的数据包将仅送往其对应的端口,而不是所有的端口。因此,交换机可用于划分数据链路层广播,即冲突域;但它不 能划分网络层广播,即广播域。</p></li><li><p>交换机被广泛应用于二层网络交换,俗称“二层交换机”。交换机的种类有:二层交换机、三层交换机、四层交换机、七层交换机分别工作在OSI七层模型中的第二层、第三层、第四层盒第七层,并因此而得名。</p></li></ul><h2 id="路由器"><a href="#路由器" class="headerlink" title="路由器"></a>路由器</h2><ul><li><p>路由器用来连通不同的网络,另一个作用是选择信息和分发传送的线路功能。</p></li><li><p>选择通畅快捷的近路,能大大提高通信速度,减轻网络系统通信负荷,节约网络系统资源,提高网络系统畅通率。</p></li><li><p>具有多个接口,用于连接多个IP子网及多种链路,并实现其互联互通的网络设备。</p></li><li><p>工作在OSI网络层,其主要工作任务是在网络中转发IP数据包</p></li></ul><h2 id="路由表"><a href="#路由表" class="headerlink" title="路由表"></a>路由表</h2><ul><li><p>又称全局路由表,存储在路由器的内存中,用于指示路由器发送IP数据包转发至正确目的地的信息表。</p></li><li><p>例如,生活中我们去乘火车,我们只关心目的城市,从上车之前我们并不知道其最近路线</p></li><li><p>路由表里面就保存着我们下一站的信息,火车每到一个站,都要经过铁路公司的统一安排开始进入对应的股道</p></li><li><p>路由器可以理解为是铁路中的小枢纽站一样,路由表就是班车的到站信息。</p></li></ul><p><img src="https://i.loli.net/2020/04/11/oU2yfX5xOK4p8uB.png" alt="路由表"></p><h2 id="区分交换机和路由器"><a href="#区分交换机和路由器" class="headerlink" title="区分交换机和路由器"></a>区分交换机和路由器</h2><p>利用现实例子了解路由器和交换机的区别:</p><ul><li><p>路由器相当于邮局,把信投递到收件人地址,它的任务就完成了。但是信邮到了你们宿舍楼,而这个地址不是你一个人专享的</p></li><li><p>所以楼管王大爷还要负责把信给到你手里,他不会关心收件人地址,只看收件人姓名,然后打个内线电话叫你来取信。</p></li><li><p>如果没有邮局,你没法向世界各地的漂亮妹子们发信,也没法从楼外的漂亮妹子那里收信。</p></li><li><p>但是因为楼管王大爷的存在,你仍然可以通过他与同宿舍楼的好基友书信往来。</p></li><li><p>所有邮局构成的系统,就是“广域网”,而你的宿舍楼,就是“局域网”,构建局域网是不需要路由器的。</p></li></ul><blockquote><p>扮演角色:</p></blockquote><p>楼管王大爷:交换机(只负责局域网分配不同的IP)</p><p>邮局:路由器,可以把一个IP分配给很多个主机使用,这些主机对外只表现出一个IP,也就是只把信送到宿舍楼下,共用一个邮箱(IP)</p><h1 id="7-典型面试题"><a href="#7-典型面试题" class="headerlink" title="7.典型面试题"></a>7.典型面试题</h1><blockquote><p>请介绍在浏览器中输入 <a href="http://www.baidu.com/">www.baidu.com</a> 后执行的全部过程。</p></blockquote><ol><li><p>客户端浏览器首先获取本地host文件获取IP,如果获取不到再通过DNS解析到<a href="http://www.baidu.com/">www.baidu.com</a> 的IP地址220.181.27.48,通过这个IP地址找到客户端到服务器的路径。</p></li><li><p>客户端浏览器发起一个HTTP会话到220.181.27.48,通过TCP握手建立连接,然后通过TCP进行封装数据包,输入到网络层。</p></li><li><p>在客户端的传输层,把HTTP会话请求分成报文段,添加源和目的端口,如服务器使用80端口监听客户端的请求,客户端由系统随机选择一个端口如5000,与服务器进行交换,服务器把相应的请求返回给客户端的5000端口。</p></li><li><p>然后使用IP层的IP地址查找目的端。客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器。</p></li><li><p>期间可能经过多个路由器,这些都是由路由器来完成的工作,无非就是通过查找路由表决定通过那个路径到达服务器。</p></li><li><p>客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定IP地址的MAC地址。</p></li><li><p>然后发送ARP请求查找目的地址,如果得到回应后就可以使用ARP的请求应答交换的IP数据包现在就可以传输了,然后发送IP数据包到达服务器的地址。</p></li><li><p>服务器处理请求后响应首页数据反向传输,传输到客户端后,TCP连接释放,浏览器将首页文件进行解析,并将Web页显示给用户。</p></li></ol>]]></content>
<categories>
<category>计算机网络</category>
</categories>
<tags>
<tag>网络</tag>
</tags>
</entry>
<entry>
<title>【Git】:git rebase和git merge有什么区别?</title>
<link href="/2020/04/06/development/git-rebase/"/>
<url>/2020/04/06/development/git-rebase/</url>
<content type="html"><![CDATA[<ul><li><p>git rebase和git merge都是Git用来合并分支代码基本命令</p></li><li><p>在 Git 工作流中,两者作用一致,但是原理上却不相同</p></li><li><p>另外,git rebase为什么在某些场景下会被限制使用呢?</p></li></ul><span id="more"></span><h1 id="1-场景模拟"><a href="#1-场景模拟" class="headerlink" title="1.场景模拟"></a>1.场景模拟</h1><p>假设当前我们有master和feature分支,当你在专用分支上开发新 feature 时,然后另一个团队成员在 master 分支提交了新的 commits,这种属于正常的Git工作场景。如下图:</p><p><img src="https://i.loli.net/2020/04/06/yFckAlPCj3n2o59.png" alt="git-work"></p><p>现在,假设在 master 分支上的新提交与你正在开发的 feature 相关,需要master分支将新提交的记录合并到你的 feature 分支中。</p><p>当我们需要对分支代码进行合并的时候,一般会使用下面两个命令:</p><ul><li><p>git merge </p></li><li><p>git rebase</p></li></ul><blockquote><p><strong>git rebase</strong>和<strong>git merge</strong>这两个命令都旨在将更改代码从一个分支合并到另一个分支,但二者的合并方式却有很大的不同。</p></blockquote><h1 id="2-git-merge"><a href="#2-git-merge" class="headerlink" title="2.git merge"></a>2.git merge</h1><p>使用git merge 命令将 <code>master</code> 分支合并到 <code>feature </code>分支中:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">git checkout feature<br>git <span class="hljs-keyword">merge</span> master<br></code></pre></td></tr></table></figure><p>缩写为一句代码就是:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">git <span class="hljs-keyword">merge</span> feature master<br></code></pre></td></tr></table></figure><p><img src="https://i.loli.net/2020/04/06/S5YmfCK7wW1JxTB.png" alt="git-merge"></p><p>由上图可知,git merge 会在 <code>feature</code> 分支中新增一个新的 <strong>merge commit</strong>,然后将两个分支的历史联系在一起</p><ul><li><p>使用 merge 是很好的方式,因为它是一种<strong>非破坏性的</strong>操作,对现有分支不会以任何方式被更改。</p></li><li><p>另一方面,这也意味着 <code>feature</code> 分支每次需要合并上游更改时,它都将产生一个额外的合并提交。</p></li><li><p>如果master 提交非常活跃,这可能会严重污染你的 <code>feature</code> 分支历史记录。不过这个问题可以使用高级选项 <code>git log</code> 来缓解</p></li></ul><h1 id="3-git-rebase"><a href="#3-git-rebase" class="headerlink" title="3.git rebase"></a>3.git rebase</h1><p>使用git rebase 命令将 <code>master</code> 分支合并到 <code>feature </code>分支中:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs sql">git checkout feature<br>git rebase master<br></code></pre></td></tr></table></figure><p>缩写为一句代码就是:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs sql">git rebase feature master<br></code></pre></td></tr></table></figure><p><img src="https://i.loli.net/2020/04/06/F1qIGKTNDW6aRul.png" alt="git-rebase"></p><ul><li><p>rebase 会将整个 <code>feature</code> 分支移动到 <code>master</code> 分支的<strong>顶端</strong>,从而有效地整合了所有 master 分支上的提交。</p></li><li><p>但是,与 merge 提交方式不同,rebase 通过为原始分支中的每个提交创建全新的 commits 来重写项目历史记录,特点是仍然会在<code>feature</code>分支上形成线性提交</p></li><li><p>rebase 的主要好处是可以获得更清晰的项目历史。首先,它消除了 git merge 所需的不必要的合并提交;其次,正如你在上图中所看到的,rebase 会产生完美线性的项目历史记录,你可以在 feature分支上没有任何分叉的情况下一直追寻到项目的初始提交。</p></li></ul><p><strong>git rebase原理再深入:</strong></p><p>将<code>master</code>分支代码合并到<code>feature</code>上:</p><p><img src="https://i.loli.net/2020/04/06/kdAPQeMRN7DhyrY.png" alt="rebase"></p><ul><li><p>这边需要强调一个概念:<strong>reapply(重放)<strong>,使用rebase并不是简单地好像你用</strong>ctrl-x/ctrl-v</strong>进行剪切复制一样</p></li><li><p>rebase会依次地将你所要操作的分支的所有提交应用到目标分支上</p></li></ul><p>合并过程如下图:</p><p><img src="https://i.loli.net/2020/04/06/SogmKiIswFJG3V6.png" alt="git-rebase-1"></p><p>从上图可以看出,在对特征分支进行rebase之后,其等效于创建了新的提交。并且老的提交也没有被销毁,只是简单地不能再被访问或者使用。</p><blockquote><p>实际上在执行rebase的时候,有两个隐含的注意点:</p></blockquote><p>1.在重放之前的提交的时候,<strong>Git会创建新的提交</strong>,也就是说即使你重放的提交与之前的一模一样Git也会将之<strong>当做新的独立的提交</strong>进行处理。</p><p>2.<strong>Git rebase并不会删除老的提交</strong>,也就是说你在对某个分支执行了rebase操作之后,老的提交仍然会存放在.git文件夹的objects目录下。</p><p>如果想要在线进行Git工作场景的模拟,可以到此<a href="https://learngitbranching.js.org/?locale=zh_CN">Git Online</a></p><h1 id="4-如何选择git-merge和git-rebase"><a href="#4-如何选择git-merge和git-rebase" class="headerlink" title="4.如何选择git merge和git rebase?"></a>4.如何选择git merge和git rebase?</h1><p>根据上面的对比可知:</p><ul><li><p><strong>git merge</strong>优点是分支代码合并后不破坏原分支的代码提交记录,缺点就是会产生额外的提交记录并进行两条分支的合并,</p></li><li><p><strong>git rebase</strong> 优点是无须新增提交记录到目标分支,rebase后可以将对象分支的提交历史续上目标分支上,形成线性提交历史记录,进行review的时候更加直观</p></li><li><p>git merge 如果有多人进行开发并进行分支合并,会形成复杂的合并分支图,如下图:</p></li></ul><p><img src="https://i.loli.net/2020/04/06/6dW3fTI27B1Xbro.png" alt="git-merge"></p><blockquote><p>问题:既然rebase如此有用,那么可以使用rebase完全取代merge吗? 答案:不能!</p></blockquote><p>git rebase的黄金原则:</p><ul><li><strong>不能在一个共享的分支上进行Git rebase操作。</strong></li></ul><p><strong>总结:</strong></p><ul><li><p>融合代码到公共分支的时使用<code>git merge</code>,而不用<code>git rebase</code></p></li><li><p>融合代码到个人分支的时候使用<code>git rebase</code>,可以不污染分支的提交记录,形成简洁的线性提交历史记录。</p></li></ul><h1 id="5-rebase的黄金原则"><a href="#5-rebase的黄金原则" class="headerlink" title="5.rebase的黄金原则"></a>5.rebase的黄金原则</h1><blockquote><p>问题:为什么不能再一个共享的分支上进行Git rebase操作呢?</p></blockquote><p>所谓共享的分支,即是指那些存在于远端并且允许团队中的其他人进行Pull操作的分支,比如我们Git工作的master分支就是最常见的公共分支。</p><p>假设现在Bob和Anna在同一个项目组中工作,项目所属的仓库和分支大概是下图这样:</p><p><img src="https://i.loli.net/2020/04/06/CeRwsKUWxoOmbnV.png" alt="rebase-1"></p><p>现在Bob为了图一时方便打破了原则,正巧这时Anna在特征分支上进行了新的提交,此时的结构图大概是这样的:</p><p><img src="https://i.loli.net/2020/04/06/xWeQj5HPCcyTIAd.png" alt="rebase-2"></p><p>当Bob推送自己的分支到远端的时候,现在的分支情况如下:</p><p><img src="https://i.loli.net/2020/04/06/Eb3Ch4UeVBWaxjH.png" alt="rebase-3"></p><p>然后呢,当Anna也进行推送的时候,她会得到如下的提醒,Git提醒Anna她本地的版本与远程分支并不一致,需要向远端服务器拉取代码进行同步:</p><p><img src="https://i.loli.net/2020/04/06/IcByegf5CYwOL63.png" alt="rebase-4"></p><p>在Anna提交之前,分支中的Commit序列是如下这样的:</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs sql">A<span class="hljs-comment">--B--C--D' origin/feature // GitHub</span><br><br>A<span class="hljs-comment">--B--D--E feature // Anna</span><br></code></pre></td></tr></table></figure><p>在进行Pull操作之后,Git会进行自动地合并操作,结果大概是这样的:</p><p><img src="https://i.loli.net/2020/04/06/nSuZJRONGkIsHxa.png" alt="rebase-5"></p><p>这个第M个提交即代表着合并的提交,也就是Anna本地的分支与Github上的特征分支最终合并的点,现在Anna解决了所有的合并冲突并且可以Push她的代码,在Bob进行Pull之后,每个人的Git Commit结构为:</p><p><img src="https://i.loli.net/2020/04/06/1gymbXko7IhKWfF.png" alt="rebase-6"></p><p>看到上面这个混乱的流线图,相信你对于Rebase和所谓的黄金准则也有了更形象深入的理解。</p><blockquote><p>假设下还有一哥们Emma,第三个开发人员,在他进行了本地Commit并且Push到远端之后,仓库变为了:</p></blockquote><p><img src="https://i.loli.net/2020/04/06/3jsGSy7Qei4g8a2.png" alt="rebase-7"></p><ul><li><p>这还只是仅有几个人,一个特征分支的项目因为误用rebase产生的后果。如果你团队中的每个人都对公共分支进行rebase操作,那么后果就是乱成一片。</p></li><li><p>另外,相信你也注意到,在远端的仓库中存有大量的重复的Commit信息,这会大大浪费我们的存储空间。</p></li><li><p>因此,<strong>不能在一个共享的分支上进行Git rebase操作,避免出现项目分支代码提交记录错乱和浪费存储空间的现象。</strong></p></li></ul><h1 id="6-使用rebase合并多次提交记录"><a href="#6-使用rebase合并多次提交记录" class="headerlink" title="6.使用rebase合并多次提交记录"></a>6.使用rebase合并多次提交记录</h1><blockquote><p>rebase和merge不同的作用还有一个就是合并分支多次提交记录。</p></blockquote><p>在分支开发的过程中,我们常常会出现为了调试程序而多次提交代码记录,但是这些记录的共同目的都是为了解决某一个需求,所以,是否可以将这些记录合并起来为一个新的记录会更方便进行代码的review呢?</p><p><strong>git rebase提供了合并记录的作用</strong></p><p>下面是一个合并的案例过程:</p><p>1.尝试合并分支的最近 4 次提交纪录</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">git</span> rebase -i HEAD~<span class="hljs-number">4</span><br></code></pre></td></tr></table></figure><p>2.这时候,会自动进入 vi 编辑模式:</p><p><img src="https://i.loli.net/2020/04/07/qiQa7GHzO2wUhIp.png" alt="rebase-x"></p><p>进入编辑模式,第一列为操作指令,第二列为commit号,第三列为commit信息。</p><ul><li><p>pick:保留该commit;</p></li><li><p>reword:保留该commit但是修改commit信息;</p></li><li><p>edit:保留该commit但是要修改commit内容;</p></li><li><p>squash:将该commit和前一个commit合并;</p></li><li><p>fixup:将该commit和前一个commit合并,并不保留该commit的commit信息;</p></li><li><p>exec:执行shell命令;</p></li><li><p>drop:删除该commit。</p></li></ul><p>按照如上命令来修改你的提交记录:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs s">p 799770a add article<br>s 72530e4 add article<br>s 53284b1 add article<br>s 9f6e388 add article<br></code></pre></td></tr></table></figure><p>成功合并了四条记录为一条:</p><p><img src="https://i.loli.net/2020/04/07/3YI6kjyh7ltDcmC.png" alt="success"></p><p>如果保存的时候,你碰到了这个错误:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs s">error: cannot 'squash' without a previous commit<br></code></pre></td></tr></table></figure><p>说明你在合并记录的时候顺序错误了,压缩顺序应该是从下往上,而不是从上往下,否则就会触发上面的错误。也就是以新记录为准。</p><p>例如上面的例子写成了这样就是出现错误:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs s">s 799770a add article<br>s 72530e4 add article<br>s 53284b1 add article<br>p 9f6e388 add article<br></code></pre></td></tr></table></figure><p>中途出现异常退出了 vi窗口,执行下面命令可以返回编辑窗口:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs s">git rebase --edit-todo<br></code></pre></td></tr></table></figure><p>继续编辑合并记录的操作,修改完保存一下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs s">git rebase --continue<br></code></pre></td></tr></table></figure><h1 id="7-git-pull-–rebase的应用"><a href="#7-git-pull-–rebase的应用" class="headerlink" title="7.git pull –rebase的应用"></a>7.git pull –rebase的应用</h1><h2 id="7-1-场景介绍"><a href="#7-1-场景介绍" class="headerlink" title="7.1 场景介绍"></a>7.1 场景介绍</h2><p>同事都基于git flow工作流的话都会从<code>develop</code>拉出分支进行并行开发,这里的分支可能是多到数十个,然后彼此在进行自己的逻辑编写,时间可能需要几天或者几周。</p><p>在这期间你可能需要时不时的需要pull下远程develop分支上的同事的提交。这是个好的习惯,这样下去就可以避免你在一个无用的代码上进行长期的开发,回头来看这些代码不是新的代码。甚至是会面临很多冲突需要解决,而这个时候你可能还需要对冲突的部分代码进行测试和问题解决,你在有些时候pull代码的时候会有这样的一个提示:</p><p><img src="https://i.loli.net/2020/04/07/lwtOEmdH9BI5fja.png" alt="pull"></p><p>通常习惯性的你可能会,”esc :wq“,直接默认commit注释。然后你的commit log就多了一笔很不好看的log。</p><p><img src="https://i.loli.net/2020/04/07/noC7k4KzTswPUqm.png" alt="log"></p><h2 id="7-2-如何移除多余的merge-commit"><a href="#7-2-如何移除多余的merge-commit" class="headerlink" title="7.2 如何移除多余的merge commit"></a>7.2 如何移除多余的merge commit</h2><blockquote><p>很简单,只要你在pull时候需要习惯性的加上—rebase参数,这样可以避免很多问题。</p></blockquote><p><code>git pull --rebase</code>可以免去分支中出现大量的merge commit,基本原理就像上面rebase一样,合并过程会直接融合到目标分支的顶端,形成线性历史提交。</p><h2 id="7-3-分析过程"><a href="#7-3-分析过程" class="headerlink" title="7.3 分析过程"></a>7.3 分析过程</h2><p>1.正常情况下的分支commit路线并且当前develop分支上有三个commit。</p><p>2.现在我们两个项目开始启动,需要分别拉出两个分支独立开发,提交过程如图:</p><p><img src="https://i.loli.net/2020/04/07/yLIcez1GkDOJ6aF.png" alt="pull"></p><p>3.我们分别checkout –b 出来两个分支,独立开发互不干扰。正常情况下,如果这两个分支的改动都没有冲突的时候,一切都很顺利的。</p><p>4.我在develop_newfeature_authorcheck里修改了点东西,push到develop。然后checkout 到develop_newfeature_apiwrapper。</p><p>5.当我再git pull,这将会把develop_newfeature_authorcheck分支的修改直接拉下来于本地代码merge,且产生一个commit,也就是merge commit。</p><p><img src="https://i.loli.net/2020/04/07/BgW9QrZYuCFxMOV.png" alt="pull"></p><p>6.使用<code>git pull –rebase</code>产生的提交结果就完全不一样,rebase并不会产生一个commit提交,而是会将你的E commit附加到D commit的结尾处。在看commit log时,不会多出你所不知道的commit出来。其实此处的F commmit是无意义的,它只是一个merge commit。</p><p><img src="https://i.loli.net/2020/04/07/6Ghz8mYrNgCEH9L.png" alt="pull"></p><blockquote><p>很明显,git pull –rebase 会使commit看起来很自然,因为代码都有一个前后依赖,看起来更加的直观。</p></blockquote><h3 id="注意:"><a href="#注意:" class="headerlink" title="注意:"></a>注意:</h3><p>在公共的分支上,例如<code>master</code>仍然要遵守rebase黄金原则,不用使用<code>git pull --rabase</code>进行代码的拉取,更改代码的历史提交记录。</p><p>参考资料:<a href="https://segmentfault.com/a/1190000005937408">https://segmentfault.com/a/1190000005937408</a></p>]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>【Git】:如何将代码支持同时上传github和gitee</title>
<link href="/2020/03/10/development/git-multpush/"/>
<url>/2020/03/10/development/git-multpush/</url>
<content type="html"><![CDATA[<ul><li><p>Github和Gitee的区别主要是服务器位于国外和国内,访问速度有区别</p></li><li><p>那么能将本地代码通过git分别传到两个不同的托管平台吗?能的</p></li><li><p>本文主要介绍如何通过<code>git remote</code>设置,将代码分别上传到github和gitee</p></li></ul><span id="more"></span><h1 id="1-Git支持关联多个远程托管库"><a href="#1-Git支持关联多个远程托管库" class="headerlink" title="1.Git支持关联多个远程托管库"></a>1.Git支持关联多个远程托管库</h1><ul><li><p>Git是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。</p></li><li><p>因为git本身是分布式版本控制系统,所以可以同步到多个远程库,所以一个本地库可以既关联GitHub,又关联gitee</p></li><li><p>使用多个远程库时,要注意git给远程库起的默认名称是origin,如果有多个远程库,我们需要用不同的名称来标识不同的远程库</p></li></ul><h1 id="2-具体操作"><a href="#2-具体操作" class="headerlink" title="2.具体操作"></a>2.具体操作</h1><h2 id="2-1-查看远程库信息"><a href="#2-1-查看远程库信息" class="headerlink" title="2.1 查看远程库信息"></a>2.1 查看远程库信息</h2><p><code>git remote -v</code></p><ul><li>输出结果</li></ul><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs scss">origin git<span class="hljs-keyword">@github</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (fetch)<br>origin git<span class="hljs-keyword">@github</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (push)<br></code></pre></td></tr></table></figure><h2 id="2-2-删除初始远程库"><a href="#2-2-删除初始远程库" class="headerlink" title="2.2 删除初始远程库"></a>2.2 删除初始远程库</h2><p><code>git remote rm origin</code></p><h2 id="2-3-重新关联多个远程库"><a href="#2-3-重新关联多个远程库" class="headerlink" title="2.3 重新关联多个远程库"></a>2.3 重新关联多个远程库</h2><ul><li>关联github远程库</li></ul><p><code>git remote add github [email protected]:xxx/LearnGit.git</code></p><ul><li>关联gitee远程库</li></ul><p><code>git remote add gitee [email protected]:xxx/LearnGit.git</code></p><blockquote><p>这样本地的项目就关联了两个不同的远程库,可以通过 git remote -v 进行查看</p></blockquote><p><code>git remote -v</code></p><ul><li>输出结果</li></ul><figure class="highlight scss"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs scss">gitee git<span class="hljs-keyword">@gitee</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (fetch)<br>gitee git<span class="hljs-keyword">@gitee</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (push)<br>github git<span class="hljs-keyword">@github</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (fetch)<br>github git<span class="hljs-keyword">@github</span>.<span class="hljs-attribute">com</span>:xxx/LearnGit.git (push)<br></code></pre></td></tr></table></figure><h1 id="3-拉取代码"><a href="#3-拉取代码" class="headerlink" title="3.拉取代码"></a>3.拉取代码</h1><blockquote><p>拉取代码的时候需要声明好关联的哪一个远程库和哪一个分支</p></blockquote><ul><li>拉取github远程库的master分支的代码</li></ul><p><code>git pull github master</code></p><ul><li>拉取gitee远程库的master分支的代码</li></ul><p><code>git pull gitee master</code></p><h1 id="4-上传代码"><a href="#4-上传代码" class="headerlink" title="4.上传代码"></a>4.上传代码</h1><blockquote><p>上传代码同样需要声明好关联的远程库和分支</p></blockquote><ul><li>上传代码到github远程库的master分支</li></ul><p><code>git push github master</code></p><ul><li>上传代码到gitee远程库的master分支</li></ul><p><code>git push gitee master</code></p><blockquote><p>通过一推一拉都要点明远程库实现了多库管理的代码同步。</p></blockquote>]]></content>
<categories>
<category>Git</category>
</categories>
<tags>
<tag>Git</tag>
</tags>
</entry>
<entry>
<title>【Linux】:如何查看内核版本和系统版本?</title>
<link href="/2020/03/06/linux/linux-version/"/>
<url>/2020/03/06/linux/linux-version/</url>
<content type="html"><![CDATA[<ul><li><p>诸如 RHEL、Debian、openSUSE、Arch Linux 这几种主流发行版来说,它们各自拥有不同的包管理器来管理系统上的软件包</p></li><li><p>如果不知道所使用的是哪一个发行版的系统,在软件包安装的时候就会无从下手</p></li></ul><h1 id="1-查看内核版本"><a href="#1-查看内核版本" class="headerlink" title="1.查看内核版本"></a>1.查看内核版本</h1><h2 id="1-1-uname-命令"><a href="#1-1-uname-命令" class="headerlink" title="1.1 uname 命令"></a>1.1 uname 命令</h2><blockquote><p>uname(unix name) 是一个打印系统信息的工具,内容包括内核名称、版本号、系统详细信息以及所运行的操作系统等等。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">uname</span> -a</span><br>Linux centos 3.10.0-327.el7.x86_64 #1 SMP Thu Nov 19 22:10:57 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux<br></code></pre></td></tr></table></figure><p>以上运行结果说明正在使用的系统版本是 centos,内核版本 3.10.0-327.el7.x86_64</p><h2 id="1-2-查看-proc-version-文件"><a href="#1-2-查看-proc-version-文件" class="headerlink" title="1.2 查看 /proc/version 文件"></a>1.2 查看 /proc/version 文件</h2><blockquote><p>这个文件记录了 Linux 内核的版本、用于编译内核的 gcc 的版本、内核编译的时间,以及内核编译者的用户名。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">cat</span> /proc/version</span><br>Linux version 2.6.32-220.23.1.el6.x86_64 ([email protected]) (gcc version 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC) ) #1 SMP Mon Jun 18 18:58:52 BST 2012<br></code></pre></td></tr></table></figure><h1 id="2-查看系统版本"><a href="#2-查看系统版本" class="headerlink" title="2.查看系统版本"></a>2.查看系统版本</h1><h2 id="2-1-lsb-release-命令"><a href="#2-1-lsb-release-命令" class="headerlink" title="2.1 lsb_release 命令"></a>2.1 lsb_release 命令</h2><blockquote><p>LSB(Linux Standard Base)能够打印发行版的具体信息,包括发行版名称、版本号、代号等。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">lsb_release -a</span><br>No LSB modules are available.<br>Distributor ID: Ubuntu<br>Description: Ubuntu 16.04.3 LTS<br>Release: 16.04<br>Codename: xenial<br></code></pre></td></tr></table></figure><ul><li><p>这个命令适用于所有的 Linux 发行版,包括 Redhat、SuSE、Debian… 等发行版。</p></li><li><p>有的系统中默认并没有安装 lsb_release,需要安装。</p></li></ul><p>下面介绍一下 CentOS 系统中安装方法。</p><p>首先查找 lsb_release 安装包:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs Shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash">yum provides lsb_release</span><br>已加载插件:fastestmirror<br>Loading mirror speeds from cached hostfile<br> * base: mirrors.btte.net<br> * extras: mirrors.btte.net<br> * updates: mirrors.btte.net<br>redhat-lsb-core-4.1-27.el7.centos.1.i686 : LSB Core module support<br>源 :base<br>匹配来源:<br>文件名 :/usr/bin/lsb_release<br></code></pre></td></tr></table></figure><p>安装:</p><p><code>$ sudo yum install -y redhat-lsb-core</code></p><h2 id="2-2-cat-etc-issue"><a href="#2-2-cat-etc-issue" class="headerlink" title="2.2 cat /etc/issue"></a>2.2 cat /etc/issue</h2><blockquote><p>此命令适用于所有的 Linux 发行版。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">cat</span> /etc/issue</span><br>Ubuntu 16.04.1 LTS \n \l<br><br></code></pre></td></tr></table></figure><h2 id="2-3-cat-etc-redhat-release"><a href="#2-3-cat-etc-redhat-release" class="headerlink" title="2.3 cat /etc/redhat-release"></a>2.3 cat /etc/redhat-release</h2><blockquote><p>这种方法只适合查看 Redhat 系的 Linux,如:CentOS。</p></blockquote><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_">$ </span><span class="language-bash"><span class="hljs-built_in">cat</span> /etc/redhat-release</span><br>CentOS Linux release 7.2.1511 (Core)<br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>Linux</category>
<category>服务器</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>服务器</tag>
</tags>
</entry>
<entry>
<title>【Linux】:如何优雅的查看生产日志?</title>
<link href="/2019/12/19/linux/linux-log/"/>
<url>/2019/12/19/linux/linux-log/</url>
<content type="html"><![CDATA[<ul><li><p>我们都知道,生产环境下的日志迭代速度是非常快</p></li><li><p>倘若仅使用cat命令来查看日志文件,不但无法查看实时日志,严重的甚至还可能影响服务器的运行</p></li><li><p>本篇重点介绍:如何使用Linux命令优雅的查看日志,快速查询到想要的日志内容!</p></li></ul><span id="more"></span><blockquote><p>Github issues:<a href="https://github.com/littlejoyo/Blog/issues/">https://github.com/littlejoyo/Blog/issues/</a></p></blockquote><h1 id="1-tail-命令"><a href="#1-tail-命令" class="headerlink" title="1.tail 命令"></a>1.tail 命令</h1><blockquote><p>tail 命令可以查看实时日志的更新,当日志有更新的时候,实时打印到控制台显示。</p></blockquote><h2 id="1-1-常用命令"><a href="#1-1-常用命令" class="headerlink" title="1.1 常用命令"></a>1.1 常用命令</h2><ul><li><p><code>tail -f online.log</code> : 把生产日志文件里的最尾部的内容显示在屏幕上,并且不断刷新,只要文件更新就可以看到最新的文件内容。(停止显示:ctrl+c)</p></li><li><p><code>tail online.log</code> : 显示 online.log 文件的最后 10 行</p></li><li><p><code>tail -n 10 online.log</code> : 显示文件 online.log 的最后 10 行日志</p></li><li><p><code>tail -n +10 online.log</code> : 显示第10行之后的所有日志内容</p></li><li><p><code>tail -100f online.log</code> : 实时监控100行日志内容(不停刷新,保持100条日志)</p></li><li><p><code>tail -c 10 online.log</code> : 显示日志文件 online.log 的最后10个字符</p></li></ul><h2 id="1-2-tail-参数说明"><a href="#1-2-tail-参数说明" class="headerlink" title="1.2 tail 参数说明"></a>1.2 tail 参数说明</h2><table><thead><tr><th align="center">参数</th><th align="center">作用</th></tr></thead><tbody><tr><td align="center">-f</td><td align="center">循环读取</td></tr><tr><td align="center">-q</td><td align="center">不显示处理信息</td></tr><tr><td align="center">-v</td><td align="center">显示详细的处理信息</td></tr><tr><td align="center">-c <行数></td><td align="center">显示的字符数</td></tr><tr><td align="center">-n <行数></td><td align="center">显示文件的尾部n行内容</td></tr></tbody></table><h1 id="2-head命令"><a href="#2-head命令" class="headerlink" title="2.head命令"></a>2.head命令</h1><blockquote><p>head命令的用法和tail命令相似,但是前者从头部开始扫描内容,后者是从尾部开始输出内容</p></blockquote><h2 id="2-1-常用命令"><a href="#2-1-常用命令" class="headerlink" title="2.1 常用命令"></a>2.1 常用命令</h2><ul><li><p><code>head online.log</code> : 显示 online.log 文件的从头开始的前10 行日志内容</p></li><li><p><code>head -n 10 online.log</code> : 显示文件 online.log 的从首行开始的 10 行日志</p></li><li><p><code>head -n +100 online.log</code> : 显示第100行之前的所有日志内容</p></li><li><p><code>head -c 10 online.log</code> : 显示日志文件 online.log 的最开始的10个字符</p></li></ul><h1 id="3-grep-命令"><a href="#3-grep-命令" class="headerlink" title="3.grep 命令"></a>3.grep 命令</h1><blockquote><p>Linux grep 命令用于查找文件里符合条件的字符串。</p></blockquote><h2 id="3-1-常用命令"><a href="#3-1-常用命令" class="headerlink" title="3.1 常用命令"></a>3.1 常用命令</h2><ul><li><p><code>grep '12:00:00' online.log</code> : 打印符合<code>12:00:00</code>匹配行的日志内容</p></li><li><p><code>grep -n '12:00:00' online.log </code> : 打印符合<code>12:00:00</code>匹配行的日志内容并显示日志行数</p></li><li><p><code>grep -n -A 3 '12:00:00' online.log</code> : 打印匹配行的日志内容和之后3行的日志</p></li><li><p><code>grep -n -B 3 '12:00:00' online.log</code> : 打印匹配行的日志内容和之前3行的日志</p></li><li><p><code>grep -n -C 3 '12:00:00' online.log</code> : 打印匹配行的日志内容和前后各3行的日志</p></li><li><p><code>grep -e '正则表达式' online.log </code> : 从online.log中查找与正则表达式匹配的行</p></li><li><p><code>grep –i "被查找的字符串" oneline.log</code> : 查找时不区分大小写</p></li><li><p><code>grep '123' online.log | grep '456'</code> : 显示既匹配 ‘123’又匹配 ‘456’的行</p></li></ul><h2 id="3-2-grep-参数说明"><a href="#3-2-grep-参数说明" class="headerlink" title="3.2 grep 参数说明"></a>3.2 grep 参数说明</h2><p><a href="https://www.linuxcool.com/grep">grep命令和参数介绍</a></p><h1 id="4-sed-命令"><a href="#4-sed-命令" class="headerlink" title="4.sed 命令"></a>4.sed 命令</h1><blockquote><p>Linux sed 命令是利用脚本来处理文本文件。此处只说明如何用来查看具体时间段的日志内容。</p></blockquote><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs markdown">sed -n '/起始时间/,/结束时间/p' 日志文件<br>例如:<br>sed -n '/2019-12-17 12:00:00/,/2019-12-17 12:10:10/p' online.log<br>// 查看2019-12-17 12:00:00-12:10:10 时间段内的日志内容<br></code></pre></td></tr></table></figure><p><a href="https://www.linuxcool.com/sed">sed命令和参数介绍</a></p><h1 id="5-less-命令-和-more-命令"><a href="#5-less-命令-和-more-命令" class="headerlink" title="5.less 命令 和 more 命令"></a>5.less 命令 和 more 命令</h1><blockquote><p>less和more都可以对读取文件内容并进行分页处理,但是两者最大的区别就是less更加的灵活。</p></blockquote><h2 id="5-1-more"><a href="#5-1-more" class="headerlink" title="5.1 more"></a>5.1 more</h2><ul><li><p>more功能类似 cat ,cat命令是整个文件的内容从上到下显示在屏幕上。 more会以一页一页的显示方便使用者逐页阅读。</p></li><li><p>让画面在显示满一页时暂停,此时可按空格健继续显示下一个画面,或按Q键停止显示。</p></li><li><p>more不可以回去,就是不可以向前,只能向后,况且只能使用Enter和Space向后翻动。</p></li><li><p>more会读取整个文件的内容进行分页处理,加载速度低于less</p></li></ul><h2 id="5-2-less"><a href="#5-2-less" class="headerlink" title="5.2 less"></a>5.2 less</h2><h3 id="5-2-1-less-命令介绍"><a href="#5-2-1-less-命令介绍" class="headerlink" title="5.2.1 less 命令介绍"></a>5.2.1 less 命令介绍</h3><ul><li><p>less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大。</p></li><li><p>less与more最大的不同就是能够前后翻页,不必读取整个文件,加载速度比more更快。</p></li></ul><h3 id="5-2-2-less命令用法"><a href="#5-2-2-less命令用法" class="headerlink" title="5.2.2 less命令用法"></a>5.2.2 less命令用法</h3><ul><li><p><code>less -N online.log</code> : 查看online.log文件并显示行号,支持前后翻页</p></li><li><p><code>less -N +10g online.log</code> : 定位到online.log文件的第10行开始显示内容</p></li><li><p>less查看文件后,<code>/2019-12-19 10:00:00</code> : 自动高亮显示查找到的内容,后续n下一个,N上一个</p></li><li><p><code>cat -n online.log | grep 'debug' | less</code> : 分页显示过滤到的内容(less和more一般都会搭配管道符<code>|</code> 一起使用)</p></li></ul><h3 id="5-2-3-less命令参数"><a href="#5-2-3-less命令参数" class="headerlink" title="5.2.3 less命令参数"></a>5.2.3 less命令参数</h3><table><thead><tr><th>参数</th><th>详解</th></tr></thead><tbody><tr><td>d</td><td>向下翻页</td></tr><tr><td>u</td><td>向上翻页</td></tr><tr><td>g</td><td>跳到首行</td></tr><tr><td>G</td><td>跳到底部</td></tr><tr><td>?查找内容</td><td>向上查找匹配的内容并高亮显示</td></tr><tr><td>/ 查找内容</td><td>向下查找匹配的内容并高亮显示</td></tr><tr><td>n</td><td>下一个</td></tr><tr><td>N</td><td>上一个</td></tr><tr><td>q</td><td>退出less命令</td></tr></tbody></table><p><a href="https://www.linuxcool.com/less">less命令和参数介绍</a></p><h1 id="6-总结"><a href="#6-总结" class="headerlink" title="6.总结"></a>6.总结</h1><ul><li><p><code>tail</code> 、<code>grep</code>、<code>sed</code> 、<code>less</code> 等命令都可以实现日志的查看</p></li><li><p>不要再单纯使用<code>cat</code> 去查看日志文件,学会以上的命令灵活变通,根据需求选择合适的命令</p></li><li><p>大部分情况下,都会再加入管道符 <code>|</code> 进行多重条件的筛选,所以命令的应用会变得更加的灵活 </p></li><li><p>管道符 <code>|</code> 的作用 :<code>命令1|命令2</code> (将命令1的输出内容作为命令2的输入)</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">cat -n online.log | grep 'abc' | less<br></code></pre></td></tr></table></figure></li></ul><h1 id="微信公众号"><a href="#微信公众号" class="headerlink" title="微信公众号"></a>微信公众号</h1><blockquote><p>扫一扫关注Joyo说公众号,共同学习和研究开发技术。</p></blockquote><p><img src="https://i.loli.net/2020/01/11/HQT8NMsmDhIkXZv.png" alt="weixin-a"></p>]]></content>
<categories>
<category>Linux</category>
<category>服务器</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>服务器</tag>
<tag>日志</tag>
</tags>
</entry>
</search>