<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jiansoft.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jiansoft.net/" rel="alternate" type="text/html" /><updated>2025-06-03T01:28:48+00:00</updated><id>https://jiansoft.net/feed.xml</id><title type="html">Jiansoft</title><subtitle>Jiansoft, 专注于WiFi, Kernel, Network, AIoT, both software and hardware</subtitle><entry><title type="html">OFDM详解</title><link href="https://jiansoft.net/2025/05/22/understand_OFDM.html" rel="alternate" type="text/html" title="OFDM详解" /><published>2025-05-22T00:00:00+00:00</published><updated>2025-05-22T00:00:00+00:00</updated><id>https://jiansoft.net/2025/05/22/understand_OFDM</id><content type="html" xml:base="https://jiansoft.net/2025/05/22/understand_OFDM.html"><![CDATA[<h2 id="引言">引言</h2>

<p>众所周知，OFDM(Orthogonal Frequency Division Mutiplexing, 正交频分多址)是现代无线通信(WiFi和4G)的底层关键技术。本文对其作下探究。它主要是将某一单独信道转化为若干正交子信道，从而实现将高速数据信号转换成并行的低速子数据流，然后把每个子数据流调制到子信道上进行传输。</p>

<p>在早期发展的无线通信系统中，采用了单载波调制（Single-carrier Modulation）技术。单载波调制是将要传送的信号调制在一个载波中，然后通过天线进行传输。根据信号隐藏在载波的不同方面，可分为振幅调制(AM, ASK)、频率调制(FM, FSK)和相位调制(PM, PSK)。使用单载波调制技术的通信系统，在增加传输速率时需要更大的载波带宽，即较短的符元时间长度。而短符元长度对信道的抗延迟能力大大减弱，一旦遇到干扰，误码率可能较高。为了解决前述问题，发展出了多载波调制技术。</p>

<h2 id="无线通信系统模型">无线通信系统模型</h2>

<p>所谓通信就是信息的传递：
A地 –信息–&gt; B地</p>

<p>无线通信的基本链路为：</p>

<p>发射：s(t) –调制–&gt; s(t).cos(ωt) —-&gt; 天线</p>

<p>接收：s(t) &lt;–低通– .cos(ωt) &lt;–解调– &lt;—- 天线</p>

<p>s(t)为基带信号，cos(ωt)为载波信号。发射方向有两个核心部件，一个是调制器，一个是发射天线；接收方向有接收天线和解调器。</p>

<h3 id="为什么需要调制">为什么需要调制</h3>

<p>第一个原因：天线的尺寸和电磁波的波长差不多的时候，才能获得比较高的发射功率。</p>

<p>原始的电语音信号，其频率为声音的频率，为几KHz的量级。我们假设是10KHz，那么按照波长定义 λ = v*T = v/f = 光速/频率，10KHz电磁波对应的波长为：300000000m/s / 10000Hz = 30km。这么大的天线是制造不出来的，因此需要把信号调制到较高的频率，减小天线尺寸。</p>

<p>同时算一下2.4GHz和5GHz对应的波长：2.4GHz, 300000000/2400000000=12.5cm;
5GHz, 300000000/5000000000=6cm;
一般天线的最佳尺寸是波长的1/4或1/2.</p>

<p>第二个原因，频谱是受管理的，只能调制到特定的频率上去发送。同时调制到不同的频率范围内，实现多路复用。</p>

<p>在无线通信领域中，把低频信号s(t)称为基带信号，把调制后的射频信号称为已调信号。</p>

<h3 id="双边带信号单边带信号与iq调制">双边带信号、单边带信号与IQ调制</h3>

<p>我们讨论如下形式的已调信号：r(t)=s(t).cos(ωt)</p>

<p>基带信号s(t)是实数，由傅里叶变换的对称性可知，其频谱的幅度是偶函数，因此基带信号的频谱是关于零点对称的。这样就出现了负频率，负频率只有数学上的意义，并不实际占用带宽。但是基带信号调制到射频后，形成了关于ω的对称频谱，原来的负频率占用了实实在在的频率资源。我们把这种信号叫作双边带信号，ω左边的边带叫作下边带，右边的边带叫作上边带。</p>

<p>双边带信号占用了两倍的频率资源，造成了很大的浪费。</p>

<p>那怎么办呢？最简单的方法，就是把基带信号的频谱S(ω)砍掉一半，频谱函数为：S+(ω)=2S(ω).u(ω)。这里的u(ω)为阶跃函数，ω&lt;0时u(ω)=0，ω&gt;0时u(ω)=1。</p>

<p>然后对S+(ω)进行傅里叶逆变换，得到s+(t)。省去其中的数学过程，最终得出s+(t)=s(t)+js’(t)，s’(t)的定义这里略过。</p>

<p>于是发现，单边带信号是一个复数。进一步可以推理出，单边带信号同时利用了载波的幅度和相位，而双边带信号只利用了载波的幅度。或者等价地说，双边带信号只利用了余弦分量，而单边带信号同时利用了正弦和余弦两个正交分量。单边带信号利用了多一倍的信息，因此只需要一半的频谱资源。</p>

<p>如果我们独立地设置正弦和余弦分量，虽然带宽仍然是和双边带信号相同，但是传递的信息也增加了一倍，频谱效率和单边带信号是相同的。这个做法就是目前普遍使用的IQ调制技术，I指In-phase(同相), Q指Quadrature(正交)。</p>

<p>假设I路的信号为x(t), Q路的信号为y(t)，那么经过IQ调制后的信号为：
x(t).cos(ωt) - y(t).sin(ωt)。</p>

<p>接收端乘以cos(ωt)，滤掉倍频，解出x(t)；乘以-sin(ωt)，滤掉倍频，解出y(t)。</p>

<h3 id="数字调制">数字调制</h3>

<p>我们前面讲的调制是指将基带信号的频谱搬移到射频载波的过程。模拟通信的目的是为了传送一个模拟信号，数字通信的目的是为了传送数字信号。对于数字通信，调制的内涵在扩大，将<strong>数字信号映射成基带信号的过程</strong>也称为调制，称之为数字调制。</p>

<p>数字基带信号可用如下公式来表达：
s(t)=ΣIn.g(t-nTs)
首先，时间被划分为长度为Ts的的片段，每个片段被称为1个符号，Ts叫作符号长度，1/Ts叫作符号速率。</p>

<p>In是第n个要发送的符号，我们使用复基带信号的表达方法，In一般为负数，可以取M个离散值，从而一个符号可以表达log2M个bit，比特速率为：
log2M/Ts。</p>

<p>g(t)是一个实函数，叫作脉冲成型函数，起到基带滤波和控制带外泄露的作用。每个符号通过g(t)产生一个波形，把所有的符号的波形按照时间顺序累加起来，就得到了复基带信号s(t)。后续把s(t)调制到载波上的过程，就是模拟调制。</p>

<p>数字基带信号所占用的带宽和符号率是有关系的。直接说结论：数字复基带信号所占用的最小带宽是1/Ts，即符号速率(这个结论来自于奈奎斯特第一准则？)。根据这个结论，假设带宽是20MHz，那么最大符号速率为20MSymbol/s，如果1个符号可以取1024个离散值，即1个符号可以携带10个bit，那么bit速率上限为200Mb/s。</p>

<h4 id="几种数字调制方式">几种数字调制方式</h4>

<p>在脉冲成形函数g(t)确定的情况下，数字调制的主要任务就是研究如何把比特b映射到符号I。</p>

<p>所有数字调制方法都是这3种方式的组合：调幅(ASK)，调相(PSK)，调频(FSK)。</p>

<p>2ASK符号有两种可能的取值，代表1bit；4ASK符号有4种可能取值，代表2bit。ASK信号为实数，其射频信号为双边带信号。</p>

<p>BPSK QPSK 8PSK，1个符号分别可以代表1bit 2bit 3bit，即将相位平均分成2份、4份、8份。16PSK则相位距离太近，基本不会考虑。</p>

<p>QAM(quadrature amplitude modulation, 正交幅度调制)，指的是在实部和虚部两个正交维度上采用幅度调制，这比ASK只在实部维度上采用幅度调制有优势。</p>

<p>4QAM相当于实部和虚部上各2ASK，2x2=4种组合，可以表示2bit。16QAM就相当于实部和虚部上各4ASK，4x4=16种组合，可以表示4bit。</p>

<p>另外，要想信号传的远，就需要功放。理想的功放应该是这个样子：y(t)=k.x(t)。但实际的功放是非线性的，只在一定范围内是线性区域。如果输入信号的功率有起伏，那么信号的平均功率会被进一步压低。因此，峰均比是信号的一个重要指标：PAPR=Ppeak/Pavg。</p>

<p>如果只调频或调相得话，基带信号可以是恒包络的，即幅度是固定值，具有最低的PAPR值，对功放的要求最低。后面出现了一种新的线性功放技术，DPD(digital pre-distortion, 数字预失真)，即在功放的非线性区域，提前给信号一个失真处理，经过功放非线性区域后，信号变成线性放大，这样就较大程度地扩大了功放的线性范围。这样对信号的PAPR就没那么苛刻了，从而使得QAM调制成为当前数字调制的最大赢家。</p>

<h3 id="更完整的无线数字通信系统">更完整的无线数字通信系统</h3>

<p>本章开头描述的通信模型更偏向于基础的无线模拟通信系统，对于无线数字通信系统，更完整的模型如下：</p>

<p>信源-&gt;信源编码-&gt;信道编码-&gt;交织-&gt;数字调制-&gt;脉冲成形-&gt;模拟调制-&gt;发射天线</p>

<p>信宿&lt;-信源译码&lt;-信道译码&lt;-去交织&lt;-采样判决&lt;-模拟解调&lt;-接收天线</p>

<p>其中数字调制和脉冲成形前面已经讲过。
这里的信道编码和交织指的是：由于信道当中存在噪声，通信总会出现错误；如果在发射方通过编码的方法使得接收方能够正确解调，这种技术叫作前向纠错技术，也就是这里指的信道编码。信道编码的一般方法是：要传输k个bit，通过信道编码成n个bit，n&gt;k；接收端收到n个bit后，解码出k个信息bit。</p>

<h2 id="ofdm">OFDM</h2>

<p>从字面上看，OFDM采用了FDM技术。一般的FDM，为了避免载波之间相互干扰，增加了保护带宽，造成了频谱浪费。OFDM为了提高频谱利用率，采用了相互正交的子载波，子载波间不需要增加保护带宽。</p>

<p>OFDM的本质就是发送端用待调制的数据对一系列复指数信号进行加权，合成一个复信号，利用IQ调制发送出去，接收端通过IQ解调恢复出复信号，求出加权系数，也就是傅里叶系数，就得到了调制数据。在实际的通信系统中，一般使用IDFT来实现基带OFDM调制，使用DFT来实现基带OFDM解调。</p>

<p>具体数学细节，待未来研究。</p>

<h3 id="ofdm如何克服多径效应">OFDM如何克服多径效应</h3>

<p>多径效应会引起符号间干扰，而消除符号间干扰的技术称作均衡。</p>

<p>在OFDM技术里面，采用了循环前缀的技术(CP, cyclic prefix)。最终的发射序列为：
CP-OFDM Symbol-CP-OFDM Symbol</p>]]></content><author><name></name></author><summary type="html"><![CDATA[引言]]></summary></entry><entry><title type="html">程序编译和链接详解</title><link href="https://jiansoft.net/2023/03/13/understand_compiling_and_linking.html" rel="alternate" type="text/html" title="程序编译和链接详解" /><published>2023-03-13T00:00:00+00:00</published><updated>2023-03-13T00:00:00+00:00</updated><id>https://jiansoft.net/2023/03/13/understand_compiling_and_linking</id><content type="html" xml:base="https://jiansoft.net/2023/03/13/understand_compiling_and_linking.html"><![CDATA[<h2 id="引言">引言</h2>

<p>一直想探究下二进制程序在Linux系统上是如何加载执行的。本文先研究下程序是如何编译和链接的。</p>

<p>本文所用的gcc版本是5.4, Linux环境是VMware+Ubuntu16。</p>

<h2 id="程序编译">程序编译</h2>

<p>以如下代码为例。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//main.c</span>
<span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">array</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">};</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">val</span> <span class="o">=</span> <span class="n">sum</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
	<span class="k">return</span> <span class="n">val</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">//sum.c</span>
<span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">n</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">s</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">s</span> <span class="o">+=</span> <span class="n">a</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">s</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>编译分4步：</p>
<ul>
  <li>预处理： gcc -E main.c -o main.i</li>
  <li>编译：gcc -S main.i        //生成汇编代码，生成的文件为main.s</li>
  <li>汇编：gcc -c main.s        //生成一个可重定位目标文件，包括将汇编代码编译成机器码</li>
  <li>链接：gcc -o prog main.o sum.o        //生成可执行目标文件</li>
</ul>

<h2 id="静态链接">静态链接</h2>

<p>像Linux ld程序这样的静态链接器，以一组可重定位目标文件为输入，以一个完全链接的、可以加载和运行的可执行目标文件作为输出。
输入的可重定位目标文件由各种不同的代码和数据节组成，每一节都是一个连续的字节序列。</p>

<p>为了构造可执行文件，链接器必须完成两个主要任务：</p>
<ol>
  <li>符号解析：符号由目标文件定义和引用，每个符号对应函数、全局变量或一个静态变量。符号解析的目的是将每个符号的引用和这个符号的定义关联起来。</li>
  <li>重定位：编译器和汇编器生成从地址0开始的代码节和数据节，链接器通过把每个符号定义与一个真实内存位置关联起来，从而重定位这些节。然后修改所有对这些符号的引用，使得它们指向真实的内存位置。链接器使用汇编器产生的<strong>重定位条目</strong>，来执行这样的重定位。</li>
</ol>

<p>链接器对目标机器知之甚少，产生目标文件的编译器和汇编器已经完成了大部分工作。</p>

<h2 id="目标文件">目标文件</h2>

<p>目标文件有三种形式：</p>
<ul>
  <li>可重定位目标文件：包含代码和数据，可与其它可重定位目标文件合并起来，创建一个可执行目标文件</li>
  <li>可执行目标文件：包含代码和数据，可以被直接复制到内存并执行</li>
  <li>共享目标文件：一种特殊的可重定位目标文件，可以被动态的加载进内存并链接</li>
</ul>

<p>编译器和汇编器生成可重定位目标文件（包括共享目标文件），链接器生成可执行目标文件。</p>

<h2 id="elf格式">ELF格式</h2>
<p>ELF文件格式主要分为三部分，按顺序为：</p>

<ul>
  <li>ELF header</li>
  <li>各个section</li>
  <li>section header table</li>
</ul>

<p>典型的节(section)有：</p>
<ul>
  <li>.text: 代码段</li>
  <li>.rodata: 只读数据，比如字符串</li>
  <li>.data: 已初始化全局变量</li>
  <li>.bss: 未初始化全局变量</li>
  <li>.symtab: 不加-g选项也有，存放函数和全局变量信息</li>
</ul>

<h2 id="符号和符号表">符号和符号表</h2>

<p>每个可重定位目标文件都有一个符号表，它包含这个模块定义和引用的符号信息。在链接器的上下文中有三种不同的符号：</p>
<ol>
  <li>由本模块定义并被其它模块引用的全局符号。对应于非static的函数和非static的全局变量。</li>
  <li>由其它模块定义被本模块引用的全局符号。</li>
  <li>只被本模块定义和引用的局部符号，对应于带static属性的C函数和带static属性的全局变量。</li>
</ol>

<p>.symtab节包含符号表，这张符号表包含所有符号的条目，每个条目的结构如下：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="kt">int</span> <span class="n">name</span><span class="p">;</span>		<span class="c1">//符号名称</span>
	<span class="kt">char</span> <span class="n">type</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>	<span class="c1">//类型：函数或数据</span>
		 <span class="nl">binding:</span> <span class="mi">4</span><span class="p">;</span><span class="c1">//本地or全局</span>
	<span class="kt">char</span> <span class="n">reserved</span><span class="p">;</span>
	<span class="kt">short</span> <span class="n">section</span><span class="p">;</span>	<span class="c1">//属于那个节</span>
	<span class="kt">long</span> <span class="n">value</span><span class="p">;</span>		<span class="c1">//距离节起始位置的偏移；对于可执行文件则是绝对运行时地址</span>
	<span class="kt">long</span> <span class="n">size</span><span class="p">;</span>		<span class="c1">//目标大小</span>
<span class="p">}</span> <span class="n">Elf64_Symbol</span><span class="p">;</span>
</code></pre></div></div>

<h2 id="符号解析">符号解析</h2>

<p>符号解析即是链接器将一个引用与定义这个引用的目标文件的符号表中的一个符号对应起来。</p>

<h3 id="链接器如何解析多重定义的全局符号">链接器如何解析多重定义的全局符号</h3>

<p>链接器的输入是一组可重定位的目标模块。每个模块定义一组符号，有些是局部的，有些是全局的。
如果多个模块定义同名的全局符号，会发生什么呢？</p>

<p>下面是Linux编译系统采用的方法：在编译时，编译器向汇编器输出每个全局符号，或者是强，或者是弱。
汇编器将这个强弱信息编码在符号表里。
函数和已初始化的全局变量是强符号，未初始化的全局变量是弱符号。</p>

<p>Linux链接器用下面的规则来处理多重定义的符号：</p>
<ul>
  <li>规则1：不允许有多个同名的强符号</li>
  <li>规则2：如果同名的有一个强符号和多个若符号，选择强符号</li>
  <li>规则3：如果有多个弱符号同名，那么从这些若符号中任意选择一个</li>
</ul>

<h3 id="与静态库链接">与静态库链接</h3>

<p>编译系统还有一个机制，将所有相关的目标模块打包成一个单独的文件，称为静态库，它可以用做链接器的输入。</p>

<p>当链接器链接生成可执行文件时，它只复制静态库里被应用程序引用的模块。
也就是说，将多个模块打包成一个静态库文件，链接时链接器只复制被引用的模块。</p>

<p>在Linux系统中，静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合，
有一个头部用来描述每个成员目标文件的大小和位置。</p>

<h3 id="链接器如何使用静态库来解析引用">链接器如何使用静态库来解析引用</h3>

<p>在符号解析阶段，链接器按照输入顺序依次扫描输入的可重定位目标文件和存档文件。</p>

<p>扫描过程中，链接器维护一个可重定位目标文件集合E，一个未解析的符号集合U，以及已定义符合集合D。
初始时，E、U和D均为空。</p>
<ol>
  <li>如果被扫描的文件是可重定位目标文件，则将其加入E，同时修改U和D</li>
  <li>如果被扫描的文件是存档文件，链接器尝试匹配U和存档文件，如果某个存档文件成员和U匹配，
则将这个文档成员加入到E中，并修改U和D，直到U不再变化。</li>
  <li>如果连接器完成对所有文件的扫描后，U是非空的，那么链接器就会输出一个错误并终止。
否则合并E中的文件，输出可执行文件</li>
</ol>

<p>不幸的的是，这种算法使得输入文件的顺序很重要。如果库在前，目标文件在后，目标文件中引用的符号可能不被解析。</p>

<h2 id="重定位">重定位</h2>

<p>重定位要完成的工作：合并输入模块，为每个符号分配运行时地址。</p>

<p>重定位由两步组成：</p>
<ol>
  <li>
    <p>重定位节和符号定义。
在这一步中，链接器将所有同类型的节合并为同一类型的新的聚合节。例如所有目标模块中的.data节，合并成可执行文件的.data节。
然后，链接器将运行时内存地址赋给新的聚合节。这一步完成时，程序中的每条指令和全局变量都有唯一的运行时内存地址了。</p>
  </li>
  <li>
    <p>重定位节中的符号引用。
在这一步中，链接器修改代码节和数据节中对每个符号的引用，使得它们指向正确的运行时地址。
要执行这一步，链接器依赖于可重定位目标模块中称为重定位条目的数据结构。</p>
  </li>
</ol>

<h3 id="重定位条目">重定位条目</h3>

<p>当汇编器生成一个模块时，它并不知道数据和代码最终将放在内存中的什么位置，也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。
即本模块和其它模块定义的函数最终在内存中的位置都是未知的。</p>

<p>所以无论何时汇编器遇到未知位置的目标引用时，它就会生成一个重定位条目，告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
代码的重定位条目放在.rel.text中；已初始化数据的重定位条目在.rel.data中。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="kt">long</span> <span class="n">offset</span><span class="p">;</span>		<span class="c1">//这个要修改的条目的位置，相对节起始位置的偏移</span>
	<span class="kt">long</span> <span class="n">type</span><span class="o">:</span> <span class="mi">32</span><span class="p">,</span>		<span class="c1">//</span>
		 <span class="nl">symbol:</span> <span class="mi">32</span><span class="p">;</span>	<span class="c1">//引用的符号名称，对应符号表中的index</span>
	<span class="kt">long</span> <span class="n">addend</span><span class="p">;</span>		<span class="c1">//常数</span>
<span class="p">}</span> <span class="n">Elf64_Rela</span>
</code></pre></div></div>
<p>type有两种最基本的类型：</p>
<ul>
  <li>R_X86_64_PC32: 重定位一个相对地址的引用。即相对于当前地址的偏移量。</li>
  <li>R_X86_64_32：重定位一个绝对地址引用。</li>
</ul>

<h3 id="重定位符号引用">重定位符号引用</h3>
<p>下面是重定位符号的伪代码：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">foreach</span> <span class="n">section</span> <span class="n">s</span> <span class="p">{</span> <span class="c1">//遍历每个节</span>
	<span class="n">foreach</span> <span class="n">relocation</span> <span class="n">entry</span> <span class="n">r</span> <span class="p">{</span> <span class="c1">//遍历每个节的每个重定位条目</span>
		<span class="n">refptr</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="p">;</span>  <span class="c1">//计算引用所在的地址，即节的起始地址加上偏移</span>
		<span class="c1">//重定位一个相对地址的引用</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">R_X86_64_PC32</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">refaddr</span> <span class="o">=</span> <span class="n">ADDR</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">+</span> <span class="n">r</span><span class="p">.</span><span class="n">offset</span><span class="p">;</span>  <span class="c1">//引用所在的运行时地址，即节的运行时地址加上偏移</span>
			<span class="o">*</span><span class="n">refptr</span> <span class="o">=</span> <span class="n">ADDR</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">symbol</span><span class="p">)</span> <span class="o">+</span> <span class="n">r</span><span class="p">.</span><span class="n">addend</span> <span class="o">-</span> <span class="n">refaddr</span><span class="p">;</span>  <span class="c1">//引用的最终地址 - 引用所在地址，差值填到引用所在地址</span>
		<span class="p">}</span>
		<span class="c1">//重定位一个绝对引用</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">R_X86_64_32</span><span class="p">)</span> <span class="p">{</span>
			<span class="o">*</span><span class="n">refptr</span> <span class="o">=</span> <span class="n">ADDR</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">symbol</span><span class="p">)</span> <span class="o">+</span> <span class="n">r</span><span class="p">.</span><span class="n">addend</span>
		<span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>接下来举一个详细的例子：
以main.c为例，通过objdump -dx main.o产生main.o的反汇编：</p>
<pre><code class="language-asm">0000000000000000 &lt;main&gt;:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	48 83 ec 10          	sub    $0x10,%rsp
   8:	be 02 00 00 00       	mov    $0x2,%esi
   d:	bf 00 00 00 00       	mov    $0x0,%edi
			e: R_X86_64_32	array
  12:	e8 00 00 00 00       	callq  17 &lt;main+0x17&gt;
			13: R_X86_64_PC32	sum-0x4
  17:	89 45 fc             	mov    %eax,-0x4(%rbp)
  1a:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1d:	c9                   	leaveq 
  1e:	c3                   	retq 
</code></pre>
<p>可以看到main函数引用了两个全局符号，array和sum，也就是说main.o有两个重定位条目。
这两个条目的信息是：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>e: R_X86_64_32	array		//此引用位置相对于.text节的offset是e，类型是绝对地址，符号名是array
13: R_X86_64_PC32	sum-0x4	//此引用位置相对于.text节的offset是13，类型是相对地址，符号名是sum，常数是4
</code></pre></div></div>
<p>下面来看如何重定位这些引用：</p>

<p>1 重定位PC相对引用：</p>

<p>再还原下sum这个引用的重定位条目信息：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">r</span><span class="p">.</span><span class="n">offset</span> <span class="o">=</span> <span class="mh">0x13</span>
<span class="n">r</span><span class="p">.</span><span class="n">symbol</span> <span class="o">=</span> <span class="n">sum</span>
<span class="n">r</span><span class="p">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">R_X86_64_PC32</span>
<span class="n">r</span><span class="p">.</span><span class="n">addend</span> <span class="o">=</span> <span class="o">-</span><span class="mi">4</span>
</code></pre></div></div>
<p>假设链接器已确定ADDR(.text)=0x4004d6和ADDR(sum)=0x4004f5
根据上面讲的伪代码：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>refaddr = ADDR(s) + r.offset 
		= 0x4004d6 + 0x13
		= 0x4004e9
*refptr = ADDR(r.symbol) + r.addend - refaddr
		= 0x4004f5 - 4 - 0x4004e9
		= 0x8
</code></pre></div></div>
<p>即这里的call指令由e8 00 00 00 00，改为e8 08 00 00 00。
e8这条指令的位置是0x4004e8，当执行到这条指令时，PC指向下一条指令，这时PC的值为0x4004ed；
接下来将PC压入栈中；然后PC = PC + 0x8 = 0x4004ed + 0x8 = 0x4004f5，即sum符号的地址。</p>

<p>2 重定位绝对引用</p>

<p>还原下array这个引用的重定位条目信息：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">r</span><span class="p">.</span><span class="n">offset</span> <span class="o">=</span> <span class="n">e</span>
<span class="n">r</span><span class="p">.</span><span class="n">symbol</span> <span class="o">=</span> <span class="n">array</span>
<span class="n">r</span><span class="p">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">R_X86_64_32</span>
<span class="n">r</span><span class="p">.</span><span class="n">addend</span> <span class="o">=</span> <span class="mi">0</span>
</code></pre></div></div>
<p>假设链接器已确定ADDR(array) = 0x601030，那么</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>*refptr = ADDR(r.symbol) + r.addend = 0x601030
</code></pre></div></div>
<p>于是这里的指令bf 00 00 00 00再连接时会被修改为bf 30 10 60 00。</p>

<h2 id="加载可执行目标文件">加载可执行目标文件</h2>

<p>shell中输入./prog来执行目标文件prog，shell会调用加载器来运行它。
加载器将ELF文件的代码和数据从磁盘复制到内存中，然后跳转到该程序的入口点。</p>

<p>每个Linux程序都有一个运行时内存映像。在Linux x86-64系统中，代码段总是从地址0x400000处开始，后面是数据段。
运行时的堆在数据段之后，通过调用malloc库往上增长。堆后面的区域是为共享模块保留的。
用户栈总是从最大合法用户地址2^48-1开始，向较小内存地址增长。</p>

<h2 id="动态连接共享库so">动态连接共享库(.so)</h2>

<p>静态库有两个明显的缺点：</p>
<ol>
  <li>如果静态库更新了，则需要显示的重新链接生成目标程序</li>
  <li>静态库里的代码会被复制到最终的目标程序，这对内存是很大的浪费</li>
</ol>

<p>共享库是一个创新产物，共享库可以加载到任意内存地址，并和一个内存中的程序链接起来。这个过程叫动态链接。</p>

<p>创建共享库的命令：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc -shared -fpic -o libvector.so addvec.c multvec.c
</code></pre></div></div>
<p>这里-fpic是必须的，表示生成与位置无关的代码。</p>

<h2 id="位置无关代码">位置无关代码</h2>

<p>可以加载而无需重定位的代码称为位置无关代码，Position Independent Code, PIC。</p>

<p>在一个x86-64系统中，对同一个目标程序(模块)中的符号引用自然是PIC的，因为可以用相对寻址来编译这些引用。
然而对so中定义的符号的引用，需要一些特殊的技巧。</p>

<p>1 PIC数据引用</p>

<p>无论我们在内存中的何处加载一个共享目标模块，数据段与代码段的距离总是保持不变的。
因此，代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量。
要想生成对全局变量PIC引用的编译器利用了这个事实，它在数据段开始的地方创建了一个表，叫做全局偏移量表(GOT)。
在GOT中，每个全局数据引用对应一个8字节条目，编译器为每个条目生成一个重定位记录。加载时，动态链接器会重定位GOT中的每个条目。</p>

<p>2 PIC函数调用</p>

<p>假设程序调用一个共享库定义的函数，编译器没办法在编译时知道这个函数的运行时地址。
正常的方法是为该引用生成一个重定位记录，然后动态链接器在程序加载的时候再解析它。不过这种方法不是PIC，因为它需要链接器修改调用模块的代码段。</p>

<p>GNU编译系统使用一种称为延迟绑定的技术来解决这个问题，将函数地址的绑定推迟到第一次调用该函数时。
延迟绑定，在第一次调用共享库中的函数时，运行时开销很大，但是其后的每次调用都只会花费一条指令和一个间接的内存引用。</p>

<p>延迟绑定是通过两个数据结构之间的交互来实现的，这个两个数据结构是GOT(Global Offset Table)和PLT(Procedure Linkage Table)。如果一个目标模块调用了共享库中的任何函数，那么它就有自己的GOT和PLT。
GOT是数据段的一部分，PLT是代码段的一部分。</p>

<p>具体的PLT和GOT如何协作这里不再展开，总之PIC的思路是，代码上访问的符号是表的地址，动态链接器会在表中填入符号真正的地址。</p>]]></content><author><name></name></author><summary type="html"><![CDATA[引言 一直想探究下二进制程序在Linux系统上是如何加载执行的。本文先研究下程序是如何编译和链接的。 本文所用的gcc版本是5.4, Linux环境是VMware+Ubuntu16。 程序编译 以如下代码为例。 //main.c int sum(int *a, int n); int array[2] = {1, 2}; int main() { int val = sum(array, 2); return val; } //sum.c int sum(int *a, int n) { int i, s = 0; for (i = 0; i &lt; n; i++) { s += a[i]; } return s; } 编译分4步： 预处理： gcc -E main.c -o main.i 编译：gcc -S main.i //生成汇编代码，生成的文件为main.s 汇编：gcc -c main.s //生成一个可重定位目标文件，包括将汇编代码编译成机器码 链接：gcc -o prog main.o sum.o //生成可执行目标文件 静态链接 像Linux ld程序这样的静态链接器，以一组可重定位目标文件为输入，以一个完全链接的、可以加载和运行的可执行目标文件作为输出。 输入的可重定位目标文件由各种不同的代码和数据节组成，每一节都是一个连续的字节序列。 为了构造可执行文件，链接器必须完成两个主要任务： 符号解析：符号由目标文件定义和引用，每个符号对应函数、全局变量或一个静态变量。符号解析的目的是将每个符号的引用和这个符号的定义关联起来。 重定位：编译器和汇编器生成从地址0开始的代码节和数据节，链接器通过把每个符号定义与一个真实内存位置关联起来，从而重定位这些节。然后修改所有对这些符号的引用，使得它们指向真实的内存位置。链接器使用汇编器产生的重定位条目，来执行这样的重定位。 链接器对目标机器知之甚少，产生目标文件的编译器和汇编器已经完成了大部分工作。 目标文件 目标文件有三种形式： 可重定位目标文件：包含代码和数据，可与其它可重定位目标文件合并起来，创建一个可执行目标文件 可执行目标文件：包含代码和数据，可以被直接复制到内存并执行 共享目标文件：一种特殊的可重定位目标文件，可以被动态的加载进内存并链接 编译器和汇编器生成可重定位目标文件（包括共享目标文件），链接器生成可执行目标文件。 ELF格式 ELF文件格式主要分为三部分，按顺序为： ELF header 各个section section header table 典型的节(section)有： .text: 代码段 .rodata: 只读数据，比如字符串 .data: 已初始化全局变量 .bss: 未初始化全局变量 .symtab: 不加-g选项也有，存放函数和全局变量信息 符号和符号表 每个可重定位目标文件都有一个符号表，它包含这个模块定义和引用的符号信息。在链接器的上下文中有三种不同的符号： 由本模块定义并被其它模块引用的全局符号。对应于非static的函数和非static的全局变量。 由其它模块定义被本模块引用的全局符号。 只被本模块定义和引用的局部符号，对应于带static属性的C函数和带static属性的全局变量。 .symtab节包含符号表，这张符号表包含所有符号的条目，每个条目的结构如下： typedef struct { int name; //符号名称 char type: 4, //类型：函数或数据 binding: 4;//本地or全局 char reserved; short section; //属于那个节 long value; //距离节起始位置的偏移；对于可执行文件则是绝对运行时地址 long size; //目标大小 } Elf64_Symbol; 符号解析 符号解析即是链接器将一个引用与定义这个引用的目标文件的符号表中的一个符号对应起来。 链接器如何解析多重定义的全局符号 链接器的输入是一组可重定位的目标模块。每个模块定义一组符号，有些是局部的，有些是全局的。 如果多个模块定义同名的全局符号，会发生什么呢？ 下面是Linux编译系统采用的方法：在编译时，编译器向汇编器输出每个全局符号，或者是强，或者是弱。 汇编器将这个强弱信息编码在符号表里。 函数和已初始化的全局变量是强符号，未初始化的全局变量是弱符号。 Linux链接器用下面的规则来处理多重定义的符号： 规则1：不允许有多个同名的强符号 规则2：如果同名的有一个强符号和多个若符号，选择强符号 规则3：如果有多个弱符号同名，那么从这些若符号中任意选择一个 与静态库链接 编译系统还有一个机制，将所有相关的目标模块打包成一个单独的文件，称为静态库，它可以用做链接器的输入。 当链接器链接生成可执行文件时，它只复制静态库里被应用程序引用的模块。 也就是说，将多个模块打包成一个静态库文件，链接时链接器只复制被引用的模块。 在Linux系统中，静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合， 有一个头部用来描述每个成员目标文件的大小和位置。 链接器如何使用静态库来解析引用 在符号解析阶段，链接器按照输入顺序依次扫描输入的可重定位目标文件和存档文件。 扫描过程中，链接器维护一个可重定位目标文件集合E，一个未解析的符号集合U，以及已定义符合集合D。 初始时，E、U和D均为空。 如果被扫描的文件是可重定位目标文件，则将其加入E，同时修改U和D 如果被扫描的文件是存档文件，链接器尝试匹配U和存档文件，如果某个存档文件成员和U匹配， 则将这个文档成员加入到E中，并修改U和D，直到U不再变化。 如果连接器完成对所有文件的扫描后，U是非空的，那么链接器就会输出一个错误并终止。 否则合并E中的文件，输出可执行文件 不幸的的是，这种算法使得输入文件的顺序很重要。如果库在前，目标文件在后，目标文件中引用的符号可能不被解析。 重定位 重定位要完成的工作：合并输入模块，为每个符号分配运行时地址。 重定位由两步组成： 重定位节和符号定义。 在这一步中，链接器将所有同类型的节合并为同一类型的新的聚合节。例如所有目标模块中的.data节，合并成可执行文件的.data节。 然后，链接器将运行时内存地址赋给新的聚合节。这一步完成时，程序中的每条指令和全局变量都有唯一的运行时内存地址了。 重定位节中的符号引用。 在这一步中，链接器修改代码节和数据节中对每个符号的引用，使得它们指向正确的运行时地址。 要执行这一步，链接器依赖于可重定位目标模块中称为重定位条目的数据结构。 重定位条目 当汇编器生成一个模块时，它并不知道数据和代码最终将放在内存中的什么位置，也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。 即本模块和其它模块定义的函数最终在内存中的位置都是未知的。 所以无论何时汇编器遇到未知位置的目标引用时，它就会生成一个重定位条目，告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。 代码的重定位条目放在.rel.text中；已初始化数据的重定位条目在.rel.data中。 typedef struct { long offset; //这个要修改的条目的位置，相对节起始位置的偏移 long type: 32, // symbol: 32; //引用的符号名称，对应符号表中的index long addend; //常数 } Elf64_Rela type有两种最基本的类型： R_X86_64_PC32: 重定位一个相对地址的引用。即相对于当前地址的偏移量。 R_X86_64_32：重定位一个绝对地址引用。 重定位符号引用 下面是重定位符号的伪代码： foreach section s { //遍历每个节 foreach relocation entry r { //遍历每个节的每个重定位条目 refptr = s + r.offset; //计算引用所在的地址，即节的起始地址加上偏移 //重定位一个相对地址的引用 if (r.type == R_X86_64_PC32) { refaddr = ADDR(s) + r.offset; //引用所在的运行时地址，即节的运行时地址加上偏移 *refptr = ADDR(r.symbol) + r.addend - refaddr; //引用的最终地址 - 引用所在地址，差值填到引用所在地址 } //重定位一个绝对引用 if (r.type == R_X86_64_32) { *refptr = ADDR(r.symbol) + r.addend } } } 接下来举一个详细的例子： 以main.c为例，通过objdump -dx main.o产生main.o的反汇编： 0000000000000000 &lt;main&gt;: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: be 02 00 00 00 mov $0x2,%esi d: bf 00 00 00 00 mov $0x0,%edi e: R_X86_64_32 array 12: e8 00 00 00 00 callq 17 &lt;main+0x17&gt; 13: R_X86_64_PC32 sum-0x4 17: 89 45 fc mov %eax,-0x4(%rbp) 1a: 8b 45 fc mov -0x4(%rbp),%eax 1d: c9 leaveq 1e: c3 retq 可以看到main函数引用了两个全局符号，array和sum，也就是说main.o有两个重定位条目。 这两个条目的信息是： e: R_X86_64_32 array //此引用位置相对于.text节的offset是e，类型是绝对地址，符号名是array 13: R_X86_64_PC32 sum-0x4 //此引用位置相对于.text节的offset是13，类型是相对地址，符号名是sum，常数是4 下面来看如何重定位这些引用： 1 重定位PC相对引用： 再还原下sum这个引用的重定位条目信息： r.offset = 0x13 r.symbol = sum r.type = R_X86_64_PC32 r.addend = -4 假设链接器已确定ADDR(.text)=0x4004d6和ADDR(sum)=0x4004f5 根据上面讲的伪代码： refaddr = ADDR(s) + r.offset = 0x4004d6 + 0x13 = 0x4004e9 *refptr = ADDR(r.symbol) + r.addend - refaddr = 0x4004f5 - 4 - 0x4004e9 = 0x8 即这里的call指令由e8 00 00 00 00，改为e8 08 00 00 00。 e8这条指令的位置是0x4004e8，当执行到这条指令时，PC指向下一条指令，这时PC的值为0x4004ed； 接下来将PC压入栈中；然后PC = PC + 0x8 = 0x4004ed + 0x8 = 0x4004f5，即sum符号的地址。 2 重定位绝对引用 还原下array这个引用的重定位条目信息： r.offset = e r.symbol = array r.type = R_X86_64_32 r.addend = 0 假设链接器已确定ADDR(array) = 0x601030，那么 *refptr = ADDR(r.symbol) + r.addend = 0x601030 于是这里的指令bf 00 00 00 00再连接时会被修改为bf 30 10 60 00。 加载可执行目标文件 shell中输入./prog来执行目标文件prog，shell会调用加载器来运行它。 加载器将ELF文件的代码和数据从磁盘复制到内存中，然后跳转到该程序的入口点。 每个Linux程序都有一个运行时内存映像。在Linux x86-64系统中，代码段总是从地址0x400000处开始，后面是数据段。 运行时的堆在数据段之后，通过调用malloc库往上增长。堆后面的区域是为共享模块保留的。 用户栈总是从最大合法用户地址2^48-1开始，向较小内存地址增长。 动态连接共享库(.so) 静态库有两个明显的缺点： 如果静态库更新了，则需要显示的重新链接生成目标程序 静态库里的代码会被复制到最终的目标程序，这对内存是很大的浪费 共享库是一个创新产物，共享库可以加载到任意内存地址，并和一个内存中的程序链接起来。这个过程叫动态链接。 创建共享库的命令： gcc -shared -fpic -o libvector.so addvec.c multvec.c 这里-fpic是必须的，表示生成与位置无关的代码。 位置无关代码 可以加载而无需重定位的代码称为位置无关代码，Position Independent Code, PIC。 在一个x86-64系统中，对同一个目标程序(模块)中的符号引用自然是PIC的，因为可以用相对寻址来编译这些引用。 然而对so中定义的符号的引用，需要一些特殊的技巧。 1 PIC数据引用 无论我们在内存中的何处加载一个共享目标模块，数据段与代码段的距离总是保持不变的。 因此，代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量。 要想生成对全局变量PIC引用的编译器利用了这个事实，它在数据段开始的地方创建了一个表，叫做全局偏移量表(GOT)。 在GOT中，每个全局数据引用对应一个8字节条目，编译器为每个条目生成一个重定位记录。加载时，动态链接器会重定位GOT中的每个条目。 2 PIC函数调用 假设程序调用一个共享库定义的函数，编译器没办法在编译时知道这个函数的运行时地址。 正常的方法是为该引用生成一个重定位记录，然后动态链接器在程序加载的时候再解析它。不过这种方法不是PIC，因为它需要链接器修改调用模块的代码段。 GNU编译系统使用一种称为延迟绑定的技术来解决这个问题，将函数地址的绑定推迟到第一次调用该函数时。 延迟绑定，在第一次调用共享库中的函数时，运行时开销很大，但是其后的每次调用都只会花费一条指令和一个间接的内存引用。 延迟绑定是通过两个数据结构之间的交互来实现的，这个两个数据结构是GOT(Global Offset Table)和PLT(Procedure Linkage Table)。如果一个目标模块调用了共享库中的任何函数，那么它就有自己的GOT和PLT。 GOT是数据段的一部分，PLT是代码段的一部分。 具体的PLT和GOT如何协作这里不再展开，总之PIC的思路是，代码上访问的符号是表的地址，动态链接器会在表中填入符号真正的地址。]]></summary></entry><entry><title type="html">使用memwatch检测内存越界</title><link href="https://jiansoft.net/2023/02/22/use_memwatch_to_detect_out-of-bounds_memory_access.html" rel="alternate" type="text/html" title="使用memwatch检测内存越界" /><published>2023-02-22T00:00:00+00:00</published><updated>2023-02-22T00:00:00+00:00</updated><id>https://jiansoft.net/2023/02/22/use_memwatch_to_detect_out-of-bounds_memory_access</id><content type="html" xml:base="https://jiansoft.net/2023/02/22/use_memwatch_to_detect_out-of-bounds_memory_access.html"><![CDATA[<h2 id="引言">引言</h2>

<p>最近在嵌入式Linux环境下，遇到了一个第三方程序概率性段错误退出的问题。</p>

<p>通过分析程序异常退出后产生的coredump文件，发现程序是挂在malloc调用里：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#0  unbin (c=0x77a85fa8, i=1) at src/malloc/malloc.c:228
#1  0x77adf254 in malloc (n=32) at src/malloc/malloc.c:356
#2  0x77a45037 in switch_create_table_entry (port_index=4 '\004', age=0 '\000', mac=0x0, vid=&lt;optimized out&gt;) at mediatek/switch_layer.c:615
#3  0x77a45341 in switch_layer_get_table_entry (entry_list=&lt;optimized out&gt;) at mediatek/switch_layer.c:1077
#4  0x00454745 in dl_list_del (item=0x48c6dc &lt;cmd_buf+56&gt;) at ethernet/list.h:44
...
</code></pre></div></div>

<p>网上搜索了下类似的调用栈，基本结论是这个错误发生前有内存越界写入，导致malloc维护的内存块信息出错，进而导致此次的malloc出现段错误。</p>

<p>所以问题明确了：<strong>如何找到代码中的内存越界写入？</strong></p>

<p>当然，我是有这个第三方程序源码的。于是想阅读源码，排查每一处malloc之后的内存读写代码，但鉴于代码规模庞大，人工排查无异于大海捞针，不具可行性。</p>

<p>继续网上搜索，发现了memwatch工具，最终问题得以解决。本文记录下使用memwatch的过程。</p>

<h2 id="memwatch介绍">memwatch介绍</h2>

<p>memwatch可以用来检测内存泄漏和内存越界写入。</p>

<p>从网上找到的最新版本是2.71. https://sourceforge.net/projects/memwatch/files/memwatch/</p>

<p>memwatch源码只有两个文件：memwatch.h和memwatch.c。使用起来也很简单，参见源码里的README，只需要三步：</p>

<ol>
  <li>所有的源文件都要包含memwatch.h文件</li>
  <li>打开MEMWATCH预编译宏，重新编译程序；在gcc中，就是增加编译选项-DMEMWATCH</li>
  <li>运行程序，检查生成的memwatch.log文件</li>
</ol>

<h2 id="调试记录">调试记录</h2>

<h3 id="testc">test.c</h3>

<p>先试一下memwatch源码自带的test.c。解压下载下来的源码文件mw271.zip，里面有一个test.c。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//注释掉test.c的最后一行，然后编译
gcc -o test -DMEMWATCH -DMEMWATCH_STDIO test.c memwatch.c
//运行程序
./test
//检查生成的memwatch.log文件
</code></pre></div></div>

<p>简单介绍下memwatch.log中的内容：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>underflow: &lt;5&gt; test.c(62), 200 bytes alloc'd at &lt;4&gt; test.c(60)
//underflow表示内存向下越界写入。即test.c的第62行，检测到test.c第60行申请的200Byte内存有向下越界写入

unfreed: &lt;3&gt; test.c(59), 20 bytes at 0x1291310
//unfreed表示未释放的内存。即test.c第59行申请的20Byte内存未释放

//再举一个overflow的例子，即向上越界写入，同underflow
overflow: &lt;35670&gt; src/cmdu_message_parse.c(3258), 2 bytes alloc'd at &lt;35467&gt; src/multi_ap.c(4955)
</code></pre></div></div>

<h3 id="实际问题调试">实际问题调试</h3>

<p>我在文章开头提到的第三方程序加入memwatch.c和memwatch.h，实际调试时遇到了下面的问题：</p>

<ol>
  <li>程序运行环境是mips平台，编译有遇到错误</li>
  <li>第三方程序是多线程的，多线程需要额外打开预编译宏MW_PTHREADS。但打开后会遇到死锁问题</li>
  <li>段错误后，程序是直接退出的，并不会刷新memwatch.log</li>
</ol>

<p>根据实际遇到的问题，我修改了memwatch.c的源码，修改后的代码见github(github上可以查看修改记录)。</p>

<p>github: <a href="https://github.com/jian-soft/memwatch">https://github.com/jian-soft/memwatch</a></p>

<p>最终我的修改如下：</p>

<ol>
  <li>使用修改后的memwatch.c，将其编译到程序中</li>
  <li>增加预编译宏-DMEMWATCH -DMW_PTHREADS</li>
  <li>所有的.c文件开头增加:
    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifdef MEMWATCH
#include</span> <span class="cpf">"memwatch.h"</span><span class="cp">
#endif
</span></code></pre></div>    </div>
  </li>
  <li>在main()里增加对SIGSEGV的捕获，发生SIGSEGV后主动退出程序，触发memwatch.log刷新:</li>
</ol>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">sigsegv_handler_fun</span><span class="p">(</span><span class="kt">int</span> <span class="n">signum</span><span class="p">)</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="n">cmd</span><span class="p">[</span><span class="mi">64</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
    <span class="n">sprintf</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="s">"echo catch sigsegv &gt; /tmp/dddd"</span><span class="p">);</span>
    <span class="n">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
    <span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="p">...</span>
    <span class="n">signal</span><span class="p">(</span><span class="n">SIGSEGV</span><span class="p">,</span> <span class="n">sigsegv_handler_fun</span><span class="p">);</span>
    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>之后编译运行程序，坐等问题复现。果然，复现后，memwatch.log里出现了overflow的信息。</p>

<p>根据overflow信息，排查对应代码行，找到了内存越界写入的bug。</p>

<hr />

<p>参考文章：</p>

<p><a href="https://www.linuxjournal.com/article/6059">https://www.linuxjournal.com/article/6059</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[引言 最近在嵌入式Linux环境下，遇到了一个第三方程序概率性段错误退出的问题。 通过分析程序异常退出后产生的coredump文件，发现程序是挂在malloc调用里： #0 unbin (c=0x77a85fa8, i=1) at src/malloc/malloc.c:228 #1 0x77adf254 in malloc (n=32) at src/malloc/malloc.c:356 #2 0x77a45037 in switch_create_table_entry (port_index=4 '\004', age=0 '\000', mac=0x0, vid=&lt;optimized out&gt;) at mediatek/switch_layer.c:615 #3 0x77a45341 in switch_layer_get_table_entry (entry_list=&lt;optimized out&gt;) at mediatek/switch_layer.c:1077 #4 0x00454745 in dl_list_del (item=0x48c6dc &lt;cmd_buf+56&gt;) at ethernet/list.h:44 ... 网上搜索了下类似的调用栈，基本结论是这个错误发生前有内存越界写入，导致malloc维护的内存块信息出错，进而导致此次的malloc出现段错误。 所以问题明确了：如何找到代码中的内存越界写入？ 当然，我是有这个第三方程序源码的。于是想阅读源码，排查每一处malloc之后的内存读写代码，但鉴于代码规模庞大，人工排查无异于大海捞针，不具可行性。 继续网上搜索，发现了memwatch工具，最终问题得以解决。本文记录下使用memwatch的过程。 memwatch介绍 memwatch可以用来检测内存泄漏和内存越界写入。 从网上找到的最新版本是2.71. https://sourceforge.net/projects/memwatch/files/memwatch/ memwatch源码只有两个文件：memwatch.h和memwatch.c。使用起来也很简单，参见源码里的README，只需要三步： 所有的源文件都要包含memwatch.h文件 打开MEMWATCH预编译宏，重新编译程序；在gcc中，就是增加编译选项-DMEMWATCH 运行程序，检查生成的memwatch.log文件 调试记录 test.c 先试一下memwatch源码自带的test.c。解压下载下来的源码文件mw271.zip，里面有一个test.c。 //注释掉test.c的最后一行，然后编译 gcc -o test -DMEMWATCH -DMEMWATCH_STDIO test.c memwatch.c //运行程序 ./test //检查生成的memwatch.log文件 简单介绍下memwatch.log中的内容： underflow: &lt;5&gt; test.c(62), 200 bytes alloc'd at &lt;4&gt; test.c(60) //underflow表示内存向下越界写入。即test.c的第62行，检测到test.c第60行申请的200Byte内存有向下越界写入 unfreed: &lt;3&gt; test.c(59), 20 bytes at 0x1291310 //unfreed表示未释放的内存。即test.c第59行申请的20Byte内存未释放 //再举一个overflow的例子，即向上越界写入，同underflow overflow: &lt;35670&gt; src/cmdu_message_parse.c(3258), 2 bytes alloc'd at &lt;35467&gt; src/multi_ap.c(4955) 实际问题调试 我在文章开头提到的第三方程序加入memwatch.c和memwatch.h，实际调试时遇到了下面的问题： 程序运行环境是mips平台，编译有遇到错误 第三方程序是多线程的，多线程需要额外打开预编译宏MW_PTHREADS。但打开后会遇到死锁问题 段错误后，程序是直接退出的，并不会刷新memwatch.log 根据实际遇到的问题，我修改了memwatch.c的源码，修改后的代码见github(github上可以查看修改记录)。 github: https://github.com/jian-soft/memwatch 最终我的修改如下： 使用修改后的memwatch.c，将其编译到程序中 增加预编译宏-DMEMWATCH -DMW_PTHREADS 所有的.c文件开头增加: #ifdef MEMWATCH #include "memwatch.h" #endif 在main()里增加对SIGSEGV的捕获，发生SIGSEGV后主动退出程序，触发memwatch.log刷新: void sigsegv_handler_fun(int signum) { char cmd[64] = {0}; sprintf(cmd, "echo catch sigsegv &gt; /tmp/dddd"); system(cmd); exit(0); } int main(int argc, char *argv[]) { ... signal(SIGSEGV, sigsegv_handler_fun); ... } 之后编译运行程序，坐等问题复现。果然，复现后，memwatch.log里出现了overflow的信息。 根据overflow信息，排查对应代码行，找到了内存越界写入的bug。 参考文章： https://www.linuxjournal.com/article/6059]]></summary></entry><entry><title type="html">算法学习之AVL树(平衡二叉排序树)</title><link href="https://jiansoft.net/2022/08/30/learn_alogrithm_avl_tree.html" rel="alternate" type="text/html" title="算法学习之AVL树(平衡二叉排序树)" /><published>2022-08-30T00:00:00+00:00</published><updated>2022-08-30T00:00:00+00:00</updated><id>https://jiansoft.net/2022/08/30/learn_alogrithm_avl_tree</id><content type="html" xml:base="https://jiansoft.net/2022/08/30/learn_alogrithm_avl_tree.html"><![CDATA[<h2 id="引言">引言</h2>

<p>最近打算补充下算法方面的知识，开一个《算法学习》专栏。</p>

<p>熟悉OpenWRT Linux系统的朋友都知道，OpenWRT里有一个基础库叫libubox，libubox是一些常用的C库的集合。
最常用的就是双向链表库”list.h”，和内核的链表头文件&lt;linux/list.h&gt;类似。此外还有md5、base64等常用工具库。
libubox中也有AVL库，本文基于libubox的AVL库代码，学习下AVL树的结构和相关操作。
当然不熟悉OpenWRT或libubox库完全不影响阅读本文。</p>

<h2 id="什么是avl树">什么是AVL树</h2>

<p>什么是AVL树呢？AVL树是<strong>平衡二叉排序树</strong>，是以它的发现者Adelson-Velsky和Landis的名字命名的。</p>

<p>首先什么是<strong>二叉树</strong>：二叉树的每个节点至多只有两棵子树，并且子树有左右之分。</p>

<p>再看<strong>平衡二叉树</strong>：平衡二叉树的左子树和右子树都是平衡二叉树，且左右子树的深度之差的绝对值不超过1。（树的深度即从根节点到叶子节点的距离或步数）</p>

<p>再看<strong>二叉排序树</strong>：它的左子树如果不为空，则左子树上的所有节点的值均小于它的根节点的值；它的右子树如果不为空，
则右子树上所有节点的值均大于它根节点的值；它的左右子树也分别是二叉排序数。</p>

<p>二叉排序树又可以称为二叉查找树，所以平衡二叉排序树的英文全称可以是Balanced Binary Search Tree，简称BBST。</p>

<p>平衡二叉排序树通常用在动态数据表查找上。动态数据表的意思是要查找的数据表是动态生成的，不是一个静态的数据表。
这是因为对一个平衡二叉排序树的查找操作，类似于静态排序表的折半查找，效率是很高的，其时间复杂度为O(log n)。
这一点大家手画一个二叉排序树比划一下，就能感受出来。（严格数学证明不在本文范畴内^_^）</p>

<p>AVL树最大的特性是<strong>自平衡性</strong>。这里自平衡的意思是，当AVL树因为插入一个节点而失去平衡时，
只需对节点进行有限且有规律的被称为“左旋”或“右旋”的操作，就可以使树再次平衡。</p>

<h2 id="avl库代码学习">AVL库代码学习</h2>

<p>代码基于libubox的AVL库：libubox/avl.h, libubox/avl.c</p>

<h3 id="avl树对象定义">AVL树对象定义</h3>

<p>先看下AVL树对象的定义：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//AVL节点对象</span>
<span class="k">struct</span> <span class="n">avl_node</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">list_head</span> <span class="n">list</span><span class="p">;</span> <span class="c1">//libubox的AVL库将所有节点串成一个双向链表</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">parent</span><span class="p">;</span> <span class="c1">//指向父节点</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">left</span><span class="p">;</span> <span class="c1">//左子节点</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">right</span><span class="p">;</span> <span class="c1">//右子节点</span>
    <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">key</span><span class="p">;</span> <span class="c1">//节点存储的值</span>
    <span class="kt">signed</span> <span class="kt">char</span> <span class="n">balance</span><span class="p">;</span> <span class="c1">//平衡因子: 取值0 -1 +1</span>
                         <span class="c1">//-1表示左子树深度大1，+1表示右子树深度大1，0表示一样大</span>
    <span class="n">bool</span> <span class="n">leader</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">//AVL树对象</span>
<span class="k">typedef</span> <span class="nf">int</span> <span class="p">(</span><span class="o">*</span><span class="n">avl_tree_comp</span><span class="p">)</span> <span class="p">(</span><span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">k1</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">k2</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">avl_tree</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">list_head</span> <span class="n">list_head</span><span class="p">;</span> <span class="c1">//libubox的AVL库将所有节点串成一个有序双向链表，方便遍历</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">root</span><span class="p">;</span> <span class="c1">//树的根节点</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">count</span><span class="p">;</span> <span class="c1">//树中节点的个数</span>
    <span class="n">bool</span> <span class="n">allow_dups</span><span class="p">;</span> <span class="c1">//树中是否允许key相同的节点存在，libubox的AVL库支持插入相同的节点</span>
    <span class="n">avl_tree_comp</span> <span class="n">comp</span><span class="p">;</span> <span class="c1">//节点大小比较函数</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">cmp_ptr</span><span class="p">;</span> <span class="c1">//比较函数的第三个入参</span>
<span class="p">};</span>
</code></pre></div></div>

<p>接下来看AVL树的3个主要方法：查找、插入、删除。</p>

<h3 id="查找节点-avl_find">查找节点 avl_find()</h3>

<p>查找节点：avl_find()</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//在一棵AVL树中，查找值为key的节点，找到则返回找到的节点，找不到则返回空</span>
<span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">avl_find</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">key</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">node</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">diff</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">//树为空则直接返回空</span>
    <span class="c1">//递归查找节点。入参是节点、key值、比较函数，出参是比较值</span>
    <span class="n">node</span> <span class="o">=</span> <span class="n">avl_find_rec</span><span class="p">(</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">tree</span><span class="o">-&gt;</span><span class="n">comp</span><span class="p">,</span> <span class="n">tree</span><span class="o">-&gt;</span><span class="n">cmp_ptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">diff</span><span class="p">)</span>
        <span class="kt">int</span> <span class="n">diff</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="n">comp</span><span class="p">)</span> <span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">cmp_ptr</span><span class="p">);</span> <span class="c1">//计算传入的key和节点的key的差值</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">//如果差值小于0，即匹配的key值应该在节点的左子树，继续去左子树寻找</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
                <span class="k">return</span> <span class="nf">avl_find_rec</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">comp</span><span class="p">,</span> <span class="n">cmp_ptr</span><span class="p">,</span> <span class="n">cmp_result</span><span class="p">);</span>
            <span class="k">else</span>
                <span class="k">return</span> <span class="n">node</span> <span class="c1">//如果没有左子树了，则返回这个叶节点</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">//如果差值大于0，即匹配的key值应该在节点的右子树，继续去右子树寻找</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
                <span class="k">return</span> <span class="n">avl_find_rec</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">comp</span><span class="p">,</span> <span class="n">cmp_ptr</span><span class="p">,</span> <span class="n">cmp_result</span><span class="p">)</span>
            <span class="k">else</span>
                <span class="k">return</span> <span class="n">node</span> <span class="c1">//如果没有右子树了，则返回这个叶节点</span>
        <span class="k">return</span> <span class="n">node</span> <span class="c1">//如果差值等于0，则返回这个匹配的节点</span>

    <span class="k">return</span> <span class="n">diff</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="n">node</span> <span class="o">:</span> <span class="nb">NULL</span> <span class="c1">//如果差值不等于0，表示没找到匹配的节点，返回空</span>
</code></pre></div></div>

<h3 id="插入节点-avl_insert">插入节点 avl_insert()</h3>

<p>插入节点：avl_insert()。本节包含AVL树的左旋或右旋操作，是AVL树的关键特性所在。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">avl_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">new</span><span class="p">)</span>
    <span class="c1">//对要插入new节点除了key属性以外的其它属性赋初值</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">leader</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="c1">//如果这个树没有节点存在</span>
        <span class="n">list_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">new</span><span class="o">-&gt;</span><span class="n">list</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">list_head</span><span class="p">)</span> <span class="c1">//new节点直接插入树中</span>
        <span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span> <span class="o">=</span> <span class="n">new</span> <span class="c1">//树的根节点指向这个new节点</span>
        <span class="n">tree</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1">//此时树的节点个数变为1</span>
        <span class="k">return</span> <span class="mi">0</span>

    <span class="c1">//递归查找与new节点相同或相近的节点node</span>
    <span class="n">node</span> <span class="o">=</span> <span class="n">avl_find_rec</span><span class="p">(</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span><span class="p">,</span> <span class="n">new</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">tree</span><span class="o">-&gt;</span><span class="n">comp</span><span class="p">,</span> <span class="n">tree</span><span class="o">-&gt;</span><span class="n">cmp_ptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">diff</span><span class="p">);</span>
    <span class="n">last</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>

    <span class="c1">//遍历last的next节点，即如果有key值相同的节点，取所有相同key值节点的最后一个</span>
    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">list_is_last</span><span class="p">(</span><span class="o">&amp;</span><span class="n">last</span><span class="o">-&gt;</span><span class="n">list</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">list_head</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">next</span> <span class="o">=</span> <span class="n">avl_next</span><span class="p">(</span><span class="n">last</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">next</span><span class="o">-&gt;</span><span class="n">leader</span><span class="p">)</span>
          <span class="k">break</span><span class="p">;</span>
        <span class="n">last</span> <span class="o">=</span> <span class="n">next</span><span class="p">;</span>

    <span class="n">diff</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">comp</span><span class="p">)</span> <span class="p">(</span><span class="n">new</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">key</span><span class="p">,</span> <span class="n">tree</span><span class="o">-&gt;</span><span class="n">cmp_ptr</span><span class="p">);</span> <span class="c1">//再次计算差值，这一步是多余的？</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">tree</span><span class="o">-&gt;</span><span class="n">allow_dups</span><span class="p">)</span>
          <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="c1">//不允许插入相同key值节点时，直接返回-1</span>
        <span class="n">new</span><span class="o">-&gt;</span><span class="n">leader</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">//leader置0表示已存在相同key值的节点</span>
        <span class="n">avl_insert_after</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">last</span><span class="p">,</span> <span class="n">new</span><span class="p">);</span> <span class="c1">//将此节点插入双向链表</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
        <span class="c1">//节点的平衡度为1，表示右子树深度大。此时直接将new节点插入node节点左侧即可</span>
        <span class="n">avl_insert_before</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">,</span> <span class="n">new</span><span class="p">);</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">//插入new节点后node节点左右深度相同</span>
        <span class="n">new</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> <span class="c1">//将new节点插入到node节点左侧</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
        <span class="c1">//节点的平衡度为-1，表示左子树深度大。此时直接将new节点插入node节点右侧即可</span>
        <span class="n">avl_insert_after</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">last</span><span class="p">,</span> <span class="n">new</span><span class="p">);</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">//插入new节点后node节点左右深度相同</span>
        <span class="n">new</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> <span class="c1">//将new节点插入到node节点的右侧</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">diff</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="c1">//new节点小于node节点，插入node节点左侧</span>
        <span class="n">avl_insert_before</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">,</span> <span class="n">new</span><span class="p">);</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="c1">//左子树深度变大</span>
        <span class="n">new</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span> <span class="c1">//将new节点插入到node节点左侧</span>
        <span class="n">post_insert</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">);</span> <span class="c1">//插入后处理，后面专门看这个函数</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

    <span class="c1">//new节点大于node节点，插入node节点右侧</span>
    <span class="n">avl_insert_after</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">last</span><span class="p">,</span> <span class="n">new</span><span class="p">);</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">new</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">new</span><span class="p">;</span>
    <span class="n">post_insert</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">);</span> <span class="c1">//插入后处理</span>

<span class="cm">/*
 总结下插入逻辑：
 在没有相同节点的情况下，插入分两大类。
 大类一：node节点平衡因子不为0，插入new后node节点的平衡因子变为0，不需要后处理
 大类二：node节点平衡因子为0，插入new后node节点平衡因子不为0，需要后处理
 */</span>

<span class="c1">//接下来看下后处理函数，这里是AVL树最具特点的地方，</span>
<span class="c1">//即插入一个新节点后，如果二叉树不再平衡，只需要特定的左旋右旋操作，二叉树就可再次平衡</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">post_insert</span><span class="p">(</span><span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">node</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span><span class="p">;</span> <span class="c1">//得到node节点的父节点</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">parent</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="k">return</span> <span class="c1">//如果父节点为空，说明node节点就是整个AVL树的根节点，此时什么都不用做</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">node</span> <span class="o">==</span> <span class="n">parent</span><span class="o">-&gt;</span><span class="n">left</span><span class="p">)</span>
        <span class="c1">//如果node节点在左侧，即插入的new节点在父节点的左子树</span>
        <span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span><span class="o">--</span><span class="p">;</span> <span class="c1">//父节点左侧深度增加</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
            <span class="k">return</span><span class="p">;</span> <span class="c1">//如果父节点左右深度相同，则什么都不用做</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
            <span class="c1">//如果左子树深度大1，递归调用post_insert，也就是修改所有父节点的balance为-1</span>
            <span class="n">post_insert</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">parent</span><span class="p">);</span>
            <span class="k">return</span><span class="p">;</span>

        <span class="c1">//函数走到此，说明parent-&gt;balance=-2，父节点不再平衡，需要旋转操作</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
            <span class="c1">//如果node节点左子树深度大1，则进行右旋操作，后面单独看旋转操作函数</span>
            <span class="n">avl_rotate_right</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">parent</span><span class="p">);</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="c1">//如果node节点右子树大1，则先左旋，再右旋</span>
        <span class="n">avl_rotate_left</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">);</span>
        <span class="n">avl_rotate_right</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">parent</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="c1">//以下是node节点在parent节点右侧的情况，与上面的代码对称</span>
    <span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span><span class="o">++</span><span class="p">;</span> <span class="c1">//父节点的右侧深度增加</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
        <span class="k">return</span><span class="p">;</span> <span class="c1">//如果父节点左右深度相同，则什么都不用做</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
        <span class="c1">//如果左子树深度大1，递归调用post_insert，也就是修改所有父节点的balance为1</span>
        <span class="n">post_insert</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">parent</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="c1">//函数走到此，说明parent-&gt;balance=2，父节点不再平衡，需要旋转操作</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
        <span class="c1">//如果node节点右子树深度大1，则进行左旋操作</span>
        <span class="n">avl_rotate_left</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">parent</span><span class="p">);</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="c1">//如果node节点左子树大1，则先右旋，再左旋</span>
    <span class="n">avl_rotate_right</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">);</span>
    <span class="n">avl_rotate_left</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">parent</span><span class="p">);</span>

<span class="c1">//接下来看一下旋转操作的代码</span>
<span class="c1">//左旋</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">avl_rotate_left</span><span class="p">(</span><span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">node</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">right</span><span class="p">,</span> <span class="o">*</span><span class="n">parent</span><span class="p">;</span>
    <span class="n">right</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span><span class="p">;</span> <span class="c1">//记录node节点的右节点和父节点</span>
    <span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span><span class="p">;</span>

    <span class="n">right</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">parent</span><span class="p">;</span> <span class="c1">//右节点和node节点互换位置</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">right</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">parent</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="c1">//如果父节点是空，则修改树的根节点</span>
        <span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span> <span class="o">=</span> <span class="n">right</span><span class="p">;</span>
    <span class="k">else</span>
        <span class="c1">//将父节点的子节点node修改为right</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">==</span> <span class="n">node</span><span class="p">)</span>
            <span class="n">parent</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">right</span><span class="p">;</span>
        <span class="k">else</span>
            <span class="n">parent</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">right</span><span class="p">;</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">right</span><span class="o">-&gt;</span><span class="n">left</span><span class="p">;</span> <span class="c1">//right的左节点赋给node右节点</span>
    <span class="n">right</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span> <span class="c1">//node置为right节点的左节点</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span> <span class="c1">//如果node的右节点不为空，改一下其父节点</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">-=</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">avl_max</span><span class="p">(</span><span class="n">right</span><span class="o">-&gt;</span><span class="n">balance</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span> <span class="c1">//修正平衡因子，至少减1，即右子树深度至少减1</span>
    <span class="n">right</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">-=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">avl_min</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

<span class="c1">//右旋。与左旋相反</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">avl_rotate_right</span><span class="p">(</span><span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">node</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">left</span><span class="p">,</span> <span class="o">*</span><span class="n">parent</span><span class="p">;</span>
    <span class="n">left</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span><span class="p">;</span>
    <span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span><span class="p">;</span>

    <span class="n">left</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">parent</span><span class="p">;</span> <span class="c1">//左节点和node节点互换</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">left</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">parent</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">tree</span><span class="o">-&gt;</span><span class="n">root</span> <span class="o">=</span> <span class="n">left</span><span class="p">;</span>
    <span class="k">else</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">parent</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">==</span> <span class="n">node</span><span class="p">)</span>
            <span class="n">parent</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">left</span><span class="p">;</span>
        <span class="k">else</span>
            <span class="n">parent</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">left</span><span class="p">;</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">=</span> <span class="n">left</span><span class="o">-&gt;</span><span class="n">right</span><span class="p">;</span>
    <span class="n">left</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
        <span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">node</span><span class="p">;</span>
    <span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">+=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">avl_min</span><span class="p">(</span><span class="n">left</span><span class="o">-&gt;</span><span class="n">balance</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">left</span><span class="o">-&gt;</span><span class="n">balance</span> <span class="o">+=</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">avl_max</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">balance</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="删除节点-avl_delete">删除节点 avl_delete()</h3>

<p>删除节点avl_delete()</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//删除节点逻辑也比较长，这里只介绍下梗概</span>
<span class="kt">void</span> <span class="nf">avl_delete</span><span class="p">(</span><span class="k">struct</span> <span class="n">avl_tree</span> <span class="o">*</span><span class="n">tree</span><span class="p">,</span> <span class="k">struct</span> <span class="n">avl_node</span> <span class="o">*</span><span class="n">node</span><span class="p">)</span>
    <span class="c1">//如果node是一个重复节点，则直接删除，不涉及二叉树结构的变动</span>

    <span class="c1">//真正从二叉树删除一个节点</span>
    <span class="n">avl_delete_worker</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">node</span><span class="p">)</span>
        <span class="c1">//node是叶子节点时：</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
            <span class="c1">//根据node父节点的平衡因子，进行后处理和旋转操作</span>
        <span class="c1">//node的左节点为空时：</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">left</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
            <span class="c1">//将node的右节点代替node节点，并调用avl_post_delete</span>
        <span class="c1">//node的右节点为空时：</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
            <span class="c1">//将node的左节点代替node节点，并调用avl_post_delete</span>
        <span class="c1">//node的左右节点都不为空时</span>
        <span class="n">min</span> <span class="o">=</span> <span class="n">avl_local_min</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">right</span><span class="p">);</span> <span class="c1">//取node右子树的最小值节点min，此节点要么是叶子节点要么左节点为空</span>
        <span class="n">avl_delete_worker</span><span class="p">(</span><span class="n">tree</span><span class="p">,</span> <span class="n">min</span><span class="p">);</span> <span class="c1">//嵌套调用avl_delete_worker，删除这个min节点</span>
        <span class="c1">//用min节点代替node节点，从而删除了node节点</span>
</code></pre></div></div>

<h3 id="遍历节点">遍历节点</h3>

<p>libubox的AVL库已经把所有的节点串成了一个有序的双向链表，所以对AVL树的遍历就变成了对双向链表的遍历。
AVL库对外提供了很多遍历宏，而这些宏的最终调用的是
avl_for_element_range avl_for_element_range_safe avl_for_element_range_reverse avl_for_element_range_reverse_safe</p>

<p>我们来看一下avl_for_element_range</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//这里入参first和last是指向包含avl_node结构的大结构体的指针，即遍历的起始大结构体和结束大结构体</span>
<span class="c1">//element是指向每一个遍历到的大结构体，node_member是avl_node结构体在大结构体中的成员名</span>
<span class="cp">#define avl_for_element_range(first, last, element, node_member) \
  for (element = (first); \
       element-&gt;node_member.list.prev != &amp;(last)-&gt;node_member.list; \
       element = avl_next_element(element, node_member))
</span>
<span class="c1">//avl_next_element，即取avl_node.list-&gt;next，然后利用container_of取到大结构体</span>
<span class="cp">#define avl_next_element(element, node_member) \
  container_of((&amp;(element)-&gt;node_member.list)-&gt;next, typeof(*(element)), node_member.list)
</span></code></pre></div></div>

<p>再看下全节点遍历avl_for_each_element</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//全节点遍历即调用avl_for_element_range，然后入参是AVL树的第一个节点和最后一个节点</span>
<span class="c1">//即从树的第一个节点 遍历到树的最后一个节点</span>
<span class="cp">#define avl_for_each_element(tree, element, node_member) \
  avl_for_element_range(avl_first_element(tree, element, node_member), \
                        avl_last_element(tree, element,  node_member), \
                        element, node_member)
</span></code></pre></div></div>

<h2 id="avl库实际使用举例">AVL库实际使用举例</h2>

<p>如文章开头提到的AVL树非常适合用在动态数据表查找上，查找的时间复杂度是O(log n)。
我们以有1000个数据的数据表举例，如果这个数据表用线性链表存储，查找一个数据最大可能搜索1000次；
如果用AVL树存储，最大只需要搜索10次。</p>

<p>OpenWRT中有一个重要的进程间通信组件ubus，就是用的AVL树维护ubus总线上各对象的信息。</p>

<p>这样在ubus总线上，即使有很多进程注册了很多ubus对象，ubusd内部可以根据目标对象ID或名称字符串，
在AVL树上很快搜索到目标对象。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ubus/ubusd_proto.c</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">ubusd_handle_lookup</span><span class="p">()</span>
	<span class="kt">char</span> <span class="o">*</span><span class="n">objpath</span><span class="p">;</span>
	<span class="n">objpath</span> <span class="o">=</span> <span class="n">blob_data</span><span class="p">(</span><span class="n">attr</span><span class="p">[</span><span class="n">UBUS_ATTR_OBJPATH</span><span class="p">]);</span>
	<span class="p">...</span>
	<span class="n">obj</span> <span class="o">=</span> <span class="n">avl_find_element</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">,</span> <span class="n">objpath</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span> <span class="c1">//AVL树查找</span>
	<span class="p">...</span>

</code></pre></div></div>]]></content><author><name></name></author><summary type="html"><![CDATA[引言]]></summary></entry><entry><title type="html">Linux内核网络源码走读之Netfilter</title><link href="https://jiansoft.net/2022/08/06/linux_source_code_netfilter.html" rel="alternate" type="text/html" title="Linux内核网络源码走读之Netfilter" /><published>2022-08-06T00:00:00+00:00</published><updated>2022-08-06T00:00:00+00:00</updated><id>https://jiansoft.net/2022/08/06/linux_source_code_netfilter</id><content type="html" xml:base="https://jiansoft.net/2022/08/06/linux_source_code_netfilter.html"><![CDATA[<p>本文走读内核网络之Netfilter子系统相关的源码。源码基于kernel 4.14版本。</p>

<p>Netfilter子系统包含数据包选择、过滤、修改，连接跟踪，网络地址转换(NAT)等内容。</p>

<h2 id="netfilter挂载点">Netfilter挂载点</h2>

<p>在上篇<a href="/2022/05/06/learn_linux_netlink.html">《Linux内核源码走读之IPv4及IPv6》</a>文章中，我们在IPv4和IPv6的接收和发送路径中，看到过这些挂载点。</p>

<ul>
  <li>
    <p><strong>NF_INET_PRE_ROUTING</strong>: 在IPv4中，这个挂载点位于方法ip_rcv()中。这是所有入站数据包遇到的第一个挂载点，它处在路由选择之前。</p>
  </li>
  <li>
    <p><strong>NF_INET_LOCAL_IN</strong>: 在IPv4中，这个挂载点位于方法ip_local_deliver中。对于所有发给当前主机的入站数据包，经过挂载点NF_INET_PRE_ROUTING和路由选择子系统之后，都将到达这个挂载点。</p>
  </li>
  <li>
    <p><strong>NF_INET_FORWARD</strong>: 在IPv4中，这个挂载点位于方法ip_forward()中。对于所有要转发的数据包，经过挂载点NF_INET_PRE_ROUTING和路由选择子系统之后，都将到达这个挂载点。</p>
  </li>
  <li>
    <p><strong>NF_INET_POST_ROUTING</strong>: 在IPv4中，这个挂载点位于方法ip_output()中。所有要转发的数据包，都在经过挂载点NF_INET_FORWARD后到达这个挂载点。另外，当前主机生成的数据包经过挂载点NF_INET_LOCAL_OUT后将到达这个挂载点。</p>
  </li>
  <li>
    <p><strong>NF_INET_LOCAL_OUT</strong>: 在IPv4中，这个挂载点位于方法__ip_local_out中。当前主机生成的所有出站数据包都在经过路由查找和此挂载点之后，到达挂载点NF_INET_POST_ROUTING。</p>
  </li>
</ul>

<p>内核网络代码中，一般通过宏NF_HOOK来调用在挂载点中注册的钩子函数。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span>
<span class="nf">NF_HOOK</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">pf</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">hook</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
    <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">in</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">out</span><span class="p">,</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">okfn</span><span class="p">)(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="p">))</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">nf_hook</span><span class="p">(</span><span class="n">pf</span><span class="p">,</span> <span class="n">hook</span><span class="p">,</span> <span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">in</span><span class="p">,</span> <span class="n">out</span><span class="p">,</span> <span class="n">okfn</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">okfn</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">//nf_hook并不调用okfn回调函数，NF_HOOK宏判断nf_hook返回值=1(表示允许包通过)调用okfn</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">nf_hook</span><span class="p">(</span><span class="n">u_int8_t</span> <span class="n">pf</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">hook</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span>
              <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
              <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">indev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">outdev</span><span class="p">,</span>
              <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">okfn</span><span class="p">)(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="p">))</span>
    <span class="k">switch</span> <span class="p">(</span><span class="n">pf</span><span class="p">)</span>
    <span class="k">case</span> <span class="n">NFPROTO_IPV4</span><span class="p">:</span>
        <span class="n">hook_head</span> <span class="o">=</span> <span class="n">rcu_dereference</span><span class="p">(</span><span class="n">net</span><span class="o">-&gt;</span><span class="n">nf</span><span class="p">.</span><span class="n">hooks_ipv4</span><span class="p">[</span><span class="n">hook</span><span class="p">]);</span>

    <span class="k">struct</span> <span class="n">nf_hook_state</span> <span class="n">state</span><span class="p">;</span>
    <span class="n">nf_hook_state_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">hook</span><span class="p">,</span> <span class="n">pf</span><span class="p">,</span> <span class="n">indev</span><span class="p">,</span> <span class="n">outdev</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">net</span><span class="p">,</span> <span class="n">okfn</span><span class="p">);</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">nf_hook_slow</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">hook_head</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span>

<span class="kt">int</span> <span class="n">nf_hook_slow</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nf_hook_state</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span>
         <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_entries</span> <span class="o">*</span><span class="n">e</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">s</span><span class="p">)</span>
    <span class="k">for</span> <span class="p">(;</span> <span class="n">s</span> <span class="o">&lt;</span> <span class="n">e</span><span class="o">-&gt;</span><span class="n">num_hook_entries</span><span class="p">;</span> <span class="n">s</span><span class="o">++</span><span class="p">)</span>
        <span class="c1">//依次执行注册的hook函数，如果返回值是NF_ACCEPT，则表示调用者可进一步执行okfn</span>
        <span class="n">verdict</span> <span class="o">=</span> <span class="n">nf_hook_entry_hookfn</span><span class="p">(</span><span class="o">&amp;</span><span class="n">e</span><span class="o">-&gt;</span><span class="n">hooks</span><span class="p">[</span><span class="n">s</span><span class="p">],</span> <span class="n">skb</span><span class="p">,</span> <span class="n">state</span><span class="p">);</span>
            <span class="k">return</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">hook</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">priv</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">state</span><span class="p">);</span>
</code></pre></div></div>

<p>Netfilter钩子回调函数返回值必须是下述五个值之一，这些值被称为netfilter verdicts(netfilter判决)</p>

<ul>
  <li>NF_DROP: 默默丢弃数据包</li>
  <li>NF_ACCEPT: 数据包继续在内核协议栈中传输</li>
  <li>NF_STOLEN: 数据包不继续传输，由钩子方法进行处理</li>
  <li>NF_QUEUE: 将数据包排序，供用户空间使用</li>
  <li>NF_REPEAT: 再次调用钩子函数</li>
</ul>

<h3 id="注册netfilter钩子回调函数">注册Netfilter钩子回调函数</h3>

<p>注册Netfilter钩子回调函数的方法有两个nf_register_net_hook和nf_register_net_hooks。
4.13之前的内核版本还有两个注册接口nf_register_hook和nf_register_hooks，
从4.13版本开始内核删除了这两个接口，这两个接口最终也是调用nf_register_net_hook，下面看下nf_register_net_hook:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">nf_register_net_hook</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_ops</span> <span class="o">*</span><span class="n">reg</span><span class="p">)</span>
    <span class="n">__nf_register_net_hook</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">reg</span><span class="o">-&gt;</span><span class="n">pf</span><span class="p">,</span> <span class="n">reg</span><span class="p">)</span>
        <span class="k">struct</span> <span class="n">nf_hook_entries</span> <span class="o">*</span><span class="n">p</span><span class="p">,</span> <span class="o">*</span><span class="n">new_hooks</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">nf_hook_entries</span> <span class="n">__rcu</span> <span class="o">**</span><span class="n">pp</span><span class="p">;</span>
        <span class="n">pp</span> <span class="o">=</span> <span class="n">nf_hook_entry_head</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">pf</span><span class="p">,</span> <span class="n">reg</span><span class="o">-&gt;</span><span class="n">hooknum</span><span class="p">,</span> <span class="n">reg</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">)</span>
            <span class="k">return</span> <span class="n">net</span><span class="o">-&gt;</span><span class="n">nf</span><span class="p">.</span><span class="n">hooks_ipv4</span> <span class="o">+</span> <span class="n">hooknum</span><span class="p">;</span> <span class="c1">//以pf==NFPROTO_IPV4为例。钩子挂载点保存在struct net对象中</span>
        <span class="n">p</span> <span class="o">=</span> <span class="n">nf_entry_dereference</span><span class="p">(</span><span class="o">*</span><span class="n">pp</span><span class="p">);</span>
        <span class="n">new_hooks</span> <span class="o">=</span> <span class="n">nf_hook_entries_grow</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">reg</span><span class="p">);</span> <span class="c1">//将新的nf_hook_ops按照优先级插入到hook entries中</span>
</code></pre></div></div>

<p>我们看到nf_register_net_hook一个入参是结构体struct nf_hook_ops，看下这个结构体：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">nf_hookfn</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">priv</span><span class="p">,</span>
                   <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
                   <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_state</span> <span class="o">*</span><span class="n">state</span><span class="p">);</span>

<span class="k">struct</span> <span class="n">nf_hook_ops</span> <span class="p">{</span>
    <span class="cm">/* User fills in from here down. */</span>
    <span class="n">nf_hookfn</span>       <span class="o">*</span><span class="n">hook</span><span class="p">;</span> <span class="c1">//要注册的钩子回调函数</span>
    <span class="k">struct</span> <span class="n">net_device</span>   <span class="o">*</span><span class="n">dev</span><span class="p">;</span>
    <span class="kt">void</span>            <span class="o">*</span><span class="n">priv</span><span class="p">;</span>
    <span class="n">u_int8_t</span>        <span class="n">pf</span><span class="p">;</span> <span class="c1">//协议簇，对于IPv4来说，它为NFPROTO_IPV4; IPV6, NFPROTO_IPV6</span>
    <span class="n">bool</span>            <span class="n">nat_hook</span><span class="p">;</span>
    <span class="kt">unsigned</span> <span class="kt">int</span>        <span class="n">hooknum</span><span class="p">;</span> <span class="c1">//netfilter的5个挂载点之一</span>
    <span class="cm">/* Hooks are ordered in ascending priority. */</span>
    <span class="kt">int</span>         <span class="n">priority</span><span class="p">;</span> <span class="c1">//按优先级升序排列回调函数，priority值越小回调函数越先被调用</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="连接跟踪">连接跟踪</h2>

<p>现代网络中，仅根据L4和L3报头来过滤流量还不够，还应考虑基于会话对包进行处理。
连接跟踪能够让内核跟踪会话，连接跟踪的主要目标是为NAT打下基础。</p>

<h3 id="连接跟踪初始化">连接跟踪初始化</h3>

<p>先看下连接跟踪模块定义的netfilter挂载点对象数组，即结构体struct nf_hook_ops数组，定义在netfilter各挂载点的处理函数。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_ops</span> <span class="n">ipv4_conntrack_ops</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_conntrack_in</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_PRE_ROUTING</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_conntrack_local</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_LOCAL_OUT</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_helper</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_POST_ROUTING</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK_HELPER</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_confirm</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_POST_ROUTING</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK_CONFIRM</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_helper</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK_HELPER</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">ipv4_confirm</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_CONNTRACK_CONFIRM</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>
<p>注册的最重要的连接跟踪回调函数是，NF_INET_PRE_ROUTING钩子回调函数ipv4_conntrack_in和NF_INET_LOCAL_OUT钩子回调函数ipv4_conntrack_local。
这两个钩子函数的优先级为NF_IP_PRI_CONNTRACK(-200)，优先级较高。
ipv4_conntrack_in和ipv4_conntrack_local都会调用到nf_conntrack_in，下一小结走读nf_conntrack_in。</p>

<p>继续看下注册这个ipv4_conntrack_ops的地方。在内核版本4.9及以前，直接在函数nf_conntrack_l3proto_ipv4_init中调用nf_register_hooks来注册。
4.10及以后内核，不在nf_conntrack_l3proto_ipv4_init中直接注册ipv4_conntrack_ops，看下相关代码：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//nf_conntrack_l3proto_ipv4.c</span>
<span class="c1">//nf_conntrack_l3proto_ipv4_init为nf_conntrack_ipv4.ko的初始化函数</span>
<span class="n">module_init</span><span class="p">(</span><span class="n">nf_conntrack_l3proto_ipv4_init</span><span class="p">);</span>


<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">nf_conntrack_l3proto_ipv4_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
    <span class="p">...</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">nf_ct_l3proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nf_conntrack_l3proto_ipv4</span><span class="p">);</span>
        <span class="n">rcu_assign_pointer</span><span class="p">(</span><span class="n">nf_ct_l3protos</span><span class="p">[</span><span class="n">proto</span><span class="o">-&gt;</span><span class="n">l3proto</span><span class="p">],</span> <span class="n">proto</span><span class="p">);</span> <span class="c1">//注册到全局变量nf_ct_l3protos中</span>


<span class="k">struct</span> <span class="n">nf_conntrack_l3proto</span> <span class="n">nf_conntrack_l3proto_ipv4</span> <span class="n">__read_mostly</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">l3proto</span>     <span class="o">=</span> <span class="n">PF_INET</span><span class="p">,</span>
    <span class="p">.</span><span class="n">pkt_to_tuple</span>    <span class="o">=</span> <span class="n">ipv4_pkt_to_tuple</span><span class="p">,</span>
    <span class="p">.</span><span class="n">invert_tuple</span>    <span class="o">=</span> <span class="n">ipv4_invert_tuple</span><span class="p">,</span>
    <span class="p">.</span><span class="n">get_l4proto</span>     <span class="o">=</span> <span class="n">ipv4_get_l4proto</span><span class="p">,</span>
<span class="cp">#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
</span>    <span class="p">.</span><span class="n">tuple_to_nlattr</span> <span class="o">=</span> <span class="n">ipv4_tuple_to_nlattr</span><span class="p">,</span>
    <span class="p">.</span><span class="n">nlattr_to_tuple</span> <span class="o">=</span> <span class="n">ipv4_nlattr_to_tuple</span><span class="p">,</span>
    <span class="p">.</span><span class="n">nla_policy</span>  <span class="o">=</span> <span class="n">ipv4_nla_policy</span><span class="p">,</span>
    <span class="p">.</span><span class="n">nla_size</span>    <span class="o">=</span> <span class="n">NLA_ALIGN</span><span class="p">(</span><span class="n">NLA_HDRLEN</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">u32</span><span class="p">))</span> <span class="o">+</span> <span class="cm">/* CTA_IP_V4_SRC */</span>
               <span class="n">NLA_ALIGN</span><span class="p">(</span><span class="n">NLA_HDRLEN</span> <span class="o">+</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">u32</span><span class="p">)),</span>  <span class="cm">/* CTA_IP_V4_DST */</span>
<span class="cp">#endif
</span>    <span class="p">.</span><span class="n">net_ns_get</span>  <span class="o">=</span> <span class="n">ipv4_hooks_register</span><span class="p">,</span> <span class="c1">//这里注册的函数用于注册连接跟踪的netfliter钩子</span>
    <span class="p">.</span><span class="n">net_ns_put</span>  <span class="o">=</span> <span class="n">ipv4_hooks_unregister</span><span class="p">,</span>
    <span class="p">.</span><span class="n">me</span>      <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
<span class="p">};</span>

<span class="c1">//先看下ipv4_hooks_register</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">ipv4_hooks_register</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">conntrack4_net</span> <span class="o">*</span><span class="n">cnet</span> <span class="o">=</span> <span class="n">net_generic</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">conntrack4_net_id</span><span class="p">);</span>
    <span class="n">cnet</span><span class="o">-&gt;</span><span class="n">users</span><span class="o">++</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">cnet</span><span class="o">-&gt;</span><span class="n">users</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span>
        <span class="k">goto</span> <span class="n">out_unlock</span><span class="p">;</span> <span class="c1">//只在第一次调用的时候往下走，之后的调用只是users技术+1</span>
    <span class="c1">//注册连接跟踪的netfilter钩子</span>
    <span class="n">nf_register_net_hooks</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">ipv4_conntrack_ops</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">ipv4_conntrack_ops</span><span class="p">));</span>

<span class="c1">//再看下调用nf_conntrack_l3proto_ipv4.net_ns_get的地方</span>
<span class="kt">int</span> <span class="n">nf_ct_netns_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="n">u8</span> <span class="n">nfproto</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">nfproto</span> <span class="o">==</span> <span class="n">NFPROTO_INET</span><span class="p">)</span>
        <span class="n">nf_ct_netns_do_get</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">NFPROTO_IPV4</span><span class="p">)</span>
        <span class="n">nf_ct_netns_do_get</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">NFPROTO_IPV6</span><span class="p">)</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">nf_ct_netns_do_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="n">u8</span> <span class="n">nfproto</span><span class="p">)</span>
    <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_conntrack_l3proto</span> <span class="o">*</span><span class="n">l3proto</span><span class="p">;</span>
    <span class="n">l3proto</span> <span class="o">=</span> <span class="n">__nf_ct_l3proto_find</span><span class="p">(</span><span class="n">nfproto</span><span class="p">);</span> <span class="c1">//对于NFPROTO_IPV4，这里返回的是nf_conntrack_l3proto_ipv4</span>
    <span class="n">l3proto</span><span class="o">-&gt;</span><span class="n">net_ns_get</span><span class="p">(</span><span class="n">net</span><span class="p">);</span> <span class="c1">//调用net_ns_get</span>

<span class="c1">//调用nf_ct_netns_get地方有很多，主要应该是通过nft_ct_get_init和nft_nat_init</span>
</code></pre></div></div>

<p>下图展示了IPv4连接跟踪钩子函数在IPv4收发流程中的位置，其中绿色方块是netfilter的5个钩子挂载点，蓝色方块是连接跟踪模块注册的钩子函数:
<img src="/assets/image/2022/08/ipv4_conntrack_hooks.png" alt="ipv4_conntrack_hooks.png" /></p>

<p>用来区分特定方向上的流的结构体是struct nf_conntrack_tuple：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nf_conntrack_tuple</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">nf_conntrack_man</span> <span class="n">src</span><span class="p">;</span>  <span class="c1">//tuple的可操作部分</span>

    <span class="cm">/* 以下是tuple的固定部分 */</span>
    <span class="k">struct</span> <span class="p">{</span>
        <span class="k">union</span> <span class="n">nf_inet_addr</span> <span class="n">u3</span><span class="p">;</span>
        <span class="k">union</span> <span class="p">{</span>
            <span class="cm">/* Add other protocols here. */</span>
            <span class="n">__be16</span> <span class="n">all</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">__be16</span> <span class="n">port</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">tcp</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">__be16</span> <span class="n">port</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">udp</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">u_int8_t</span> <span class="n">type</span><span class="p">,</span> <span class="n">code</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">icmp</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">__be16</span> <span class="n">port</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">dccp</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">__be16</span> <span class="n">port</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">sctp</span><span class="p">;</span>
            <span class="k">struct</span> <span class="p">{</span>
                <span class="n">__be16</span> <span class="n">key</span><span class="p">;</span>
            <span class="p">}</span> <span class="n">gre</span><span class="p">;</span>
        <span class="p">}</span> <span class="n">u</span><span class="p">;</span>
        <span class="n">u_int8_t</span> <span class="n">protonum</span><span class="p">;</span> <span class="c1">//protocol</span>
        <span class="n">u_int8_t</span> <span class="n">dir</span><span class="p">;</span>
    <span class="p">}</span> <span class="n">dst</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="连接跟踪条目">连接跟踪条目</h3>

<p>struct nf_conn表示连接跟踪条目，即保存到连接跟踪hash表里的节点。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nf_conn</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">nf_conntrack</span> <span class="n">ct_general</span><span class="p">;</span>
    <span class="n">spinlock_t</span>  <span class="n">lock</span><span class="p">;</span>
    <span class="n">u16</span>     <span class="n">cpu</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">nf_conntrack_zone</span> <span class="n">zone</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">nf_conntrack_tuple_hash</span> <span class="n">tuplehash</span><span class="p">[</span><span class="n">IP_CT_DIR_MAX</span><span class="p">];</span> <span class="c1">//hashlist节点</span>
    <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">status</span><span class="p">;</span>
    <span class="n">u32</span> <span class="n">timeout</span><span class="p">;</span>

    <span class="n">possible_net_t</span> <span class="n">ct_net</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">hlist_node</span>   <span class="n">nat_bysource</span><span class="p">;</span>
    <span class="cm">/* all members below initialized via memset */</span>
    <span class="k">struct</span> <span class="p">{</span> <span class="p">}</span> <span class="n">__nfct_init_offset</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">nf_conn</span> <span class="o">*</span><span class="n">master</span><span class="p">;</span>
    <span class="n">u_int32_t</span> <span class="n">mark</span><span class="p">;</span>
    <span class="n">u_int32_t</span> <span class="n">secmark</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">nf_ct_ext</span> <span class="o">*</span><span class="n">ext</span><span class="p">;</span>
    <span class="k">union</span> <span class="n">nf_conntrack_proto</span> <span class="n">proto</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>接下来看一下方法nf_conntrack_in():</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">nf_conntrack_in</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="n">u_int8_t</span> <span class="n">pf</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">hooknum</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">l3proto</span> <span class="o">=</span> <span class="n">__nf_ct_l3proto_find</span><span class="p">(</span><span class="n">pf</span><span class="p">);</span>  <span class="c1">//对于pf=PF_INET,PF_INET,返回的是全局变量nf_conntrack_l3proto_ipv4</span>
    <span class="n">l3proto</span><span class="o">-&gt;</span><span class="n">get_l4proto</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">skb_network_offset</span><span class="p">(</span><span class="n">skb</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">dataoff</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">protonum</span><span class="p">);</span> <span class="c1">//.get_l4proto=ipv4_get_l4proto</span>
        <span class="c1">//对于IPv4 -&gt;get_l4proto=ipv4_get_l4proto</span>
        <span class="o">*</span><span class="n">dataoff</span> <span class="o">=</span> <span class="n">nhoff</span> <span class="o">+</span> <span class="p">(</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">ihl</span> <span class="o">&lt;&lt;</span> <span class="mi">2</span><span class="p">);</span>
        <span class="o">*</span><span class="n">protonum</span> <span class="o">=</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">protocol</span><span class="p">;</span> <span class="c1">//protonum即四层协议</span>
    <span class="n">l4proto</span> <span class="o">=</span> <span class="n">__nf_ct_l4proto_find</span><span class="p">(</span><span class="n">pf</span><span class="p">,</span> <span class="n">protonum</span><span class="p">);</span> <span class="c1">//以IPPROTO_TCP为例，返回的是全局变量nf_conntrack_l4proto_tcp4</span>

    <span class="n">resolve_normal_ct</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">tmpl</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">dataoff</span><span class="p">,</span> <span class="n">pf</span><span class="p">,</span> <span class="n">protonum</span><span class="p">,</span> <span class="n">l3proto</span><span class="p">,</span> <span class="n">l4proto</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">nf_conntrack_tuple</span> <span class="n">tuple</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">nf_conntrack_tuple_hash</span> <span class="o">*</span><span class="n">h</span><span class="p">;</span>
        <span class="n">nf_ct_get_tuple</span><span class="p">()</span> <span class="c1">//填充tuple</span>
        <span class="n">hash</span> <span class="o">=</span> <span class="n">hash_conntrack_raw</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tuple</span><span class="p">,</span> <span class="n">net</span><span class="p">);</span> <span class="c1">//对tuple进行hash散列运算，调用的内核提供的jhash2()</span>
        <span class="n">h</span> <span class="o">=</span> <span class="n">__nf_conntrack_find_get</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">zone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tuple</span><span class="p">,</span> <span class="n">hash</span><span class="p">);</span> <span class="c1">//在全局变量nf_conntrack_hash hash表下查找连接是否存在</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">h</span><span class="p">)</span> <span class="c1">//如果连接不存在，则新建一个连接，保存到unconfirmed list</span>
            <span class="n">h</span> <span class="o">=</span> <span class="n">init_conntrack</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">tmpl</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tuple</span><span class="p">,</span> <span class="n">l3proto</span><span class="p">,</span> <span class="n">l4proto</span><span class="p">,</span><span class="n">skb</span><span class="p">,</span> <span class="n">dataoff</span><span class="p">,</span> <span class="n">hash</span><span class="p">);</span>
        <span class="n">ct</span> <span class="o">=</span> <span class="n">nf_ct_tuplehash_to_ctrack</span><span class="p">(</span><span class="n">h</span><span class="p">);</span> <span class="c1">//利用container_of得到真正的连接对象</span>
        <span class="p">...</span><span class="c1">//一系列ctinfo赋值逻辑，对于新建的连接ctinfo = IP_CT_NEW</span>
        <span class="n">nf_ct_set</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">ct</span><span class="p">,</span> <span class="n">ctinfo</span><span class="p">);</span> <span class="c1">//将连接对象和连接状态值，保存到skb中</span>
            <span class="n">skb</span><span class="o">-&gt;</span><span class="n">_nfct</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">ct</span> <span class="o">|</span> <span class="n">info</span><span class="p">;</span> <span class="c1">//借助指针低4位一定为0的逻辑，低4位存整数值</span>
    <span class="n">timeouts</span> <span class="o">=</span> <span class="n">nf_ct_timeout_lookup</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">ct</span><span class="p">,</span> <span class="n">l4proto</span><span class="p">);</span>
    <span class="n">l4proto</span><span class="o">-&gt;</span><span class="n">packet</span><span class="p">(</span><span class="n">ct</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">dataoff</span><span class="p">,</span> <span class="n">ctinfo</span><span class="p">,</span> <span class="n">pf</span><span class="p">,</span> <span class="n">timeouts</span><span class="p">);</span> <span class="c1">//以TCP为例，-&gt;packet==tcp_packet()</span>

</code></pre></div></div>

<p>再看下ipv4_confirm()的代码：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ipv4_confirm</span>
    <span class="n">nf_conntrack_confirm</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">nf_conntrack_confirm</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">ct</span> <span class="o">=</span> <span class="n">nf_ct_get</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ctinfo</span><span class="p">);</span>
    <span class="p">...</span>
    <span class="n">nf_ct_del_from_dying_or_unconfirmed_list</span><span class="p">(</span><span class="n">ct</span><span class="p">);</span> <span class="c1">//从unconfirmed或dying表中删除连接</span>
    <span class="p">...</span>
    <span class="n">__nf_conntrack_hash_insert</span><span class="p">(</span><span class="n">ct</span><span class="p">,</span> <span class="n">hash</span><span class="p">,</span> <span class="n">reply_hash</span><span class="p">);</span> <span class="c1">//插入到nf_conntrack_hash</span>
    <span class="p">...</span>
</code></pre></div></div>

<h2 id="iptables">iptables</h2>

<p>iptables由内核部分和用户空间部分组成，核心是内核部分。</p>

<p>iptables的字面意思就是ip表项，每个表由struct xt_table表示。IPv4中，注册和注销表的接口是ipt_register_table()和ipt_unregister_table()。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">xt_table</span> <span class="p">{</span>
    <span class="k">struct</span> <span class="n">list_head</span> <span class="n">list</span><span class="p">;</span>
    <span class="cm">/* What hooks you will enter on */</span>
    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">valid_hooks</span><span class="p">;</span>
    <span class="cm">/* Man behind the curtain... */</span>
    <span class="k">struct</span> <span class="n">xt_table_info</span> <span class="o">*</span><span class="n">private</span><span class="p">;</span> <span class="c1">//</span>
    <span class="k">struct</span> <span class="n">module</span> <span class="o">*</span><span class="n">me</span><span class="p">;</span>
    <span class="n">u_int8_t</span> <span class="n">af</span><span class="p">;</span>        <span class="cm">/* address/protocol family */</span>
    <span class="kt">int</span> <span class="n">priority</span><span class="p">;</span>       <span class="cm">/* hook order */</span>
    <span class="cm">/* called when table is needed in the given netns */</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">table_init</span><span class="p">)(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">);</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="n">name</span><span class="p">[</span><span class="n">XT_TABLE_MAXNAMELEN</span><span class="p">];</span>
<span class="p">};</span>

<span class="kt">int</span> <span class="nf">ipt_register_table</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">xt_table</span> <span class="o">*</span><span class="n">table</span><span class="p">,</span>
               <span class="k">const</span> <span class="k">struct</span> <span class="n">ipt_replace</span> <span class="o">*</span><span class="n">repl</span><span class="p">,</span>
               <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_ops</span> <span class="o">*</span><span class="n">ops</span><span class="p">,</span> <span class="k">struct</span> <span class="n">xt_table</span> <span class="o">**</span><span class="n">res</span><span class="p">)</span>
    <span class="n">xt_register_table</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">table</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bootstrap</span><span class="p">,</span> <span class="n">newinfo</span><span class="p">);</span>
        <span class="n">list_add</span><span class="p">(</span><span class="o">&amp;</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">list</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">net</span><span class="o">-&gt;</span><span class="n">xt</span><span class="p">.</span><span class="n">tables</span><span class="p">[</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">af</span><span class="p">]);</span> <span class="c1">//注册到net-&gt;xt.tables上</span>
    <span class="n">nf_register_net_hooks</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">ops</span><span class="p">,</span> <span class="n">hweight32</span><span class="p">(</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">valid_hooks</span><span class="p">))</span> <span class="c1">//注册netfilter钩子</span>
</code></pre></div></div>

<p>struct net对象包含IPv4和IPv6专用对象netns_ipv4和netns_ipv6，netns_ipv4和netns_ipv6又包含指向xt_table对象的指针。
例如netns_ipv4包含iptable_filter、iptable_mangle、iptable_raw、arptable_filter、nat_table。</p>

<p>我们以iptable_filter过滤表为例，来进一步看下iptables的工作原理。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//filter表的定义</span>
<span class="cp">#define FILTER_VALID_HOOKS ((1 &lt;&lt; NF_INET_LOCAL_IN) | \
                (1 &lt;&lt; NF_INET_FORWARD) | \
                (1 &lt;&lt; NF_INET_LOCAL_OUT))
</span><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">xt_table</span> <span class="n">packet_filter</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">name</span>       <span class="o">=</span> <span class="s">"filter"</span><span class="p">,</span>
    <span class="p">.</span><span class="n">valid_hooks</span>    <span class="o">=</span> <span class="n">FILTER_VALID_HOOKS</span><span class="p">,</span> <span class="c1">//按照FILTER_VALID_HOOKS定义，在netfilter的3个挂载点挂载钩子</span>
    <span class="p">.</span><span class="n">me</span>     <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
    <span class="p">.</span><span class="n">af</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
    <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_FILTER</span><span class="p">,</span>
    <span class="p">.</span><span class="n">table_init</span> <span class="o">=</span> <span class="n">iptable_filter_table_init</span><span class="p">,</span>
<span class="p">};</span>

<span class="c1">//初始化</span>
<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">iptable_filter_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
    <span class="c1">//这一步主要是初始化netfilter钩子挂载对象，3个挂载点的回调函数都是iptable_filter_hook</span>
    <span class="n">filter_ops</span> <span class="o">=</span> <span class="n">xt_hook_ops_alloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">packet_filter</span><span class="p">,</span> <span class="n">iptable_filter_hook</span><span class="p">);</span>
    <span class="n">register_pernet_subsys</span><span class="p">(</span><span class="o">&amp;</span><span class="n">iptable_filter_net_ops</span><span class="p">)</span>
        <span class="n">iptable_filter_net_init</span>
            <span class="nf">iptable_filter_table_init</span><span class="p">(</span><span class="n">net</span><span class="p">)</span>
                <span class="c1">//注册filter表</span>
                <span class="n">ipt_register_table</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">packet_filter</span><span class="p">,</span> <span class="n">repl</span><span class="p">,</span> <span class="n">filter_ops</span><span class="p">,</span>
                 <span class="o">&amp;</span><span class="n">net</span><span class="o">-&gt;</span><span class="n">ipv4</span><span class="p">.</span><span class="n">iptable_filter</span><span class="p">);</span>
</code></pre></div></div>
<p>总结下，内核提供了一些表，表里的条目由用户空间程序设置。</p>

<p>看一个用户空间iptables命令例子:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iptables</span> <span class="o">-</span><span class="n">A</span> <span class="n">INPUT</span> <span class="o">-</span><span class="n">p</span> <span class="n">udp</span> <span class="o">--</span><span class="n">dport</span><span class="o">=</span><span class="mi">5001</span> <span class="o">-</span><span class="n">j</span> <span class="n">LOG</span> <span class="o">--</span><span class="n">log</span><span class="o">-</span><span class="n">level</span> <span class="mi">1</span>
</code></pre></div></div>
<p>这条规则的意思是，向filter表中添加一条规则，将目标端口为5001的UDP入站数据包转储到系统日志中。
使用iptables命令时，应使用修饰符-t来指定要使用的表，如果没指定，默认使用过滤表。</p>

<p>再看一个规则：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">iptables</span> <span class="o">-</span><span class="n">A</span> <span class="n">INPUT</span> <span class="o">-</span><span class="n">p</span> <span class="n">tcp</span> <span class="o">-</span><span class="n">m</span> <span class="n">conntrack</span> <span class="o">--</span><span class="n">ctstate</span> <span class="n">ESTABLISHED</span> <span class="o">-</span><span class="n">j</span> <span class="n">LOG</span> <span class="o">--</span><span class="n">log</span><span class="o">-</span><span class="n">level</span> <span class="mi">1</span>
</code></pre></div></div>
<p>这个规则是根据连接跟踪状态来过滤数据包，将连接状态为ESTABLISHED的数据包转储到系统日志中。</p>

<p><strong>本文主要聚焦内核源码，关于用户空间的iptables命令，后面另起文章学习</strong></p>

<h2 id="nat">NAT</h2>

<p>NAT(Network Address Translation)网络地址转换，主要用于IP地址转换或端口转换。
NAT最常见的用途之一是，让局域网中一组使用私有IP地址的主机能够通过网关的公网IP访问Internet。</p>

<h3 id="nat初始化">NAT初始化</h3>

<p>与上节介绍的过滤表一样，NAT表也是一个xt_table对象。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">xt_table</span> <span class="n">nf_nat_ipv4_table</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">name</span>       <span class="o">=</span> <span class="s">"nat"</span><span class="p">,</span>
    <span class="p">.</span><span class="n">valid_hooks</span>    <span class="o">=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">NF_INET_PRE_ROUTING</span><span class="p">)</span> <span class="o">|</span>
              <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">NF_INET_POST_ROUTING</span><span class="p">)</span> <span class="o">|</span>
              <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">NF_INET_LOCAL_OUT</span><span class="p">)</span> <span class="o">|</span>
              <span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">),</span>
    <span class="p">.</span><span class="n">me</span>     <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
    <span class="p">.</span><span class="n">af</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
    <span class="p">.</span><span class="n">table_init</span> <span class="o">=</span> <span class="n">iptable_nat_table_init</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>
<p>nat表的netfilter钩子函数：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_ops</span> <span class="n">nf_nat_ipv4_ops</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="cm">/* Before packet filtering, change destination */</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">iptable_nat_ipv4_in</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">nat_hook</span>   <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_PRE_ROUTING</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_NAT_DST</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="cm">/* After packet filtering, change source */</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">iptable_nat_ipv4_out</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">nat_hook</span>   <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_POST_ROUTING</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_NAT_SRC</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="cm">/* Before packet filtering, change destination */</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">iptable_nat_ipv4_local_fn</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">nat_hook</span>   <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_LOCAL_OUT</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_NAT_DST</span><span class="p">,</span>
    <span class="p">},</span>
    <span class="cm">/* After packet filtering, change source */</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">hook</span>       <span class="o">=</span> <span class="n">iptable_nat_ipv4_fn</span><span class="p">,</span>
        <span class="p">.</span><span class="n">pf</span>     <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
        <span class="p">.</span><span class="n">nat_hook</span>   <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
        <span class="p">.</span><span class="n">hooknum</span>    <span class="o">=</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">,</span>
        <span class="p">.</span><span class="n">priority</span>   <span class="o">=</span> <span class="n">NF_IP_PRI_NAT_SRC</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">};</span>
</code></pre></div></div>
<p>nat表的初始化：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="n">iptable_nat_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
    <span class="n">iptable_nat_table_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">init_net</span><span class="p">)</span>
        <span class="k">struct</span> <span class="n">ipt_replace</span> <span class="o">*</span><span class="n">repl</span><span class="p">;</span>
        <span class="n">repl</span> <span class="o">=</span> <span class="n">ipt_alloc_initial_table</span><span class="p">(</span><span class="o">&amp;</span><span class="n">nf_nat_ipv4_table</span><span class="p">);</span>
        <span class="c1">//调用ipt_register_table注册nat表</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">ipt_register_table</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">nf_nat_ipv4_table</span><span class="p">,</span> <span class="n">repl</span><span class="p">,</span>
                 <span class="n">nf_nat_ipv4_ops</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">net</span><span class="o">-&gt;</span><span class="n">ipv4</span><span class="p">.</span><span class="n">nat_table</span><span class="p">);</span>

</code></pre></div></div>

<h3 id="nat钩子回调函数">NAT钩子回调函数</h3>

<p>NAT的核心实现位于net/netfilter/nf_nat_core.c。NAT实现的基本元素为结构nf_nat_l4proto和nf_nat_l3proto。
(在3.7之前的内核中，使用的是结构nf_nat_protocol)。这两个结构都包含函数指针manip_pkt()，它会修改数据报头。
下面看下这两个结构。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_nat_l3proto</span> <span class="n">nf_nat_l3proto_ipv4</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">l3proto</span>        <span class="o">=</span> <span class="n">NFPROTO_IPV4</span><span class="p">,</span>
    <span class="p">.</span><span class="n">in_range</span>       <span class="o">=</span> <span class="n">nf_nat_ipv4_in_range</span><span class="p">,</span>
    <span class="p">.</span><span class="n">secure_port</span>        <span class="o">=</span> <span class="n">nf_nat_ipv4_secure_port</span><span class="p">,</span>
    <span class="p">.</span><span class="n">manip_pkt</span>      <span class="o">=</span> <span class="n">nf_nat_ipv4_manip_pkt</span><span class="p">,</span> <span class="c1">//修改ip包</span>
    <span class="p">.</span><span class="n">csum_update</span>        <span class="o">=</span> <span class="n">nf_nat_ipv4_csum_update</span><span class="p">,</span>
    <span class="p">.</span><span class="n">csum_recalc</span>        <span class="o">=</span> <span class="n">nf_nat_ipv4_csum_recalc</span><span class="p">,</span>
<span class="cp">#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
</span>    <span class="p">.</span><span class="n">nlattr_to_range</span>    <span class="o">=</span> <span class="n">nf_nat_ipv4_nlattr_to_range</span><span class="p">,</span>
<span class="cp">#endif
#ifdef CONFIG_XFRM
</span>    <span class="p">.</span><span class="n">decode_session</span>     <span class="o">=</span> <span class="n">nf_nat_ipv4_decode_session</span><span class="p">,</span>
<span class="cp">#endif
</span><span class="p">};</span>

<span class="c1">//专门看下这个修改ip包的函数nf_nat_ipv4_manip_pkt</span>
<span class="k">static</span> <span class="n">bool</span> <span class="nf">nf_nat_ipv4_manip_pkt</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
                  <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">iphdroff</span><span class="p">,</span>
                  <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_nat_l4proto</span> <span class="o">*</span><span class="n">l4proto</span><span class="p">,</span>
                  <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_conntrack_tuple</span> <span class="o">*</span><span class="n">target</span><span class="p">,</span>
                  <span class="k">enum</span> <span class="n">nf_nat_manip_type</span> <span class="n">maniptype</span><span class="p">)</span>
    <span class="p">...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">maniptype</span> <span class="o">==</span> <span class="n">NF_NAT_MANIP_SRC</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">csum_replace4</span><span class="p">(</span><span class="o">&amp;</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">check</span><span class="p">,</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">saddr</span><span class="p">,</span> <span class="n">target</span><span class="o">-&gt;</span><span class="n">src</span><span class="p">.</span><span class="n">u3</span><span class="p">.</span><span class="n">ip</span><span class="p">);</span>
        <span class="n">iph</span><span class="o">-&gt;</span><span class="n">saddr</span> <span class="o">=</span> <span class="n">target</span><span class="o">-&gt;</span><span class="n">src</span><span class="p">.</span><span class="n">u3</span><span class="p">.</span><span class="n">ip</span><span class="p">;</span> <span class="c1">//修改源IP</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">csum_replace4</span><span class="p">(</span><span class="o">&amp;</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">check</span><span class="p">,</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">daddr</span><span class="p">,</span> <span class="n">target</span><span class="o">-&gt;</span><span class="n">dst</span><span class="p">.</span><span class="n">u3</span><span class="p">.</span><span class="n">ip</span><span class="p">);</span>
        <span class="n">iph</span><span class="o">-&gt;</span><span class="n">daddr</span> <span class="o">=</span> <span class="n">target</span><span class="o">-&gt;</span><span class="n">dst</span><span class="p">.</span><span class="n">u3</span><span class="p">.</span><span class="n">ip</span><span class="p">;</span> <span class="c1">//修改目标IP</span>
    <span class="p">}</span>

<span class="c1">//TCP</span>
<span class="k">const</span> <span class="k">struct</span> <span class="n">nf_nat_l4proto</span> <span class="n">nf_nat_l4proto_tcp</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">l4proto</span>        <span class="o">=</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span>
    <span class="p">.</span><span class="n">manip_pkt</span>      <span class="o">=</span> <span class="n">tcp_manip_pkt</span><span class="p">,</span> <span class="c1">//修改IP包</span>
    <span class="p">.</span><span class="n">in_range</span>       <span class="o">=</span> <span class="n">nf_nat_l4proto_in_range</span><span class="p">,</span>
    <span class="p">.</span><span class="n">unique_tuple</span>       <span class="o">=</span> <span class="n">tcp_unique_tuple</span><span class="p">,</span>
<span class="cp">#if IS_ENABLED(CONFIG_NF_CT_NETLINK)
</span>    <span class="p">.</span><span class="n">nlattr_to_range</span>    <span class="o">=</span> <span class="n">nf_nat_l4proto_nlattr_to_range</span><span class="p">,</span>
<span class="cp">#endif
</span><span class="p">};</span>

<span class="c1">//看下tcp_manip_pkt, udp的类似</span>
<span class="k">static</span> <span class="n">bool</span> <span class="nf">tcp_manip_pkt</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
          <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_nat_l3proto</span> <span class="o">*</span><span class="n">l3proto</span><span class="p">,</span>
          <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">iphdroff</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">hdroff</span><span class="p">,</span>
          <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_conntrack_tuple</span> <span class="o">*</span><span class="n">tuple</span><span class="p">,</span>
          <span class="k">enum</span> <span class="n">nf_nat_manip_type</span> <span class="n">maniptype</span><span class="p">)</span>
    <span class="p">...</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">maniptype</span> <span class="o">==</span> <span class="n">NF_NAT_MANIP_SRC</span><span class="p">)</span> <span class="p">{</span>
        <span class="cm">/* Get rid of src port */</span>
        <span class="n">newport</span> <span class="o">=</span> <span class="n">tuple</span><span class="o">-&gt;</span><span class="n">src</span><span class="p">.</span><span class="n">u</span><span class="p">.</span><span class="n">tcp</span><span class="p">.</span><span class="n">port</span><span class="p">;</span>
        <span class="n">portptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">hdr</span><span class="o">-&gt;</span><span class="n">source</span><span class="p">;</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="cm">/* Get rid of dst port */</span>
        <span class="n">newport</span> <span class="o">=</span> <span class="n">tuple</span><span class="o">-&gt;</span><span class="n">dst</span><span class="p">.</span><span class="n">u</span><span class="p">.</span><span class="n">tcp</span><span class="p">.</span><span class="n">port</span><span class="p">;</span>
        <span class="n">portptr</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">hdr</span><span class="o">-&gt;</span><span class="n">dest</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">oldport</span> <span class="o">=</span> <span class="o">*</span><span class="n">portptr</span><span class="p">;</span>
    <span class="o">*</span><span class="n">portptr</span> <span class="o">=</span> <span class="n">newport</span><span class="p">;</span> <span class="c1">//修改端口号</span>
</code></pre></div></div>

<p>继续看下NAT模块注册的netfilter钩子函数。IPv4 NAT模块在4个挂载点注册了钩子函数，
这4个函数最终都调用到nf_nat_ipv4_fn()。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nf_nat_ipv4_fn</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">priv</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_state</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span>
           <span class="kt">unsigned</span> <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">do_chain</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">priv</span><span class="p">,</span>
                    <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span>
                    <span class="k">const</span> <span class="k">struct</span> <span class="n">nf_hook_state</span> <span class="o">*</span><span class="n">state</span><span class="p">,</span>
                    <span class="k">struct</span> <span class="n">nf_conn</span> <span class="o">*</span><span class="n">ct</span><span class="p">))</span>
    <span class="k">struct</span> <span class="n">nf_conn</span> <span class="o">*</span><span class="n">ct</span><span class="p">;</span>
    <span class="k">enum</span> <span class="n">ip_conntrack_info</span> <span class="n">ctinfo</span><span class="p">;</span>
    <span class="n">ct</span> <span class="o">=</span> <span class="n">nf_ct_get</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ctinfo</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ct</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">NF_ACCEPT</span><span class="p">;</span> <span class="c1">//没有连接跟踪就直接返回</span>

    <span class="k">switch</span> <span class="p">(</span><span class="n">ctinfo</span><span class="p">)</span>
    <span class="k">case</span> <span class="n">IP_CT_NEW</span><span class="p">:</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">nf_nat_initialized</span><span class="p">(</span><span class="n">ct</span><span class="p">,</span> <span class="n">maniptype</span><span class="p">))</span>
            <span class="c1">//do_chain最终调用ipt_do_table，在nat标准查找指定条目，找到则调用target的回调函数</span>
            <span class="n">do_chain</span><span class="p">(</span><span class="n">priv</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="n">ct</span><span class="p">);</span>

    <span class="c1">//执行报文修改操作</span>
    <span class="n">nf_nat_packet</span><span class="p">(</span><span class="n">ct</span><span class="p">,</span> <span class="n">ctinfo</span><span class="p">,</span> <span class="n">state</span><span class="o">-&gt;</span><span class="n">hook</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
        <span class="c1">//这里的l3proto对应前面讲的nf_nat_l3proto_ipv4</span>
        <span class="n">l3proto</span> <span class="o">=</span> <span class="n">__nf_nat_l3proto_find</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">src</span><span class="p">.</span><span class="n">l3num</span><span class="p">);</span>
        <span class="c1">//如果是TCP的话，l4proto是nf_nat_l4proto_tcp</span>
        <span class="n">l4proto</span> <span class="o">=</span> <span class="n">__nf_nat_l4proto_find</span><span class="p">(</span><span class="n">target</span><span class="p">.</span><span class="n">src</span><span class="p">.</span><span class="n">l3num</span><span class="p">,</span><span class="n">target</span><span class="p">.</span><span class="n">dst</span><span class="p">.</span><span class="n">protonum</span><span class="p">)</span>
        <span class="n">l3proto</span><span class="o">-&gt;</span><span class="n">manip_pkt</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">l4proto</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">target</span><span class="p">,</span> <span class="n">mtype</span><span class="p">)</span> <span class="c1">//调用manip_pkt函数</span>
</code></pre></div></div>

<hr />
<p>参考文章：
https://www.kancloud.cn/pshizhsysu/network/2158320</p>]]></content><author><name></name></author><summary type="html"><![CDATA[本文走读内核网络之Netfilter子系统相关的源码。源码基于kernel 4.14版本。]]></summary></entry><entry><title type="html">Linux内核源码走读之IPv4及IPv6</title><link href="https://jiansoft.net/2022/06/30/linux_source_code_ipv4_and_ipv6.html" rel="alternate" type="text/html" title="Linux内核源码走读之IPv4及IPv6" /><published>2022-06-30T00:00:00+00:00</published><updated>2022-06-30T00:00:00+00:00</updated><id>https://jiansoft.net/2022/06/30/linux_source_code_ipv4_and_ipv6</id><content type="html" xml:base="https://jiansoft.net/2022/06/30/linux_source_code_ipv4_and_ipv6.html"><![CDATA[<p>最近在看内核网络协议栈的代码，打算写几篇文章记录下。本文是关于IPv4及IPv6相关的内核源码走读，包括IPv4/IPv6的初始化，以及IP报文的接收和发送。</p>

<h2 id="ipv4">IPv4</h2>

<h3 id="ipv4报头">IPv4报头</h3>

<p>首先看下IPv4报头的定义，对应内核源码中的结构体是struct iphdr:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">iphdr</span> <span class="p">{</span>
    <span class="n">__u8</span>    <span class="n">ihl</span><span class="o">:</span><span class="mi">4</span><span class="p">,</span>  <span class="c1">//header length, 以4字节为单位，最小为4，最大为15</span>
        <span class="nl">version:</span><span class="mi">4</span><span class="p">;</span>  <span class="c1">//总是4</span>
    <span class="n">__u8</span>    <span class="n">tos</span><span class="p">;</span>
    <span class="n">__be16</span>  <span class="n">tot_len</span><span class="p">;</span>  <span class="c1">//包括报头在内的数据包总长度</span>
    <span class="n">__be16</span>  <span class="n">id</span><span class="p">;</span>  <span class="c1">//对于分段来说，所有分段的id值都必须相同</span>
    <span class="n">__be16</span>  <span class="n">frag_off</span><span class="p">;</span>  <span class="c1">//后13bit为分段的偏移量，以8Byte为单位</span>
    <span class="n">__u8</span>    <span class="n">ttl</span><span class="p">;</span>  <span class="c1">//存活时间，每个转发节点都会将ttl减1</span>
    <span class="n">__u8</span>    <span class="n">protocol</span><span class="p">;</span>  <span class="c1">//包所属的第四层协议</span>
    <span class="n">__sum16</span> <span class="n">check</span><span class="p">;</span>  <span class="c1">//报头校验和</span>
    <span class="n">__be32</span>  <span class="n">saddr</span><span class="p">;</span>  <span class="c1">//源IP地址</span>
    <span class="n">__be32</span>  <span class="n">daddr</span><span class="p">;</span>  <span class="c1">//目的IP地址</span>
    <span class="cm">/*The options start here. */</span> <span class="c1">//IP选项，可选</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="ipv4的初始化">IPv4的初始化</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//net/ipv4/af_inet.c</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">inet_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcp_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>  <span class="c1">//所有注册的协议可以通过cat /proc/net/protocols查看</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">udp_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">raw_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ping_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">sock_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inet_family_ops</span><span class="p">)</span>

    <span class="c1">//注册各协议的接收处理函数，最终赋值到全局变量inet_protos[protocol]</span>
    <span class="n">inet_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">icmp_protocol</span><span class="p">,</span> <span class="n">IPPROTO_ICMP</span><span class="p">)</span>  
    <span class="n">inet_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">udp_protocol</span><span class="p">,</span> <span class="n">IPPROTO_UDP</span><span class="p">)</span>
    <span class="n">inet_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcp_protocol</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">)</span>
    <span class="n">inet_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">igmp_protocol</span><span class="p">,</span> <span class="n">IPPROTO_IGMP</span><span class="p">)</span>
    
    <span class="c1">//注册各协议的socket interface接口</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">q</span> <span class="o">=</span> <span class="n">inetsw_array</span><span class="p">;</span> <span class="n">q</span> <span class="o">&lt;</span> <span class="o">&amp;</span><span class="n">inetsw_array</span><span class="p">[</span><span class="n">INETSW_ARRAY_LEN</span><span class="p">];</span> <span class="o">++</span><span class="n">q</span><span class="p">)</span>
        <span class="n">inet_register_protosw</span><span class="p">(</span><span class="n">q</span><span class="p">);</span>

    <span class="n">arp_init</span><span class="p">()</span>  <span class="c1">//arp模块初始化</span>
        <span class="n">dev_add_pack</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arp_packet_type</span><span class="p">)</span>  <span class="c1">//注册ETH_P_ARP=0x0806类型的处理函数</span>
        <span class="n">arp_proc_init</span><span class="p">()</span>  <span class="c1">//cat /proc/net/arp, 查看arp表项</span>
        <span class="n">register_netdevice_notifier</span><span class="p">(</span><span class="o">&amp;</span><span class="n">arp_netdev_notifier</span><span class="p">)</span>  <span class="c1">//注册netdevice事件监听</span>
    <span class="n">ip_init</span><span class="p">()</span>
        <span class="n">ip_rt_init</span> <span class="c1">//ip路由相关的初始化</span>
            <span class="n">devinet_init</span>
                <span class="c1">//注册netdevice时间回调</span>
                <span class="n">register_netdevice_notifier</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ip_netdev_notifier</span><span class="p">);</span>
                    <span class="c1">//这里ip_netdev_notifier的回调函数是inetdev_event，看下这个回调</span>
                    <span class="kt">int</span> <span class="nf">inetdev_event</span><span class="p">(</span><span class="k">struct</span> <span class="n">notifier_block</span> <span class="o">*</span><span class="n">this</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">event</span><span class="p">,</span><span class="kt">void</span> <span class="o">*</span><span class="n">ptr</span><span class="p">)</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">event</span> <span class="o">==</span> <span class="n">NETDEV_REGISTER</span><span class="p">)</span>
                            <span class="c1">//给net_device创建in_device，in_device即ip相关的配置，比如ip地址</span>
                            <span class="c1">//可以通过ip或ifconfig来修改</span>
                            <span class="n">in_dev</span> <span class="o">=</span> <span class="n">inetdev_init</span><span class="p">(</span><span class="n">dev</span><span class="p">);</span>
    <span class="n">tcp_init</span><span class="p">()</span>
    <span class="n">udp_init</span><span class="p">()</span>
    <span class="n">ping_init</span><span class="p">()</span>
    <span class="n">icmp_init</span><span class="p">()</span>
        <span class="n">icmp_sk_init</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">)</span>
            <span class="c1">//每CPU注册一个ICMP RAW socket，用于处理接收的ICMP报文</span>
            <span class="n">inet_ctl_sock_create</span>
    <span class="n">ip_mr_init</span>  <span class="c1">//组播路由初始化</span>

    <span class="c1">//注册IP协议ETH_P_IP=0x0800接受处理函数ip_rcv，即注册IP报文接收入口函数</span>
    <span class="n">dev_add_pack</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ip_packet_type</span><span class="p">)</span>
</code></pre></div></div>

<p>这里展开看一下dev_add_pack函数</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">dev_add_pack</span><span class="p">(</span><span class="k">struct</span> <span class="n">packet_type</span> <span class="o">*</span><span class="n">pt</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="n">ptype_head</span><span class="p">(</span><span class="n">pt</span><span class="p">)</span>
         <span class="k">return</span> <span class="o">&amp;</span><span class="n">ptype_base</span><span class="p">[</span><span class="n">ntohs</span><span class="p">(</span><span class="n">pt</span><span class="o">-&gt;</span><span class="n">type</span><span class="p">)]</span>  <span class="c1">//ptype_base是一个全局变量数组，记录每个协议的处理函数</span>
    <span class="n">list_add_rcu</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pt</span><span class="o">-&gt;</span><span class="n">list</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>  <span class="c1">//将处理函数pt赋值到全局变量ptype_base中</span>
</code></pre></div></div>

<h3 id="接收ipv4数据包">接收IPv4数据包</h3>

<p>Linux网卡驱动接收包有两种方式，NAPI和非NAPI。现在新的网卡驱动一般采用NAPI方式。
网卡驱动在通过接收中断、软中断等一些列处理后，最终调用napi_gro_receive将数据包上报到协议栈处理。</p>

<p>非NAPI方式，最终调用netif_rx。这里跟一下从napi_gro_receive开始的收包流程。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gro_result_t</span> <span class="nf">napi_gro_receive</span><span class="p">(</span><span class="k">struct</span> <span class="n">napi_struct</span> <span class="o">*</span><span class="n">napi</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">napi_skb_finish</span><span class="p">(</span><span class="n">dev_gro_receive</span><span class="p">(</span><span class="n">napi</span><span class="p">,</span> <span class="n">skb</span><span class="p">),</span> <span class="n">skb</span><span class="p">);</span>
        <span class="n">netif_receive_skb_internal</span>
            <span class="n">__netif_receive_skb</span>
                <span class="n">__netif_receive_skb_core</span>
</code></pre></div></div>
<p>__netif_receive_skb_core可以认为是内核协议栈处理接收包的起点，下面跟一下这个函数。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">__netif_receive_skb_core</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="n">bool</span> <span class="n">pfmemalloc</span><span class="p">)</span>
    <span class="c1">//ptype_all是所有包类型的接收处理，对应tcpdump、raw socket等处理</span>
    <span class="n">list_for_each_entry_rcu</span><span class="p">(</span><span class="n">ptype</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ptype_all</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">pt_prev</span><span class="p">)</span>
            <span class="n">ret</span> <span class="o">=</span> <span class="n">deliver_skb</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">pt_prev</span><span class="p">,</span> <span class="n">orig_dev</span><span class="p">);</span>
        <span class="n">pt_prev</span> <span class="o">=</span> <span class="n">ptype</span><span class="p">;</span>

    <span class="c1">//如果这个设备有注册rx_handler，通过接口netdev_rx_handler_register注册</span>
    <span class="c1">//则将包交给注册的rx_handler处理。例如加入网桥的接口会被注册rx_handler</span>
    <span class="n">rx_handler</span> <span class="o">=</span> <span class="n">rcu_dereference</span><span class="p">(</span><span class="n">skb</span><span class="o">-&gt;</span><span class="n">dev</span><span class="o">-&gt;</span><span class="n">rx_handler</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">rx_handler</span><span class="p">)</span>
        <span class="n">rx_handler</span><span class="p">(</span><span class="o">&amp;</span><span class="n">skb</span><span class="p">)</span>

    <span class="c1">//交给ptype_base里注册的对应协议类型的处理函数</span>
    <span class="n">deliver_ptype_list_skb</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pt_prev</span><span class="p">,</span> <span class="n">orig_dev</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span>
                <span class="o">&amp;</span><span class="n">ptype_base</span><span class="p">[</span><span class="n">ntohs</span><span class="p">(</span><span class="n">type</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">PTYPE_HASH_MASK</span><span class="p">]);</span>
</code></pre></div></div>
<p>ptype_base里的对象是通过dev_add_pack接口注册的，在上节IPv4初始化里，我们知道IPv4协议注册的对象是ip_packet_type</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">packet_type</span> <span class="n">ip_packet_type</span> <span class="n">__read_mostly</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">cpu_to_be16</span><span class="p">(</span><span class="n">ETH_P_IP</span><span class="p">),</span>
    <span class="p">.</span><span class="n">func</span> <span class="o">=</span> <span class="n">ip_rcv</span><span class="p">,</span> <span class="c1">//IP协议报文的入口</span>
<span class="p">};</span>
</code></pre></div></div>

<p>接下来跟踪ip_rcv源码。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ip_rcv</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">packet_type</span> <span class="o">*</span><span class="n">pt</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">orig_dev</span><span class="p">)</span>
    <span class="n">iph</span> <span class="o">=</span> <span class="n">ip_hdr</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">ihl</span> <span class="o">&lt;</span> <span class="mi">5</span> <span class="o">||</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">version</span> <span class="o">!=</span> <span class="mi">4</span><span class="p">)</span> <span class="c1">//如果包长度小于20或版本不是4，则报头不合法</span>
        <span class="k">goto</span> <span class="n">inhdr_error</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">ip_fast_csum</span><span class="p">((</span><span class="n">u8</span> <span class="o">*</span><span class="p">)</span><span class="n">iph</span><span class="p">,</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">ihl</span><span class="p">)))</span> <span class="c1">//报头校验和校验</span>
        <span class="k">goto</span> <span class="n">csum_error</span><span class="p">;</span>

    <span class="c1">//netfilter的第一个钩子挂载点，NF_INET_PRE_ROUTING</span>
    <span class="c1">//如果没被netfilter过滤，最终调用ip_rcv_finish</span>
    <span class="k">return</span> <span class="nf">NF_HOOK</span><span class="p">(</span><span class="n">NFPROTO_IPV4</span><span class="p">,</span> <span class="n">NF_INET_PRE_ROUTING</span><span class="p">,</span>
               <span class="n">net</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">dev</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span>
               <span class="n">ip_rcv_finish</span><span class="p">);</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">ip_rcv_finish</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">skb_valid_dst</span><span class="p">(</span><span class="n">skb</span><span class="p">))</span>
        <span class="c1">//在路由选择子系统进行查找</span>
        <span class="n">err</span> <span class="o">=</span> <span class="n">ip_route_input_noref</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">daddr</span><span class="p">,</span> <span class="n">iph</span><span class="o">-&gt;</span><span class="n">saddr</span><span class="p">,</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">tos</span><span class="p">,</span> <span class="n">dev</span><span class="p">);</span>
            <span class="c1">//ip_route_input_noref中如果判断此IP包是发给本地，</span>
            <span class="c1">//则skb-&gt;_skb_refdst的input函数赋值为ip_local_deliver</span>
            <span class="c1">//如果需要转发，则iput函数赋值为ip_forward</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">iph</span><span class="o">-&gt;</span><span class="n">ihl</span> <span class="o">&gt;</span> <span class="mi">5</span> <span class="o">&amp;&amp;</span> <span class="n">ip_rcv_options</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">dev</span><span class="p">))</span> <span class="c1">//ip报头的选项处理</span>
        <span class="k">goto</span> <span class="n">drop</span><span class="p">;</span>

    <span class="c1">//调用路由选择子系统查找到的input函数</span>
    <span class="k">return</span> <span class="nf">dst_input</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
        <span class="n">skb_dst</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">input</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span> <span class="c1">//即调用skb-&gt;_skb_refdst对象的input函数</span>

<span class="c1">//这里看一下struct dst_entry，即路由查找的结果</span>
<span class="k">struct</span> <span class="n">dst_entry</span> <span class="p">{</span>
    <span class="p">...</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">input</span><span class="p">)(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="p">);</span> <span class="c1">//路由查找后的接收处理函数，发给本机的包对应函数为ip_local_deliver</span>
    <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">output</span><span class="p">)(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">);</span>

    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>继续看一下IP包发给本地时的处理函数ip_local_deliver</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ip_local_deliver</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ip_is_fragment</span><span class="p">(</span><span class="n">ip_hdr</span><span class="p">(</span><span class="n">skb</span><span class="p">)))</span>
        <span class="n">ip_defrag</span> <span class="c1">//如果是分片报文，则交给解分片函数处理</span>

    <span class="c1">//netfilter的第二个钩子挂载点NF_INET_LOCAL_IN</span>
    <span class="k">return</span> <span class="n">NF_HOOK</span><span class="p">(</span><span class="n">NFPROTO_IPV4</span><span class="p">,</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">,</span>
               <span class="n">net</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">skb</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span>
               <span class="n">ip_local_deliver_finish</span><span class="p">);</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">ip_local_deliver_finish</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="c1">//先查并发给raw socket</span>
    <span class="n">raw</span> <span class="o">=</span> <span class="n">raw_local_deliver</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">protocol</span><span class="p">);</span>

    <span class="n">ipprot</span> <span class="o">=</span> <span class="n">rcu_dereference</span><span class="p">(</span><span class="n">inet_protos</span><span class="p">[</span><span class="n">protocol</span><span class="p">]);</span>  <span class="c1">//inet_protos: 各协议注册的处理函数</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ipprot</span><span class="p">)</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">ipprot</span><span class="o">-&gt;</span><span class="n">handler</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>  <span class="c1">//调用注册的协议处理函数，通过接口inet_add_protocol注册</span>
                                     <span class="c1">//比如IPPROTO_ICMP的处理函数icmp_rcv，IPPROTO_TCP的是tcp_v4_rcv</span>
</code></pre></div></div>

<h3 id="发送ipv4数据包">发送IPv4数据包</h3>

<p>IPv4为L4层提供将数据包发到L2层的接口和功能。</p>

<p>从L4发送IPv4数据包的主要方法有两个，一个是方法ip_queue_xmit()，由TCPv4使用；一个是ip_append_data()，由UDPv4和ICMPv4使用。</p>

<p>先看方法ip_queue_xmit()</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">ip_queue_xmit</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">flowi</span> <span class="o">*</span><span class="n">fl</span><span class="p">)</span>
    <span class="k">struct</span> <span class="n">rtable</span> <span class="o">*</span><span class="n">rt</span><span class="p">;</span>
    <span class="c1">//在路由子系统中查找路由</span>
    <span class="n">rt</span> <span class="o">=</span> <span class="n">ip_route_output_ports</span>
        <span class="n">ip_route_output_flow</span>
            <span class="n">__ip_route_output_key</span>
                <span class="n">ip_route_output_key_hash</span>
                    <span class="nf">ip_route_output_key_hash_rcu</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">fl4</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">res</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
                        <span class="n">fib_lookup</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">fl4</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">skb_dst_set_noref</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rt</span><span class="o">-&gt;</span><span class="n">dst</span><span class="p">);</span>
    <span class="p">...</span> <span class="c1">//此处是一些填写ip header的逻辑</span>
    <span class="n">res</span> <span class="o">=</span> <span class="n">ip_local_out</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
        <span class="n">__ip_local_out</span>
            <span class="c1">//netfilter钩子挂载点NF_INET_LOCAL_OUT</span>
            <span class="n">nf_hook</span><span class="p">(</span><span class="n">NFPROTO_IPV4</span><span class="p">,</span> <span class="n">NF_INET_LOCAL_OUT</span><span class="p">,</span>
               <span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb_dst</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">,</span>
               <span class="n">dst_output</span><span class="p">);</span>
        <span class="n">dst_output</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">)</span>
            <span class="n">skb_dst</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">output</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">)</span>  <span class="c1">//一般地，这里的output是ip_output</span>

<span class="c1">//继续看下ip_output</span>
<span class="kt">int</span> <span class="nf">ip_output</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="c1">//netfilter的钩子挂载点NF_INET_POST_ROUTING</span>
    <span class="n">NF_HOOK_COND</span><span class="p">(</span><span class="n">NFPROTO_IPV4</span><span class="p">,</span> <span class="n">NF_INET_POST_ROUTING</span><span class="p">,</span>
                <span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">dev</span><span class="p">,</span>
                <span class="n">ip_finish_output</span><span class="p">,</span>
                <span class="o">!</span><span class="p">(</span><span class="n">IPCB</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">IPSKB_REROUTED</span><span class="p">));</span>

<span class="c1">//ip_finish_output</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">ip_finish_output</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">ip_finish_output2</span>
        <span class="c1">//从字面上理解，找到目的邻居，然后发给邻居</span>
        <span class="n">neigh</span> <span class="o">=</span> <span class="n">__ipv4_neigh_lookup_noref</span><span class="p">(</span><span class="n">dev</span><span class="p">,</span> <span class="n">nexthop</span><span class="p">);</span>
        <span class="n">res</span> <span class="o">=</span> <span class="n">neigh_output</span><span class="p">(</span><span class="n">neigh</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
            <span class="n">dev_queue_xmit</span> <span class="c1">//最终交给网卡驱动</span>
</code></pre></div></div>

<p>再看方法ip_append_data</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">ip_append_data</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">flowi4</span> <span class="o">*</span><span class="n">fl4</span><span class="p">,</span>
           <span class="kt">int</span> <span class="n">getfrag</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">from</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">to</span><span class="p">,</span> <span class="kt">int</span> <span class="n">offset</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">,</span>
                   <span class="kt">int</span> <span class="n">odd</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">),</span>
           <span class="kt">void</span> <span class="o">*</span><span class="n">from</span><span class="p">,</span> <span class="kt">int</span> <span class="n">length</span><span class="p">,</span> <span class="kt">int</span> <span class="n">transhdrlen</span><span class="p">,</span>
           <span class="k">struct</span> <span class="n">ipcm_cookie</span> <span class="o">*</span><span class="n">ipc</span><span class="p">,</span> <span class="k">struct</span> <span class="n">rtable</span> <span class="o">**</span><span class="n">rtp</span><span class="p">,</span>
           <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span>
    <span class="n">__ip_append_data</span>
        <span class="c1">//此函数很长，此处略过。</span>
</code></pre></div></div>

<h3 id="转发">转发</h3>

<p>在前面接收IPv4数据包中讲到，接收的数据包经过路由查找后，如果是发给本机的，则走到ip_local_deliver。
如果是要转发，则走到ip_forward。下面看下ip_forward的代码。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ip_forward</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="c1">//netfilter挂载点 NF_NET_FORWARD</span>
    <span class="k">return</span> <span class="n">NF_HOOK</span><span class="p">(</span><span class="n">NFPROTO_IPV4</span><span class="p">,</span> <span class="n">NF_INET_FORWARD</span><span class="p">,</span>
               <span class="n">net</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">skb</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">,</span> <span class="n">rt</span><span class="o">-&gt;</span><span class="n">dst</span><span class="p">.</span><span class="n">dev</span><span class="p">,</span>
               <span class="n">ip_forward_finish</span><span class="p">);</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">ip_forward_finish</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">dst_output</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">)</span>
    <span class="c1">//dst_output在上节讲到过，最终会调用到dev_queue_xmit交给网卡驱动</span>
</code></pre></div></div>

<p>关于IPv4，还有一些主题，比如接收组播数据包、IP选项、分段等，后面有时间再补充。</p>

<p>IPv4报文接收和发送的流程图如下：</p>

<p><img src="/assets/image/2022/06/ipv4_rxtx_codeflow.png" alt="codeflow.png" /></p>

<h2 id="ipv6">IPv6</h2>

<h3 id="ipv6地址">IPv6地址</h3>

<p>学习IPv6之前，先看下IPv6地址。IPv6地址长度为128bit，由8部分组成，每部分16bit。</p>

<p>IPv6地址的写法为：
xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx。</p>

<p>如果一部分、或连续的几部分都为0，则可用::表示。</p>

<p>在IPv6中，需要用到地址前缀，前缀相当于IPv4子网掩码，用/n表示。
比如2001:da7::/32，表示开头32bit为2001:0da7的所有IPv6地址。</p>

<p>一些特殊的IPv6地址：</p>
<ul>
  <li>每个接口都必须至少有一个链路本地单播地址。路由器不得转发此地址的包。地址前缀为fe80::/64</li>
  <li>全局单播地址的通用格式如下：n位全局路由选择前缀，m位子网ID，余下的为接口ID</li>
  <li>::1为环回地址</li>
  <li>全0(0:0:0:0:0:0:0:0)地址称为未指定地址，用于DAD(重复地址检测)</li>
  <li>映射IPv4的IPv6地址，前80位为0，接下来16位为1，余下32位为IPv4地址，例如：::ffff:192.0.2.128</li>
</ul>

<p>在Linux中，IPv6地址用in6_addr表示。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">in6_addr</span> <span class="p">{</span>
    <span class="k">union</span> <span class="p">{</span>
        <span class="n">__u8</span>        <span class="n">u6_addr8</span><span class="p">[</span><span class="mi">16</span><span class="p">];</span> <span class="c1">//用union定义了3种形式的128bit长度</span>
        <span class="n">__be16</span>      <span class="n">u6_addr16</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
        <span class="n">__be32</span>      <span class="n">u6_addr32</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
    <span class="p">}</span> <span class="n">in6_u</span><span class="p">;</span>
<span class="cp">#define s6_addr         in6_u.u6_addr8
#define s6_addr16       in6_u.u6_addr16
#define s6_addr32       in6_u.u6_addr32
</span><span class="p">};</span>
</code></pre></div></div>

<h3 id="ipv6报头">IPv6报头</h3>

<p>IPv6报头在Linux中的结构体是struct ipv6hdr:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">ipv6hdr</span> <span class="p">{</span>
    <span class="n">__u8</span>            <span class="n">priority</span><span class="o">:</span><span class="mi">4</span><span class="p">,</span> <span class="c1">//流量优先级</span>
                    <span class="nl">version:</span><span class="mi">4</span><span class="p">;</span> <span class="c1">//版本号，总是6</span>
    <span class="n">__u8</span>            <span class="n">flow_lbl</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span> <span class="c1">//流标签</span>
    <span class="n">__be16</span>          <span class="n">payload_len</span><span class="p">;</span> <span class="c1">//数据包的长度，不包含包头</span>
    <span class="n">__u8</span>            <span class="n">nexthdr</span><span class="p">;</span> <span class="c1">//扩展报头或者上层协议编号</span>
    <span class="n">__u8</span>            <span class="n">hop_limit</span><span class="p">;</span> <span class="c1">//相当于ttl</span>

    <span class="k">struct</span>  <span class="n">in6_addr</span>    <span class="n">saddr</span><span class="p">;</span> <span class="c1">//128bit源地址</span>
    <span class="k">struct</span>  <span class="n">in6_addr</span>    <span class="n">daddr</span><span class="p">;</span> <span class="c1">//128bit目的地址</span>
<span class="p">};</span>

</code></pre></div></div>

<p>IPv6扩展报头：
IPv6报头的nexthdr字段，指出下一个报头的编号。没有扩展报头或最后一个扩展报头，指示上层协议。</p>

<h3 id="ipv6初始化">IPv6初始化</h3>

<p>inet6_init执行各种IPv6的初始化工作，位于net/ipv6/af_inet6.c</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">inet6_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
    <span class="c1">//和IPv4初始化类似，一堆协议注册。没跟到这里注册的协议后面怎么用。</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcpv6_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">udpv6_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">udplitev6_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rawv6_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">proto_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pingv6_prot</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

    <span class="n">rawv6_init</span><span class="p">()</span>
    <span class="n">sock_register</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inet6_family_ops</span><span class="p">)</span>
    <span class="n">inet6_net_init</span>
    <span class="n">ip6_mr_init</span>
    <span class="n">icmpv6_init</span>
        <span class="n">icmpv6_sk_init</span>
        <span class="c1">//注册ICMPv6的接收处理函数icmpv6_rcv</span>
        <span class="n">inet6_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">icmpv6_protocol</span><span class="p">,</span> <span class="n">IPPROTO_ICMPV6</span><span class="p">)</span>
    <span class="n">ndisc_init</span><span class="p">()</span>
    <span class="n">igmp6_init</span>
    <span class="n">ipv6_netfilter_init</span>
    <span class="n">ip6_route_init</span>
    <span class="n">ipv6_frag_init</span>
        <span class="n">inet6_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">frag_protocol</span><span class="p">,</span> <span class="n">IPPROTO_FRAGMENT</span><span class="p">)</span>
    <span class="n">udpv6_init</span>
        <span class="n">inet6_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">udpv6_protocol</span><span class="p">,</span> <span class="n">IPPROTO_UDP</span><span class="p">)</span>
    <span class="n">tcpv6_init</span>
        <span class="n">inet6_add_protocol</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcpv6_protocol</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">)</span>
    <span class="n">ipv6_packet_init</span><span class="p">()</span>
        <span class="c1">//注册IPv6协议的接收处理函数 ipv6_rcv</span>
        <span class="c1">//ETH_P_IPV6 = 0x86DD</span>
        <span class="n">dev_add_pack</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ipv6_packet_type</span><span class="p">);</span>

</code></pre></div></div>

<h3 id="接收ipv6数据包">接收IPv6数据包</h3>

<p>IPv6数据包的主接收方法为ipv6_rcv()，看下这个函数源码</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ipv6_rcv</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">dev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">packet_type</span> <span class="o">*</span><span class="n">pt</span><span class="p">,</span> <span class="k">struct</span> <span class="n">net_device</span> <span class="o">*</span><span class="n">orig_dev</span><span class="p">)</span>
    <span class="p">...</span> <span class="c1">//此处省略一系列校验和检查</span>
    <span class="c1">//netfilter挂载点NF_INET_PRE_ROUTING，最终调用ip6_rcv_finish</span>
    <span class="k">return</span> <span class="n">NF_HOOK</span><span class="p">(</span><span class="n">NFPROTO_IPV6</span><span class="p">,</span> <span class="n">NF_INET_PRE_ROUTING</span><span class="p">,</span>
               <span class="n">net</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">dev</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span>
               <span class="n">ip6_rcv_finish</span><span class="p">);</span>

<span class="kt">int</span> <span class="nf">ip6_rcv_finish</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="c1">//路由查找</span>
    <span class="n">ip6_route_input</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
        <span class="n">ip6_route_input_lookup</span>
            <span class="n">fib6_rule_lookup</span>

    <span class="k">return</span> <span class="nf">dst_input</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
        <span class="n">skb_dst</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">input</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span> <span class="c1">//调用路由查找后的input函数</span>
        <span class="c1">//如果是给当前主机的包input为ip6_input</span>
        <span class="c1">//如果需要转发input为ip6_forward</span>
        <span class="c1">//如果数据包目的地址为组播input为ip6_mc_input</span>
</code></pre></div></div>

<p>这里看下本地投递的情形，此时skb_dst(skb)-&gt;input函数为ip6_input</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">ip6_input</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="c1">//netfilter挂载点NF_INET_LOCAL_IN</span>
    <span class="k">return</span> <span class="n">NF_HOOK</span><span class="p">(</span><span class="n">NFPROTO_IPV6</span><span class="p">,</span> <span class="n">NF_INET_LOCAL_IN</span><span class="p">,</span>
               <span class="n">dev_net</span><span class="p">(</span><span class="n">skb</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">skb</span><span class="o">-&gt;</span><span class="n">dev</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span>
               <span class="n">ip6_input_finish</span><span class="p">);</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">ip6_input_finish</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
    <span class="n">raw6_local_deliver</span> <span class="c1">//先投递给原始套接字</span>
    <span class="n">ipprot</span> <span class="o">=</span> <span class="n">rcu_dereference</span><span class="p">(</span><span class="n">inet6_protos</span><span class="p">[</span><span class="n">nexthdr</span><span class="p">])</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ipprot</span><span class="p">)</span> 
        <span class="n">ipprot</span><span class="o">-&gt;</span><span class="n">handler</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span> <span class="c1">//调用在IPv6初始化时注册的协议处理函数</span>
</code></pre></div></div>

<h3 id="发送ipv6数据包">发送IPv6数据包</h3>

<p>IPv6数据包的发送路径与IPv4很像。IPv6中也有两个发送IPv6数据包的主方法：
一个是ip6_xmit，由TCP、SCTP等使用；另一个是ip6_append_data，有UDP和RAW套接字使用。</p>

<p>最终的调用路径为:ip6_local_out-&gt;ip6_output-&gt;ip6_finish_output-&gt;交给网卡驱动。</p>

<p>IPv6报文接收和发送的流程图如下：</p>

<p><img src="/assets/image/2022/06/ipv6_rxtx_codeflow.JPG" alt="codeflow.png" /></p>]]></content><author><name></name></author><summary type="html"><![CDATA[最近在看内核网络协议栈的代码，打算写几篇文章记录下。本文是关于IPv4及IPv6相关的内核源码走读，包括IPv4/IPv6的初始化，以及IP报文的接收和发送。]]></summary></entry><entry><title type="html">Linux netlink详解</title><link href="https://jiansoft.net/2022/05/06/learn_linux_netlink.html" rel="alternate" type="text/html" title="Linux netlink详解" /><published>2022-05-06T00:00:00+00:00</published><updated>2022-05-06T00:00:00+00:00</updated><id>https://jiansoft.net/2022/05/06/learn_linux_netlink</id><content type="html" xml:base="https://jiansoft.net/2022/05/06/learn_linux_netlink.html"><![CDATA[<p>熟悉Linux wifi的同学都知道，wpa_supplicant程序是基于netlink与wifi驱动进行通信的。
(wpa_supplicant是wifi station用户空间守护进程)</p>

<p>本文学习下Linux的netlink，给出用户空间与内核空间基于netlink通信的示例。
示例包括netlink和generic netlink。用户空间程序包括基于原生Linux API和基于libnl API。</p>

<h2 id="netlink基础">netlink基础</h2>

<p>netlink协议是一个基于socket的，用于内核与用户空间进程通信的一个协议。</p>

<p>用户态创建netlink socket的代码如下：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_NETLINK</span><span class="p">,</span> <span class="n">SOCK_RAW</span><span class="p">,</span> <span class="n">MY_NETLINK</span><span class="p">)</span>

<span class="c1">//socket接口的原型为：</span>
<span class="kt">int</span> <span class="nf">socket</span><span class="p">(</span><span class="kt">int</span> <span class="n">domain</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="kt">int</span> <span class="n">protocol</span><span class="p">);</span>
<span class="c1">//这里用到了第三个入参protocol，即创建Netlink socket时要指定协议号</span>
</code></pre></div></div>
<p>内核态创建netlink socket的接口原型为：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span>
<span class="n">netlink_kernel_create</span><span class="p">(</span><span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span><span class="p">,</span> <span class="kt">int</span> <span class="n">unit</span><span class="p">,</span> <span class="k">struct</span> <span class="n">netlink_kernel_cfg</span> <span class="o">*</span><span class="n">cfg</span><span class="p">)</span>
<span class="c1">//这里的第二个参数unit为协议号，与用户空间的protocol相同</span>
</code></pre></div></div>

<p>Netlink消息有固定的格式，struct nlmsghdr</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="p">{</span>
    <span class="n">__u32</span>       <span class="n">nlmsg_len</span><span class="p">;</span>  <span class="cm">/* Length of message including header */</span>
    <span class="n">__u16</span>       <span class="n">nlmsg_type</span><span class="p">;</span> <span class="cm">/* Message content */</span>
    <span class="n">__u16</span>       <span class="n">nlmsg_flags</span><span class="p">;</span>    <span class="cm">/* Additional flags */</span>
    <span class="n">__u32</span>       <span class="n">nlmsg_seq</span><span class="p">;</span>  <span class="cm">/* Sequence number */</span>
    <span class="n">__u32</span>       <span class="n">nlmsg_pid</span><span class="p">;</span>  <span class="cm">/* Sending process port ID */</span>
<span class="p">};</span>
</code></pre></div></div>

<p>完整示例见github: <a href="https://github.com/jian-soft/netlink_examples">https://github.com/jian-soft/netlink_examples</a></p>

<p>下文为关键代码注释。</p>

<h2 id="原生linux-api示例">原生Linux API示例</h2>

<h3 id="内核侧示例">内核侧示例</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//定义自己的netlink协议号</span>
<span class="cp">#define MY_NETLINK 31
</span>
<span class="c1">//接收回调，即内核侧收到用户发来的netlink消息回调</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">netlink_recv_msg</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">...</span>
    <span class="n">nlh</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="p">)</span><span class="n">skb</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">;</span>  <span class="c1">//取netlink消息头</span>
    <span class="n">pid</span> <span class="o">=</span> <span class="n">nlh</span><span class="o">-&gt;</span><span class="n">nlmsg_pid</span><span class="p">;</span> <span class="cm">/* pid of sending process */</span>
    <span class="n">msg</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">nlmsg_data</span><span class="p">(</span><span class="n">nlh</span><span class="p">);</span>  <span class="c1">//取netlink消息data部分</span>
    <span class="n">msg_size</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
    
    <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"netlink_kernel: Received from pid %d: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
    <span class="p">...</span>
<span class="p">}</span>

<span class="c1">//定义netlink_kernel_cfg，即声明接收回调</span>
<span class="k">struct</span> <span class="n">netlink_kernel_cfg</span> <span class="n">cfg</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">input</span> <span class="o">=</span> <span class="n">netlink_recv_msg</span><span class="p">,</span>
<span class="p">};</span>

<span class="c1">//创建内核测netlink socket</span>
<span class="n">g_nl_sock</span> <span class="o">=</span> <span class="n">netlink_kernel_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">init_net</span><span class="p">,</span> <span class="n">MY_NETLINK</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cfg</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">g_nl_sock</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printk</span><span class="p">(</span><span class="n">KERN_ALERT</span> <span class="s">"netlink_kernel: Error creating socket.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="o">-</span><span class="mi">10</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="用户侧示例">用户侧示例</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="c1">//创建socket</span>
    <span class="n">sock_fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">PF_NETLINK</span><span class="p">,</span> <span class="n">SOCK_RAW</span><span class="p">,</span> <span class="n">MY_NETLINK</span><span class="p">);</span>

    <span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">src_addr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">src_addr</span><span class="p">));</span>
    <span class="n">src_addr</span><span class="p">.</span><span class="n">nl_family</span> <span class="o">=</span> <span class="n">AF_NETLINK</span><span class="p">;</span>
    <span class="n">src_addr</span><span class="p">.</span><span class="n">nl_pid</span> <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span> <span class="cm">/* self pid */</span>
    <span class="c1">//绑定端口</span>
    <span class="n">bind</span><span class="p">(</span><span class="n">sock_fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">src_addr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">src_addr</span><span class="p">));</span>
    
    <span class="c1">//设置目标地址为内核netlink</span>
    <span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">dest_addr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dest_addr</span><span class="p">));</span>
    <span class="n">dest_addr</span><span class="p">.</span><span class="n">nl_family</span> <span class="o">=</span> <span class="n">AF_NETLINK</span><span class="p">;</span>
    <span class="n">dest_addr</span><span class="p">.</span><span class="n">nl_pid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* For Linux Kernel */</span>
    <span class="n">dest_addr</span><span class="p">.</span><span class="n">nl_groups</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* unicast */</span>
    
    <span class="c1">//可以通过sendmsg或sendto两种接口向内核发送消息</span>
    <span class="c1">//相应的有recvmsg和recvfrom两种接口接收来自内核的消息</span>
    <span class="c1">//详见github，此处略</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="libnl-api示例">libnl API示例</h2>

<p><strong>libnl</strong>对用户空间Linu原生的netlink API进行了封装，使得用户空间程序更容易编写，尤其是对于generic netlink API。
关于generic netlink API我们下一章节详细介绍。</p>

<p>另外wpa_supplicant与内核wifi驱动的通信就是用的libnl generic netlink API。</p>

<p>先介绍下libnl的主要接口，定义在头文件&lt;netlink/netlink.h&gt;</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//创建netlink socket, libnl中用struct nl_sock表示一个socket</span>
<span class="cp">#include</span> <span class="cpf">&lt;netlink/socket.h&gt;</span><span class="cp">
</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="nf">nl_socket_alloc</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="kt">void</span> <span class="n">nl_socket_free</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>

<span class="c1">//回调配置</span>
<span class="k">struct</span> <span class="n">nl_cb</span> <span class="o">*</span><span class="n">nl_socket_get_cb</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">nl_socket_set_cb</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_cb</span> <span class="o">*</span><span class="n">cb</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">nl_socket_modify_cb</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="p">,</span> <span class="k">enum</span> <span class="n">nl_cb_type</span><span class="p">,</span> <span class="k">enum</span> <span class="n">nl_cb_kind</span><span class="p">,</span> <span class="n">nl_recvmsg_msg_cb_t</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="p">);</span>

<span class="c1">//发送</span>
<span class="kt">int</span> <span class="n">nl_send_auto</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">)</span>
<span class="kt">int</span> <span class="n">nl_send</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">)</span>
<span class="kt">int</span> <span class="n">nl_send_iovec</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="k">struct</span> <span class="n">iovec</span> <span class="o">*</span><span class="n">iov</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="n">iovlen</span><span class="p">)</span>
<span class="kt">int</span> <span class="n">nl_sendmsg</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msghdr</span> <span class="o">*</span><span class="n">hdr</span><span class="p">)</span> <span class="c1">//nl_sendmsg里调用Linux原生sendmsg接口</span>
<span class="kt">int</span> <span class="n">nl_sendto</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span> <span class="c1">//nl_sendto调用Linux原生sendto接口</span>
<span class="kt">int</span> <span class="n">nl_send_simple</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>

<span class="c1">//接收</span>
<span class="kt">int</span> <span class="n">nl_recvmsgs_default</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>

<span class="kt">int</span> <span class="n">nl_recvmsgs</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">nl_cb</span> <span class="o">*</span><span class="n">cb</span><span class="p">)</span>
<span class="c1">//如果socket是阻塞的，就阻塞式接收。recv到数据之后，通过cb进行处理</span>
</code></pre></div></div>

<h3 id="libnl用户侧示例">libnl用户侧示例</h3>

<p>基于上一节的例子，内核测代码不变，用户侧使用libnl重写。</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;netlink/netlink.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;netlink/msg.h&gt;</span><span class="cp">
</span>
<span class="cp">#define MY_NETLINK 31
#define MY_NETLINK_TYPE_SET 0
</span>
<span class="c1">//接收回调</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">my_input</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="n">nlh</span> <span class="o">=</span> <span class="n">nlmsg_hdr</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="n">nlmsg_data</span><span class="p">(</span><span class="n">nlh</span><span class="p">);</span>
    <span class="kt">int</span> <span class="n">datalen</span> <span class="o">=</span> <span class="n">nlmsg_datalen</span><span class="p">(</span><span class="n">nlh</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"input cb: datalen:%d, data:%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">datalen</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
    <span class="c1">//创建并绑定socket</span>
    <span class="n">sk</span> <span class="o">=</span> <span class="n">nl_socket_alloc</span><span class="p">();</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">nl_connect</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">MY_NETLINK</span><span class="p">);</span>

    <span class="c1">//修改接收回调函数，收到任何消息都会回调my_input</span>
    <span class="n">nl_socket_modify_cb</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">NL_CB_MSG_IN</span><span class="p">,</span> <span class="n">NL_CB_CUSTOM</span><span class="p">,</span> <span class="n">my_input</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

    <span class="kt">char</span> <span class="n">msg</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"Hello libnl!</span><span class="se">\n</span><span class="s">"</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">nl_send_simple</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">MY_NETLINK_TYPE_SET</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>

    <span class="c1">//阻塞式等待接收。接收到内核发来的消息后，会进入接收回调my_input</span>
    <span class="n">nl_recvmsgs_default</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

    <span class="n">nl_socket_free</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>

<h2 id="generic-netlink示例基于libnl">generic netlink示例，基于libnl</h2>

<p>netlink通信协议在不修改内核源码的情况下，最大只支持定义32种协议。
随着netlink的使用越来越多，32个协议号已不够用，所以引入了generic netlink。
generic netlink其实是对netlink报文进行了又一次封装，generic netlink使用的netlink协议号是NETLINK_GENERIC=16。</p>

<p>genl的消息格式如下：</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="mi">0</span>                   <span class="mi">1</span>                   <span class="mi">2</span>                   <span class="mi">3</span>
  <span class="mi">0</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span> <span class="mi">0</span> <span class="mi">1</span>
 <span class="o">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
 <span class="o">|</span>                <span class="n">Netlink</span> <span class="n">message</span> <span class="n">header</span> <span class="p">(</span><span class="n">nlmsghdr</span><span class="p">)</span>              <span class="o">|</span>
 <span class="o">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
 <span class="o">|</span>           <span class="n">Generic</span> <span class="n">Netlink</span> <span class="n">message</span> <span class="n">header</span> <span class="p">(</span><span class="n">genlmsghdr</span><span class="p">)</span>         <span class="o">|</span>
 <span class="o">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
 <span class="o">|</span>             <span class="n">Optional</span> <span class="n">user</span> <span class="n">specific</span> <span class="n">message</span> <span class="n">header</span>             <span class="o">|</span>
 <span class="o">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
 <span class="o">|</span>           <span class="n">Optional</span> <span class="n">Generic</span> <span class="n">Netlink</span> <span class="n">message</span> <span class="n">payload</span>            <span class="o">|</span>
 <span class="o">+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
 
 <span class="k">struct</span> <span class="n">genlmsghdr</span> <span class="p">{</span>
    <span class="n">__u8</span>    <span class="n">cmd</span><span class="p">;</span>
    <span class="n">__u8</span>    <span class="n">version</span><span class="p">;</span>
    <span class="n">__u16</span>   <span class="n">reserved</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">//genl的message payload基于netlink的属性机制，即payload是由一个个nlattr组成</span>
<span class="cm">/*
 *  &lt;------- NLA_HDRLEN ------&gt; &lt;-- NLA_ALIGN(payload)--&gt;
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 * |        Header       | Pad |     Payload       | Pad |
 * |   (struct nlattr)   | ing |                   | ing |
 * +---------------------+- - -+- - - - - - - - - -+- - -+
 *  &lt;-------------- nlattr-&gt;nla_len --------------&gt;
 */</span>
<span class="k">struct</span> <span class="n">nlattr</span> <span class="p">{</span>
    <span class="n">__u16</span>           <span class="n">nla_len</span><span class="p">;</span>
    <span class="n">__u16</span>           <span class="n">nla_type</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<h3 id="genl内核侧示例">genl内核侧示例</h3>

<p>参考：
https://wiki.linuxfoundation.org/networking/generic_netlink_howto</p>

<p>注意：genl_register_ops接口只在3.12及之前版本有；
3.13~4.9版本用genl_register_family_with_ops；
4.10版本及以后没有注册ops的接口，只有注册family的接口，ops要直接定义在family内。</p>

<p>本文示例基于4.15内核。</p>

<p>注册generic netlink family需要3步:</p>
<ol>
  <li>定义操作</li>
  <li>定义family</li>
  <li>注册family</li>
</ol>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Step1: 定义操作 */</span>
<span class="cm">/* attributes */</span>
<span class="k">enum</span> <span class="p">{</span>
    <span class="n">EXMPL_A_UNSPEC</span><span class="p">,</span>
    <span class="n">EXMPL_A_MSG</span><span class="p">,</span>
    <span class="n">_EXMPL_A_MAX</span><span class="p">,</span>
<span class="p">};</span>
<span class="cp">#define EXMPL_A_MAX (_EXMPL_A_MAX - 1)
</span><span class="cm">/* attribute policy */</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">nla_policy</span> <span class="n">exmpl_genl_policy</span><span class="p">[</span><span class="n">EXMPL_A_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="n">EXMPL_A_MSG</span><span class="p">]</span> <span class="o">=</span> <span class="p">{.</span><span class="n">type</span> <span class="o">=</span> <span class="n">NLA_NUL_STRING</span><span class="p">},</span>
<span class="p">};</span>
<span class="c1">// handler</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">exmpl_echo</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">genl_info</span> <span class="o">*</span><span class="n">info</span><span class="p">);</span>
<span class="c1">// commands</span>
<span class="k">enum</span> <span class="p">{</span>
    <span class="n">EXMPL_C_UNSPEC</span><span class="p">,</span>
    <span class="n">EXMPL_C_ECHO</span><span class="p">,</span>
    <span class="n">_EXMPL_C_MAX</span><span class="p">,</span>
<span class="p">};</span>
<span class="cp">#define EXMPL_C_MAX (_EXMPL_C_MAX - 1)
</span><span class="c1">// operation definition</span>
<span class="k">struct</span> <span class="n">genl_ops</span> <span class="n">exmpl_genl_ops</span><span class="p">[</span><span class="n">EXMPL_C_MAX</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">{</span>
        <span class="p">.</span><span class="n">cmd</span> <span class="o">=</span> <span class="n">EXMPL_C_ECHO</span><span class="p">,</span>
        <span class="p">.</span><span class="n">doit</span> <span class="o">=</span> <span class="n">exmpl_echo</span><span class="p">,</span>
        <span class="p">.</span><span class="n">policy</span> <span class="o">=</span> <span class="n">exmpl_genl_policy</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define FAMILY_NAME "my_genl"
</span><span class="cm">/* Step2: 定义family */</span>
<span class="c1">// family definition</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">genl_family</span> <span class="n">my_genl_family</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">.</span><span class="n">id</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
    <span class="p">.</span><span class="n">hdrsize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>  <span class="c1">//表示没有用户自定义的额外header</span>
    <span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">FAMILY_NAME</span><span class="p">,</span>
    <span class="p">.</span><span class="n">version</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
    <span class="p">.</span><span class="n">ops</span> <span class="o">=</span> <span class="n">exmpl_genl_ops</span><span class="p">,</span>
    <span class="p">.</span><span class="n">n_ops</span> <span class="o">=</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">exmpl_genl_ops</span><span class="p">),</span>
    <span class="p">.</span><span class="n">maxattr</span> <span class="o">=</span> <span class="n">EXMPL_A_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
<span class="p">};</span>

<span class="c1">// handler的具体定义</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">exmpl_echo</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">genl_info</span> <span class="o">*</span><span class="n">info</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">nlattr</span> <span class="o">*</span><span class="n">na</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">reply_skb</span><span class="p">;</span>
    <span class="kt">void</span> <span class="o">*</span><span class="n">msg_head</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>

    <span class="n">printk</span><span class="p">(</span><span class="s">"%s in.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">__func__</span><span class="p">);</span>

    <span class="c1">//内核已经解析好了每个attr</span>
    <span class="n">na</span> <span class="o">=</span> <span class="n">info</span><span class="o">-&gt;</span><span class="n">attrs</span><span class="p">[</span><span class="n">EXMPL_A_MSG</span><span class="p">];</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">na</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="s">"Error: attr EXMPL_A_MSG is null</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">printk</span><span class="p">(</span><span class="s">"Recv message: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">nla_data</span><span class="p">(</span><span class="n">na</span><span class="p">));</span>

    <span class="c1">//将收到的消息发回去</span>
    <span class="n">reply_skb</span> <span class="o">=</span> <span class="n">genlmsg_new</span><span class="p">(</span><span class="n">NLMSG_GOODSIZE</span><span class="p">,</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
    <span class="c1">//填写genl消息头</span>
    <span class="n">msg_head</span> <span class="o">=</span> <span class="n">genlmsg_put</span><span class="p">(</span><span class="n">reply_skb</span><span class="p">,</span> <span class="n">info</span><span class="o">-&gt;</span><span class="n">snd_portid</span><span class="p">,</span> <span class="n">info</span><span class="o">-&gt;</span><span class="n">snd_seq</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">my_genl_family</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">EXMPL_C_ECHO</span><span class="p">);</span>
    <span class="c1">//向skb尾部填写attr</span>
    <span class="n">nla_put_string</span><span class="p">(</span><span class="n">reply_skb</span><span class="p">,</span> <span class="n">EXMPL_A_MSG</span><span class="p">,</span> <span class="n">nla_data</span><span class="p">(</span><span class="n">na</span><span class="p">));</span>
    <span class="c1">//Finalize the message: 更新nlmsghdr中的nlmsg_len字段</span>
    <span class="n">genlmsg_end</span><span class="p">(</span><span class="n">reply_skb</span><span class="p">,</span> <span class="n">msg_head</span><span class="p">);</span>
    <span class="c1">//Send the message back</span>
    <span class="n">ret</span> <span class="o">=</span> <span class="n">genlmsg_reply</span><span class="p">(</span><span class="n">reply_skb</span><span class="p">,</span> <span class="n">info</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="s">"genlmsg_reply return fail: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
        <span class="k">return</span> <span class="o">-</span><span class="n">ret</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Step3: 注册famliy */</span>
<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">genl_register_family</span><span class="p">(</span><span class="o">&amp;</span><span class="n">my_genl_family</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">printk</span><span class="p">(</span><span class="s">"genl_register_family fail, ret:%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

</code></pre></div></div>

<h3 id="genl用户侧示例基于libnl">genl用户侧示例(基于libnl)</h3>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MY_FAMILY_NAME "my_genl"
</span>
<span class="c1">//用户侧需要定义和内核侧相同的属性以及命令，所以通常把这一部分摘成一个独立的.h，内核和app共用</span>
<span class="c1">//这里没有摘成一个独立的.h，用户侧也重复定义一份</span>
<span class="cm">/* attributes */</span>
<span class="k">enum</span> <span class="p">{</span>
    <span class="n">EXMPL_A_UNSPEC</span><span class="p">,</span>
    <span class="n">EXMPL_A_MSG</span><span class="p">,</span>
    <span class="n">_EXMPL_A_MAX</span><span class="p">,</span>
<span class="p">};</span>
<span class="cp">#define EXMPL_A_MAX (_EXMPL_A_MAX - 1)
</span><span class="c1">// define attribute policy</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">nla_policy</span> <span class="n">exmpl_genl_policy</span><span class="p">[</span><span class="n">EXMPL_A_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">[</span><span class="n">EXMPL_A_MSG</span><span class="p">]</span> <span class="o">=</span> <span class="p">{.</span><span class="n">type</span> <span class="o">=</span> <span class="n">NLA_STRING</span><span class="p">},</span>
<span class="p">};</span>
<span class="c1">// commands</span>
<span class="k">enum</span> <span class="p">{</span>
    <span class="n">EXMPL_C_UNSPEC</span><span class="p">,</span>
    <span class="n">EXMPL_C_ECHO</span><span class="p">,</span>
    <span class="n">_EXMPL_C_MAX</span><span class="p">,</span>
<span class="p">};</span>
<span class="cp">#define EXMPL_C_MAX (_EXMPL_C_MAX - 1)
</span>
<span class="c1">//接收回调定义</span>
<span class="kt">int</span> <span class="nf">recv_callback</span><span class="p">(</span><span class="k">struct</span> <span class="n">nl_msg</span><span class="o">*</span> <span class="n">recv_msg</span><span class="p">,</span> <span class="kt">void</span><span class="o">*</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">struct</span> <span class="n">nlmsghdr</span> <span class="o">*</span><span class="n">nlh</span> <span class="o">=</span> <span class="n">nlmsg_hdr</span><span class="p">(</span><span class="n">recv_msg</span><span class="p">);</span>
    <span class="k">struct</span> <span class="n">nlattr</span> <span class="o">*</span><span class="n">tb_msg</span><span class="p">[</span><span class="n">EXMPL_A_MAX</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">nlh</span><span class="o">-&gt;</span><span class="n">nlmsg_type</span> <span class="o">==</span> <span class="n">NLMSG_ERROR</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Received NLMSG_ERROR message!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">NL_STOP</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">struct</span> <span class="n">genlmsghdr</span> <span class="o">*</span><span class="n">gnlh</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">genlmsghdr</span><span class="o">*</span><span class="p">)</span><span class="n">nlmsg_data</span><span class="p">(</span><span class="n">nlh</span><span class="p">);</span>
    <span class="c1">//按照每attr解析内核发来的genl消息</span>
    <span class="n">nla_parse</span><span class="p">(</span><span class="n">tb_msg</span><span class="p">,</span> <span class="n">EXMPL_A_MAX</span><span class="p">,</span>
              <span class="n">genlmsg_attrdata</span><span class="p">(</span><span class="n">gnlh</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
              <span class="n">genlmsg_attrlen</span><span class="p">(</span><span class="n">gnlh</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
              <span class="n">exmpl_genl_policy</span><span class="p">);</span>

    <span class="c1">//判断是否包含属性EXMPL_A_MSG</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">tb_msg</span><span class="p">[</span><span class="n">EXMPL_A_MSG</span><span class="p">])</span> <span class="p">{</span>
        <span class="c1">// parse it as string</span>
        <span class="kt">char</span> <span class="o">*</span> <span class="n">payload_msg</span> <span class="o">=</span> <span class="n">nla_get_string</span><span class="p">(</span><span class="n">tb_msg</span><span class="p">[</span><span class="n">EXMPL_A_MSG</span><span class="p">]);</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Kernel replied: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">payload_msg</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Attribute EXMPL_A_MSG is missing</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="n">NL_OK</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
    <span class="c1">//创建并连接genl socket</span>
    <span class="k">struct</span> <span class="n">nl_sock</span> <span class="o">*</span><span class="n">sk</span> <span class="o">=</span> <span class="n">nl_socket_alloc</span><span class="p">();</span>
    <span class="n">genl_connect</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
    <span class="c1">//根据FAMILY_NAME获得对应的famlily_id</span>
    <span class="kt">int</span> <span class="n">family_id</span><span class="p">;</span>
    <span class="n">family_id</span> <span class="o">=</span> <span class="n">genl_ctrl_resolve</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">FAMILY_NAME</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">family_id</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"generic netlink family '"</span> <span class="n">FAMILY_NAME</span> <span class="s">"' NOT REGISTERED</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">nl_socket_free</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"Family-ID of generic netlink family '"</span> <span class="n">FAMILY_NAME</span> <span class="s">"' is: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">family_id</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">//设置接收回调 </span>
    <span class="n">nl_socket_modify_cb</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">NL_CB_MSG_IN</span><span class="p">,</span> <span class="n">NL_CB_CUSTOM</span><span class="p">,</span> <span class="n">recv_callback</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

    <span class="c1">//发送消息</span>
    <span class="k">struct</span> <span class="n">nl_msg</span> <span class="o">*</span><span class="n">msg</span> <span class="o">=</span> <span class="n">nlmsg_alloc</span><span class="p">();</span>
    <span class="n">genlmsg_put</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">NL_AUTO_PORT</span><span class="p">,</span> <span class="n">NL_AUTO_SEQ</span><span class="p">,</span> <span class="n">family_id</span><span class="p">,</span>
                <span class="mi">0</span><span class="p">,</span> <span class="n">NLM_F_REQUEST</span><span class="p">,</span> <span class="n">EXMPL_C_ECHO</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
    <span class="n">NLA_PUT_STRING</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">EXMPL_A_MSG</span><span class="p">,</span> <span class="s">"genl message from user to kernel"</span><span class="p">);</span>
    <span class="kt">int</span> <span class="n">res</span> <span class="o">=</span> <span class="n">nl_send_auto</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">msg</span><span class="p">);</span>
    <span class="n">nlmsg_free</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">res</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"nl_send_auto fail, ret:%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">printf</span><span class="p">(</span><span class="s">"nl_send_auto OK, ret: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">//接收消息。接收到内核发来的消息后，触发回调recv_callback</span>
    <span class="n">nl_recvmsgs_default</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

<span class="nl">nla_put_failure:</span> <span class="c1">//referenced by NLA_PUT_STRING </span>
    <span class="n">nl_socket_free</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>这里的示例是内核收到用户空间发来的genl消息后，根据发送端的struct genl_info *info，
调用genlmsg_reply(reply_skb, info)，将内核的genl消息单播给用户空间app。</p>

<p>如果内核不知道用户空间的socket信息，内核如何将消息发送到用户空间呢？
这时一般用组播netlink消息，即内核将消息组播出去。用户空间谁订阅了这个组播，谁就能收到内核发来的消息。
关于组播netlink示例，后续有空再补一下。。。</p>

<hr />

<p>完整示例见github: <a href="https://github.com/jian-soft/netlink_examples">https://github.com/jian-soft/netlink_examples</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[熟悉Linux wifi的同学都知道，wpa_supplicant程序是基于netlink与wifi驱动进行通信的。 (wpa_supplicant是wifi station用户空间守护进程)]]></summary></entry><entry><title type="html">TensorFlow Lite图像分类实战</title><link href="https://jiansoft.net/2022/02/20/run_tensorflowlite_example.html" rel="alternate" type="text/html" title="TensorFlow Lite图像分类实战" /><published>2022-02-20T00:00:00+00:00</published><updated>2022-02-20T00:00:00+00:00</updated><id>https://jiansoft.net/2022/02/20/run_tensorflowlite_example</id><content type="html" xml:base="https://jiansoft.net/2022/02/20/run_tensorflowlite_example.html"><![CDATA[<p>TFL(TensorFlow Lite)是TensorFlow面向移动设备、嵌入式设备的执行机器学习的工具。
在嵌入式设备上调试TFL之前，我们先在x86环境将其跑起来。
本文记录作者在x86-64 Linux环境中如何运行TFL的图像分类示例。</p>

<p>运行环境：</p>
<ul>
  <li>CPU: 11th Gen Intel(R) Core(TM) i5-1135G7</li>
  <li>Window 10</li>
  <li>VMware Workstation 16 + Ubuntu 20.04</li>
  <li>Python3 3.8.10(Ubuntu 20.04自带)</li>
</ul>

<h2 id="第1步安装tensorflow-lite解释器">第1步：安装TensorFlow Lite解释器</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 -m pip install tflite-runtime
</code></pre></div></div>

<p>安装之前，先将pip的源改为国内。</p>

<ol>
  <li>创建~/.pip/pip.conf</li>
  <li>在其中添加或修改：</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[global]
index-url = https://mirrors.aliyun.com/pypi/simple/

[install]
trusted-host=mirrors.aliyun.com
</code></pre></div></div>

<h2 id="第2步下载tensorflow-lite的图像分类示例代码">第2步：下载TensorFlow Lite的图像分类示例代码</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/tensorflow/examples --depth 1
cd examples/lite/examples/image_classification/raspberry_pi
</code></pre></div></div>

<p>参照此路径下的README，运行sh setup.sh。</p>

<p>setup.sh中有一步是下载模型文件efficientnet_lite0.tflite，如果下载失败，
可以手动浏览器打开链接https://tfhub.dev/tensorflow/lite-model/efficientnet/lite0/uint8/2?lite-format=tflite下载。
手动下载下来的文件名为lite-model_efficientnet_lite0_uint8_2.tflite，需要将其重命名为efficientnet_lite0.tflite(image_classifier_test.py使用此文件)。</p>

<p>之后运行demo中自带的测试用例<code class="language-plaintext highlighter-rouge">python3 image_classifier_test.py</code>，可以看到运行正常，输出结果如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.....
----------------------------------------------------------------------
Ran 5 tests in 4.385s

OK
</code></pre></div></div>

<h3 id="第3步写一个自己的测试代码">第3步：写一个自己的测试代码</h3>

<p>参照image_classifier_test.py，自己写一个简单的测试代码。
测试代码的功能是对test_data/fox.jpeg文件执行图像分类运算，并输出结果。</p>

<p>my_test.py内容如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import sys
import time

import cv2
from image_classifier import Category
from image_classifier import ImageClassifier
from image_classifier import ImageClassifierOptions

_IMAGE_FILE = 'test_data/fox.jpeg'  #测试文件
_MODEL_FILE = 'efficientnet_lite0.tflite'  #模型文件

image = cv2.imread(_IMAGE_FILE)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
classifier = ImageClassifier(_MODEL_FILE)  #加载模型

stime = time.time()
categories = classifier.classify(image)  #运行图像分类模型
endtime = time.time()
print(endtime - stime)  #打印模型执行时间
 
print(categories)  #打印图像分类结果
</code></pre></div></div>

<p>执行：<code class="language-plaintext highlighter-rouge">python3 my_test.py</code></p>

<p>执行结果如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0.03605842590332031
[Category(label='red fox', score=0.77734375), Category(label='kit fox', score=0.10546875), Category(label='grey fox', score=0.046875)]
</code></pre></div></div>

<p>即在我的PC上，对test/fox.jpeg文件运行一次efficientnet_lite0.tflite模型，耗时36ms。
识别test/fox.jpeg图片是”red fox”的概率为77.7%。</p>

<p>附：test/fox.jpeg
<img src="/assets/image/2022/02/fox.jpeg" alt="fox.jpeg" /></p>

<hr />
<p>参考链接：</p>
<ul>
  <li><a href="https://tensorflow.google.cn/lite/guide/python">https://tensorflow.google.cn/lite/guide/python</a> （注意语言选择英语，汉语的内容更新较慢）</li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[TFL(TensorFlow Lite)是TensorFlow面向移动设备、嵌入式设备的执行机器学习的工具。 在嵌入式设备上调试TFL之前，我们先在x86环境将其跑起来。 本文记录作者在x86-64 Linux环境中如何运行TFL的图像分类示例。]]></summary></entry><entry><title type="html">OpenWRT开发之创建软件包</title><link href="https://jiansoft.net/2022/01/05/openwrt_create_package.html" rel="alternate" type="text/html" title="OpenWRT开发之创建软件包" /><published>2022-01-05T00:00:00+00:00</published><updated>2022-01-05T00:00:00+00:00</updated><id>https://jiansoft.net/2022/01/05/openwrt_create_package</id><content type="html" xml:base="https://jiansoft.net/2022/01/05/openwrt_create_package.html"><![CDATA[<p>OpenWRT二次开发时总免不了开发自己的软件包。本文介绍如何在OpenWRT中创建一个新的软件包。</p>

<p>首先创建软件包所在的目录，在openwrt根目录中执行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p package/mypackages/helloworld
</code></pre></div></div>
<p>这里的mypackages目录和helloworld目录都是新建的，helloworld就是我们本次新建的软件包的包名。我们后续可以将自己创建的包都放在mypackages目录下。</p>

<p>helloworld包的目录结构如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>helloworld
├── Makefile  #openwrt’s package manifest file
└── src
    ├── helloworld.c  #helloworld source code
    └── Makefile  #helloworld’s makefile
</code></pre></div></div>

<h2 id="package-manifest-file">package manifest file</h2>

<p>即软件包helloworld目录下的Makefile文件。例子以及注释如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 导入通用编译规则
include $(TOPDIR)/rules.mk

# name和version用来定义编译目录名$(PKG_BUILD_DIR)]
PKG_NAME:=helloworld
PKG_VERSION:=1.0
PKG_RELEASE:=1
#PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)  # 也可以直接定义编译目录名，代替默认的目录名

# 导入包定义
include $(INCLUDE_DIR)/package.mk

# 包定义：定义我们的包在menuconfig中的位置
# Makefile中的define语法可以理解为函数，用于定义命令集合
define Package/helloworld
  SECTION:=examples
  CATEGORY:=Examples
  TITLE:=helloworld, learn from example.
endef

# 包描述：关于我们包的更详细的描述
define Package/helloworld/description
  A simple helloworld example, my first openwrt package example.
endef

# 编译准备. 必须使用tab缩进，表示是可执行的命令
define Build/Prepare
	echo "Here is Build/Prepare"
	mkdir -p $(PKG_BUILD_DIR)
	cp ./src/* $(PKG_BUILD_DIR)/
endef

# 安装
define Package/helloworld/install
	$(INSTALL_DIR) $(1)/usr/bin
	$(INSTALL_BIN) $(PKG_BUILD_DIR)/helloworld $(1)/usr/bin
endef

# 这一行总是在最后
$(eval $(call BuildPackage,helloworld))
</code></pre></div></div>
<p>上面的例子中没有定义<code class="language-plaintext highlighter-rouge">define Build/Compile</code>，表示使用默认的Compile命令。默认的Compile行为就是在$(PKG_BUILD_DIR)目录下执行make命令。</p>

<h2 id="helloworldc及其makefile">helloworld.c及其Makefile</h2>

<p>helloworld.c内容如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include&lt;stdio.h&gt;
int main(void)
{
    printf("Hello world!\n");
    printf("This is my first package!\n");
    return 0;
}
</code></pre></div></div>

<p>与helloworld.c同目录的Makefile内容如下：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>TARGET = helloworld
OBJS = helloworld.o

$(TARGET):$(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $&lt; -o $@

.PHONY: clean
clean:
	rm -f $(TARGET) $(OBJS)
</code></pre></div></div>
<p>说明：这里的$(CC)、$(CFLAGS)、$(LDFLAGS)都是由OpenWRT的build系统赋值的，CC就是目标平台对应的交叉编译工具链里的gcc。</p>

<h2 id="测试">测试</h2>

<p>在OpenWRT根目录下运行make menuconfig，可以看到多出来一个”Examples  —&gt;”菜单，按回车进去后可以看到我们新建的”helloworld” 包。
（从这里也可以看出，在执行make menuconfig时，OpenWRT会自动扫描package目录以及其子目录下所有的包。）</p>

<p>选中这个”helloworld”包。然后再OpenWRT根目录下执行：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make package/helloworld/compile V=s
</code></pre></div></div>
<p>此命令即为OpenWRT单package编译命令。</p>

<p>通过log，可以看到我们的包编译成功。编译目录为
build_dir/target-XXXX/helloworld-1.0</p>

<p>如果要再次编译，可以执行：</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make package/helloworld/{clean,compile} V=s
</code></pre></div></div>

<p>本文源码见：
<a href="https://github.com/jian-soft/openwrt-package-example">https://github.com/jian-soft/openwrt-package-example</a></p>

<p><br /></p>

<hr />
<p>参考文章：</p>
<ul>
  <li><a href="https://openwrt.org/docs/guide-developer/packages">https://openwrt.org/docs/guide-developer/packages</a></li>
  <li><a href="https://openwrt.org/zh-cn/doc/devel/packages">https://openwrt.org/zh-cn/doc/devel/packages</a></li>
  <li><a href="https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem">https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem</a></li>
  <li><a href="https://openwrt.org/docs/guide-developer/helloworld/start">https://openwrt.org/docs/guide-developer/helloworld/start</a></li>
  <li><a href="https://github.com/mwarning/openwrt-examples">https://github.com/mwarning/openwrt-examples</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[OpenWRT二次开发时总免不了开发自己的软件包。本文介绍如何在OpenWRT中创建一个新的软件包。]]></summary></entry><entry><title type="html">VIM配置</title><link href="https://jiansoft.net/2021/12/12/my_vimrc.html" rel="alternate" type="text/html" title="VIM配置" /><published>2021-12-12T00:00:00+00:00</published><updated>2021-12-12T00:00:00+00:00</updated><id>https://jiansoft.net/2021/12/12/my_vimrc</id><content type="html" xml:base="https://jiansoft.net/2021/12/12/my_vimrc.html"><![CDATA[<p>写一篇关于VIM配置的文章，记录下自己的VIM配置，力求简洁实用。</p>

<p>VIM的配置保存在文件~/.vimrc中(Windows下是C:\Users\yourname_vimrc)。VIM除了自身可配置项外，还可插件扩展。VIM的插件一般用vundle或vim-plug来管理，但我力求简单，不打算装太多插件，所以也不打算使用插件管理，直接手动安装插件。</p>

<p>文章分基础配置、插件和VIM高级用法三部分。同时适用Linux和Windows。</p>

<h2 id="基础配置">基础配置</h2>

<p>以下配置相当于VIM的必备配置，没有这些配置感觉VIM少了什么。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set encoding=utf-8  "使用utf8编码
let mapleader=";"  "定义快捷键的前缀，即&lt;Leader&gt;

filetype plugin indent on "打开文件类型检测
syntax on  "开启语法高亮，并替换默认配色方案
set nowrap  "禁止折行

set incsearch  "开启实时搜索
set hlsearch  "搜索结果高亮
set ignorecase  "大小写不敏感
set smartcase  "如果搜索的pattern中含有大写字母，则大小写敏感

set gcr=a:block-blinkon0  "禁止光标闪烁，windows gVim有效，ubuntu无效
set guioptions-=m  "禁止显示菜单和工具条，windows gVim有效
set guioptions-=T

""--辅助信息
set laststatus=2  "总是显示状态栏
set ruler  "显示光标当前位置
set number  "显示行号
set cursorline  "高亮显示当前行
set cursorcolumn  "高亮显示当前列
set wildmenu  "输入命令点击Tab键时，会展示所有候选命令

""--制表符
set expandtab  "将制表符扩展为空格
set tabstop=4  "编辑时制表符占用空格数
set shiftwidth=4
set softtabstop=4  "让VIM把连续4个空格视为一个制表符

""--窗口相关操作
nnoremap &lt;Leader&gt;nw &lt;C-W&gt;&lt;C-W&gt;  "遍历窗口
nnoremap &lt;Leader&gt;lw &lt;C-W&gt;l
nnoremap &lt;Leader&gt;hw &lt;C-W&gt;h
nnoremap &lt;Leader&gt;kw &lt;C-W&gt;k
nnoremap &lt;Leader&gt;jw &lt;C-W&gt;j
nnoremap &lt;Leader&gt;= &lt;C-W&gt;+  "增加窗口高度
nnoremap &lt;Leader&gt;- &lt;C-W&gt;-  "减小窗口高度
</code></pre></div></div>

<h2 id="插件">插件</h2>

<p>VIM有一套自己的脚本语言vimscript，通过这种脚本语言可以实现与vim交互，达到扩展功能的目的。
一组vimscript就是就是一个vim插件。~/.vim是存放插件的默认目录(Windows下是C:\Users\yourname\vimfiles，可以通过命令:set runtimepath?确认)。</p>

<h3 id="nerdcommenter">nerdcommenter</h3>

<ol>
  <li>功能： 快速注释</li>
  <li>地址： <a href="https://github.com/preservim/nerdcommenter">https://github.com/preservim/nerdcommenter</a></li>
  <li>安装方法： 直接将github代码仓里的plugin/nerdcommenter.vim和autoload/nerdcommenter.vim两个文件复制到~/.vim/plugin和~/.vim/autoload下即可</li>
  <li>用法：如下是插件自带的命令绑定</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;leader&gt;cc  "注释
&lt;leader&gt;cu  "取消注释
&lt;leader&gt;ca  "切换为第二种注释符, 比如C文件第一种注释符是/* */，第二种注释符是//
&lt;leader&gt;cs  "更性感的注释
&lt;leader&gt;cm  "紧凑型注释
</code></pre></div></div>

<h3 id="nerdtree">nerdtree</h3>

<ol>
  <li>功能： 文件浏览</li>
  <li>地址： <a href="https://github.com/preservim/nerdtree">https://github.com/preservim/nerdtree</a></li>
  <li>安装方法： 下载代码，将autoload lib/nerdtree nerdtree_plugin plugin syntax这几个目录复制到~/.vim文件夹下</li>
  <li>用法：需要在vimrc中增加如下配置</li>
</ol>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nnoremap &lt;leader&gt;fl :NERDTreeToggle&lt;CR&gt;  "打开/关闭文件浏览窗口，选中文件后回车打开
                                         "在nerdtree窗口中输入cd命令，则切换VIM的CWD到选中的目录
</code></pre></div></div>

<h2 id="vim高级用法">VIM高级用法</h2>

<h3 id="多文件搜索vimgrep">多文件搜索vimgrep</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:vim[grep] {pattern} {file}  "其中file部分，*表示当前目录，**表示当前目录和子目录
:cw or :copen  "打开Quickfix列表，vimgrep的搜索结果在Quickfix中
:cn or :cnext "下一个结果
:cp or :cprevious "上一个结果
:vim // {file}  "如果模式为空，则使用最近一次的查找模式
:colder  "最后10个搜索结果会被记录，此命令跳到上一个搜索记录
:cnewer
</code></pre></div></div>

<p>快捷键绑定</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nnoremap &lt;Leader&gt;sw :copen 15&lt;CR&gt;  "打开quickfix窗口，高度15行(默认是10行)
nnoremap &lt;Leader&gt;sc :cclose&lt;CR&gt;  "关闭quickfix窗口
nnoremap &lt;Leader&gt;sn :cnext&lt;CR&gt;  "quickfix下一个结果
nnoremap &lt;Leader&gt;sr :cprevious&lt;CR&gt;  "quickfix上一个结果
nnoremap &lt;Leader&gt;ss :vim //j **&lt;CR&gt;  "以当前匹配模式搜索
nnoremap &lt;Leader&gt;so :colder&lt;CR&gt;  "旧一次的搜索结果
nnoremap &lt;Leader&gt;se :cnewer&lt;CR&gt;  "新一次的搜索结果
</code></pre></div></div>

<h3 id="替换命令">替换命令</h3>

<ul>
  <li>常规替换命令：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:[range]s[ubstitute]/{pattern}/{string}/[flags]
" range: %表示全文
" flags: c表示要确认
</code></pre></div></div>
<ul>
  <li>多文件替换：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:args **/*.h  "递归当前目录下所有.h文件，生成文件列表
:args  "查看当前的args参数
:argdo %s//newword/gc | update  "对args里的每个文件，以当前匹配模式，执行替换命令
</code></pre></div></div>
<ul>
  <li>快捷键绑定</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>""--快速替换：i表示input，s//表示以当前匹配模式搜索，输入要替换的单词，然后输入/gc&lt;CR&gt;
nnoremap &lt;Leader&gt;si :% s//
</code></pre></div></div>

<h3 id="多buffer操作">多buffer操作</h3>

<p>当一个文件加载到内存时，VIM为其创建一个buffer</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:ls  "列出所有的buffer
:b 5  "跳到编号为5的buffer
:b name  "跳到文件名为name的buffer，支持部分名字和tab补全
&lt;C-6&gt;  "跳到上一个buffer
:bw  "关闭当前buffer
</code></pre></div></div>

<h3 id="自动补全">自动补全</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ctrl-n, ctrl-p  "弹出补全窗口
ctrl-e  "取消补全窗口
</code></pre></div></div>

<h2 id="汇总后的vimrc文件">汇总后的.vimrc文件</h2>

<p>见 <a href="https://github.com/jian-soft/vimrc">https://github.com/jian-soft/vimrc</a></p>

<hr />
<p>参考文章：
<a href="https://github.com/yangyangwithgnu/use_vim_as_ide">https://github.com/yangyangwithgnu/use_vim_as_ide</a></p>]]></content><author><name></name></author><summary type="html"><![CDATA[写一篇关于VIM配置的文章，记录下自己的VIM配置，力求简洁实用。]]></summary></entry></feed>