<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Michael</title>
    <description>The latest articles on Forem by Michael (@shouhua_57).</description>
    <link>https://forem.com/shouhua_57</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1513924%2Fba4fc722-0079-487d-847d-6661b70774ba.jpeg</url>
      <title>Forem: Michael</title>
      <link>https://forem.com/shouhua_57</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/shouhua_57"/>
    <language>en</language>
    <item>
      <title>排序(collation)探究</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Mon, 03 Jun 2024 08:14:31 +0000</pubDate>
      <link>https://forem.com/shouhua_57/pai-xu-collationtan-jiu-4bcc</link>
      <guid>https://forem.com/shouhua_57/pai-xu-collationtan-jiu-4bcc</guid>
      <description>&lt;p&gt;应用程序中到处可见排序，特别是需要国际化的时候，排序更是多种多样。面对这些排序的时候，刚开始的本能就是应该是这样的，比如2排在123后面，但是仔细琢磨下，好像也可以排在前面，因为2小于123，还有各种本地语言更是充满特色。本文主要探究下各种语言对排序支持情况, 如何支持的以及底层库如何运作的。&lt;/p&gt;

&lt;h2&gt;
  
  
  现状
&lt;/h2&gt;

&lt;p&gt;大部分时候终端数据拿到的是有序数据，如果需要排序，比如表单中表单头字段点击下，可以切换排序方式，发送请求给后端，后端一般在数据库中排序，然后拿到区间数据给终端。有时候数据比较少，或者数据简单，希望直接在终端操作。&lt;br&gt;&lt;br&gt;
根据以上的描述，数据经过的每一个端都有可能排序，那每一端使用的底层排序库不一样，排序结果也有可能大相径庭。&lt;/p&gt;
&lt;h2&gt;
  
  
  Linux系统排序
&lt;/h2&gt;

&lt;p&gt;系统排序使用 &lt;code&gt;glibc&lt;/code&gt; 提供的排序功能。每个linux都需要设置&lt;a href="https://wiki.archlinuxcn.org/wiki/Locale"&gt;locale&lt;/a&gt;，其中就包括排序相关的设置。&lt;code&gt;glibc&lt;/code&gt; 和应用程序、函数库使用区域设置显示本地化的文字、货币、时间、日期、特殊字符等包含地域属性的内容。其中的 &lt;code&gt;LC_COLLATE&lt;/code&gt; 就是用于系统排序设置参数，不同的值排序结果导致不同排序，比如如果使用原生的&lt;code&gt;"C"&lt;/code&gt;排序时，字符&lt;code&gt;'A'&lt;/code&gt;在&lt;code&gt;'a'&lt;/code&gt;前面；而使用&lt;code&gt;"en_US.UTF-8"&lt;/code&gt;时，顺序相反。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;man 5 locale
man 7 locale

&lt;span class="nv"&gt;LC_COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"en_US.UTF-8"&lt;/span&gt; &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;$'a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;一&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;测&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="c"&gt;# 123\n2\na\nA\n一\n测\n&lt;/span&gt;

&lt;span class="nv"&gt;LC_COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"C"&lt;/span&gt; sort &lt;span class="o"&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;$'a&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;一&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;测&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;123&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;2&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="c"&gt;# 123\n2\nA\na\n一\n测\n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;默认情况下是&lt;code&gt;"C"&lt;/code&gt;，可以看下&lt;code&gt;/usr/lib/locale/C.utf8/LC_COLLATE&lt;/code&gt;，提供了基础的ASCII码字符的顺序。其他的可以查看&lt;code&gt;/usr/share/locale/&lt;/code&gt;文件夹，但是没有&lt;code&gt;LC_COLLATE&lt;/code&gt;文件，猜测直接在&lt;code&gt;glibc&lt;/code&gt;中处理。&lt;code&gt;glibc&lt;/code&gt;的版本的排序方式一般是很稳定的，但是也有&lt;a href="https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html"&gt;例外&lt;/a&gt;。&lt;/p&gt;

&lt;h2&gt;
  
  
  数据库支持情况
&lt;/h2&gt;

&lt;p&gt;关系型数据库一般使用Postgresql和Mysql，在各自的文档中描述的非常清晰。&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgresql
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/collation.html"&gt;Postgresql collation&lt;/a&gt;主要用于排序。Postgresql排序目前使用两种方式，libc(表示系统自带的库，比如glibc)和ICU，前者使用数据库编译时操作系统支持的collation，后者是引入第三方库icu，扩展性更高，&lt;strong&gt;官方docker镜像编译时默认带入了icu&lt;/strong&gt;。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 自定义一个collation，使用icu提供的'zh-u-kn-kf-upper-kr-digit-latn'规则排序，kf表示大小写字母排序自定义，kr表示reorder各种字符顺序，总体表示使用zh的locale，大写字母在前，整体顺序是数字，拉丁字符，中文拼音&lt;/span&gt;
create collation zh_num_upper &lt;span class="o"&gt;(&lt;/span&gt;provider &lt;span class="o"&gt;=&lt;/span&gt; icu, locale &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'zh-u-kn-kf-upper-kr-digit-latn'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;select &lt;/span&gt;name from &lt;span class="o"&gt;(&lt;/span&gt;values &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'123'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'一'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'测'&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; _&lt;span class="o"&gt;(&lt;/span&gt;name&lt;span class="o"&gt;)&lt;/span&gt; order by name COLLATE &lt;span class="s2"&gt;"zh-u-kn-kf-upper-kr-digit-latn"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mariadb
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 默认支持的collation&lt;/span&gt;
show character &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# 默认支持的字符集，包括字符集默认的collation，gbk默认的是gbk_chinese_ci, utf8mb4默认的collation是utf8mb4_general_ci&lt;/span&gt;
create table &lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;name character &lt;span class="nb"&gt;set &lt;/span&gt;utf8mb4 collation utf8mb4_unicode_ci not null&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
insert into &lt;span class="nb"&gt;test &lt;/span&gt;values &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'一'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'变'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'导'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'测'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'用'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; from &lt;span class="nb"&gt;test &lt;/span&gt;order by name CONVERT&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;**&lt;/span&gt;name using gbk&lt;span class="k"&gt;**&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  总结
&lt;/h3&gt;

&lt;p&gt;相比较而言，由于Postgresql引入了第三方库ICU，排序功能空前多，mysql系列显得很轻量。&lt;/p&gt;

&lt;h2&gt;
  
  
  语言支持
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Java
&lt;/h3&gt;

&lt;p&gt;引入&lt;code&gt;ICU&lt;/code&gt;库&lt;code&gt;maven&lt;/code&gt;配置&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.ibm.icu&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;icu4j&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;LATEST&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.ibm.icu&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;icu4j-charset&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;LATEST&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.ibm.icu&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;icu4j-localespi&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;LATEST&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java默认版本和ICU库排序比较&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.ibm.icu.text.Collator&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.ibm.icu.util.ULocale&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.ArrayList&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.List&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.Locale&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Demo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Collator&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Collator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CHINA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt;

        &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"a"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"一"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"测"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"icu中Locale.CHINA结果: %s\n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Collator&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Collator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CHINA&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"java原生Locale.CHINA结果: %s\n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Collator&lt;/span&gt; &lt;span class="n"&gt;cus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Collator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ULocale&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"zh-u-kr-Latn-Hani-digit"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cus&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"icu中custom locale结果: %s\n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCollationKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"一"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;toByteArray&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%x "&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// icu中Locale.CHINA结果: [123, 测, 一, a, A]&lt;/span&gt;
&lt;span class="c1"&gt;// java原生Locale.CHINA结果: [123, a, A, 测, 一]&lt;/span&gt;
&lt;span class="c1"&gt;// icu中custom locale结果: [123, a, A, 测, 一]&lt;/span&gt;
&lt;span class="c1"&gt;// 97 39 1 5 1 5 0 &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Javascript
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator"&gt;Javascript文档&lt;/a&gt;讲的很清晰。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;一&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;A&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;测&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;].sort(new Intl.Collator(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;zh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;).compare)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator"&gt;初始化文档&lt;/a&gt;说明很清晰，locales后面后说明，根据&lt;a href="https://en.wikipedia.org/wiki/IETF_language_tag"&gt;BCP 47&lt;/a&gt;文档来定义的，可惜现在还不支持reorder(kr)，options可配置项也很丰富。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Collator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  排序国际化
&lt;/h2&gt;

&lt;p&gt;综上所描述的各个层面的排序支持和结果情况，结果可以说极其不统一。排序是国际化概念中的一个分支，使用统一的排序算法支持的（&lt;a href="https://unicode.org/reports/tr10/"&gt;https://unicode.org/reports/tr10/&lt;/a&gt;），并且基于此有有一个ICU库实现（&lt;a href="https://unicode-org.github.io/icu/userguide/collation/"&gt;https://unicode-org.github.io/icu/userguide/collation/&lt;/a&gt;），为什么推荐使用ICU统一实现各个层的排序呢？&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;清晰的规则，icu基于标准，规则可解释，也方便定制&lt;/li&gt;
&lt;li&gt;一致的规则，比如glibc各个版本的排序规则就不统一，如果postgres使用libc规则，不同的glibc版本可能出现排序不一致的情况&lt;/li&gt;
&lt;li&gt;Postgres使用icu，业务代码中可以使用icu，保证了各个微服务模块统一的排序，不然各个微服务使用自己理解的排序，导致终端服务排序不一致（Mysql目前不支持ICU collation）&lt;/li&gt;
&lt;li&gt;易于扩展，比如如果按照中文偏旁或者bpmf排序怎么办，而且这些规则都是正常的规则；比如出海（国际化），不仅排序，currency，datetime等规则也有这个需要&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  排序规则拓展
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Language Tags
&lt;/h3&gt;

&lt;p&gt;ICU库里面使用&lt;code&gt;Locale&lt;/code&gt;初始化&lt;code&gt;Collator&lt;/code&gt;，&lt;code&gt;ICU&lt;/code&gt;中&lt;code&gt;Locale&lt;/code&gt;概念与&lt;code&gt;POSIX&lt;/code&gt;定义的&lt;code&gt;Locale&lt;/code&gt;是不一样的，&lt;code&gt;POSIX&lt;/code&gt;定义的是[language][_TERRITORY][.CODESET][@modifier]，ICU中的没有codeset相关的设置。&lt;a href="https://peter.eisentraut.org/blog/2023/05/16/overview-of-icu-collation-settings"&gt;现在icu中使用language tag标识locale&lt;/a&gt;，而且她具有可扩展性，包括各种&lt;code&gt;tailoring&lt;/code&gt;设置等。在HTML的lang设置或者http中的&lt;code&gt;Accept-Language&lt;/code&gt;等都使用标准的&lt;code&gt;BCP 47&lt;/code&gt;定义的&lt;code&gt;language tag&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;网上的&lt;code&gt;BCP 47&lt;/code&gt;的定义的RFC文档比较细节，建议看其他入门后可以再看，这里主要推荐先看&lt;a href="https://www.w3.org/International/articles/language-tags/"&gt;https://www.w3.org/International/articles/language-tags/&lt;/a&gt;，主要描述性讲述&lt;code&gt;language tags&lt;/code&gt;（结合&lt;a href="https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry"&gt;https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;格式：language[-extlang][-script][-region][-variant][-extension][-privateuse]&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;language，也叫 the primary language subtag，比如en表示English，ISO 639描述了language code，可以在上面的language subtag registry中查看类别为language的item，使用2个或者3个小写字符表示，比如&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%%
Type: language
Subtag: es
Description: Spanish
Description: Castilian
Added: 2005-10-16
Suppress-Script: Latn
%%
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;extlang，也叫 the extended language subtag，比如zh-yue表示粤语，使用3个小写字符表示。一般情况下，如果使用单个language可以表示的尽量不适用，比如yue可以单独表示language，替换zh-yue&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;

&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%%
Type: language
Subtag: yue
Description: Yue Chinese
Description: Cantonese
Added: 2009-07-29
Macrolanguage: zh
%%
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;script，也叫the script subtag，比如zh-Hans中的Hans表示简体中文，表示书写方式，后面的region表示表达方式，比如HK等，同样在IANA中也有相关描述。四个字符表示，首字符大写，比如Hans，Hant，Latn&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;region，也叫the region subtag，比如zh-Hans-HK，表示Traditional Chinese as used in Hong Kong。使用2个大写字符或者3位数字表示&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;variant，这个没用过，Variant subtags are values used to indicate dialects or script variations not already covered by combinations of language, script and region subtag&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;extension和privateuse，extension以一个注册的字符开头，比如u表示unicode相关设置，这个在icu中用到很多，比如zh-u-kn-kf-upper-kr-digit-latn，zh是language，u开始时unicode的extension；privateuse是自定义使用，通常以x开始，意义自己解释&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ICU中language tag扩展UNICODE
&lt;/h3&gt;

&lt;p&gt;很多引用&lt;code&gt;ICU&lt;/code&gt;的库，比如&lt;code&gt;Chromium&lt;/code&gt;就是使用类似的定义，&lt;code&gt;Javascript&lt;/code&gt;中的&lt;code&gt;Intl.Locale&lt;/code&gt;初始化参数就是类似，还有&lt;code&gt;Postgresql&lt;/code&gt;中定义&lt;code&gt;collation&lt;/code&gt;的&lt;code&gt;locale&lt;/code&gt;参数也是如此。&lt;/p&gt;

&lt;p&gt;扩展注册见&lt;a href="https://www.iana.org/assignments/language-tag-extensions-registry/language-tag-extensions-registry"&gt;https://www.iana.org/assignments/language-tag-extensions-registry/language-tag-extensions-registry&lt;/a&gt;，扩展的各个&lt;code&gt;item&lt;/code&gt;见&lt;a href="https://unicode.org/reports/tr35/tr35-collation.html#Collation_Tailorings"&gt;https://unicode.org/reports/tr35/tr35-collation.html#Collation_Tailorings&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这些扩展都有对应的&lt;code&gt;rule&lt;/code&gt;，比如&lt;code&gt;kf&lt;/code&gt;用于设置是否大小写顺序，&lt;code&gt;en-u-kf-upper&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;kf&lt;/th&gt;
&lt;th&gt;upper&lt;/th&gt;
&lt;th&gt;&lt;code&gt;[caseFirst upper]&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;上表中的[caseFirst upper]可以写入&lt;code&gt;rule table&lt;/code&gt;文件中，这个文件用于判断各个字符的顺序，可以通过&lt;code&gt;Python&lt;/code&gt;里面的语句获取(需要安装&lt;code&gt;pyicu&lt;/code&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;icu&lt;/span&gt;
&lt;span class="n"&gt;zh_collator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zh-CN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;zh_collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zh_collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getSortKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zh-CN&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;getRules&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rules&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[caseFirst upper]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rules&lt;/span&gt;
&lt;span class="n"&gt;zh_rule_coll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RuleBasedCollator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zh_rule_coll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getSortKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;a href="https://unicode.org/reports/tr10/"&gt;Unicode Collation Algorithm（UCA）&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Unicode&lt;/code&gt;提供默认的顺序给&lt;code&gt;CLDR&lt;/code&gt;的&lt;code&gt;root collation&lt;/code&gt;，用于各种语言，&lt;code&gt;locale&lt;/code&gt;或者其他配置&lt;code&gt;tailor&lt;/code&gt;，&lt;a href="https://unicode.org/reports/tr10/#Order_DUCET"&gt;https://unicode.org/reports/tr10/#Order_DUCET&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Unicode&lt;/code&gt;字符比较受到多个方面的影响，比如语言(同样两个字符，不同国家排序不一样)，使用场景(德国电话本和字典里面的字符顺序可能不一样)，自定义(大小写顺序)等，为了描述这些复杂性，使用多层结构描述，见下表&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;L1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Base characters&lt;/td&gt;
&lt;td&gt;role &amp;lt; roles &amp;lt; rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;L2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accents&lt;/td&gt;
&lt;td&gt;role &amp;lt; rôle &amp;lt; roles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;L3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Case/Variants&lt;/td&gt;
&lt;td&gt;role &amp;lt; Role &amp;lt; rôle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;L4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Punctuation&lt;/td&gt;
&lt;td&gt;role &amp;lt; “role” &amp;lt; Role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ln&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Identical&lt;/td&gt;
&lt;td&gt;role &amp;lt; ro□le &amp;lt; “role”&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;level&lt;/code&gt;与&lt;code&gt;weight&lt;/code&gt;通用，这些&lt;code&gt;level&lt;/code&gt;在配置中使用&lt;code&gt;strength&lt;/code&gt;表示，比如&lt;code&gt;setStrength&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;以下这段解释了多个名词，DUCET，CLDR（Unicode Common Locale Data Repository，UCA的各种扩展和补充），tailor，Collation Element转化为Sort key便于比较&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The Unicode Collation Algorithm (UCA) details how to compare two Unicode strings while
remaining conformant to the requirements of the Unicode Standard. This standard includes the
Default Unicode Collation Element Table (DUCET), which is data specifying the default collation
order for all Unicode characters, and the CLDR root collation element table that is based on
the DUCET. This table is designed so that it can be *tailored* to meet the requirements of
different languages and customizations.

Briefly stated, the Unicode Collation Algorithm takes an input Unicode string and a Collation
Element Table, containing mapping data for characters. It produces a sort key, which is an
array of unsigned 16-bit integers. Two or more sort keys so produced can then be binary
compared to give the correct comparison between the strings for which they were generated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CU&lt;/code&gt;使用&lt;code&gt;RuleBasedCollator&lt;/code&gt;现&lt;code&gt;Unicode&lt;/code&gt;方的&lt;code&gt;collation order&lt;/code&gt;，具体见文档&lt;a href="https://github.com/unicode-org/icu/blob/main/docs/userguide/collation/customization/index.md"&gt;https://github.com/unicode-org/icu/blob/main/docs/userguide/collation/customization/index.md&lt;/a&gt;，这个提供很多细粒度的&lt;code&gt;order&lt;/code&gt;设置，比如想把&lt;code&gt;a&lt;/code&gt;放在&lt;code&gt;b&lt;/code&gt;的后面等&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Variable collation elements, which typically include punctuation characters and which may or
may not include a subset of symbol characters, require special handling in the Unicode
Collation Algorithm.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;比如空格，-等这些需要设置特别对待，glibc对于这种17，28版本的默认设置有变化（&lt;a href="https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html"&gt;https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html&lt;/a&gt; ）&lt;/p&gt;

&lt;p&gt;以下是&lt;code&gt;CLDR&lt;/code&gt;的&lt;code&gt;root collation&lt;/code&gt;的&lt;code&gt;locale&lt;/code&gt;默认&lt;code&gt;tailors&lt;/code&gt;，对于中文&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  zh/defaultCollation=pinyin&lt;/li&gt;
&lt;li&gt;  zh/pinyin&lt;/li&gt;
&lt;li&gt;  zh/stroke&lt;/li&gt;
&lt;li&gt;  zh-Hant/defaultCollation=stroke
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;zh_pinyin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zh&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;zh_stroke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Collator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;icu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;zh-Hant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;zh_pinyin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;一&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;测&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1
&lt;/span&gt;&lt;span class="n"&gt;zh_stroke&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;一&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;测&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# -1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.unicode.org/reports/tr35/tr35-collation.html#Setting_Options"&gt;https://www.unicode.org/reports/tr35/tr35-collation.html#Setting_Options&lt;/a&gt; CLDR中也描述了各种unicode的扩展描述，以及对应的rule syntax&lt;/p&gt;

&lt;h2&gt;
  
  
  引用链接
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://symbl.cc/en/unicode/blocks/cjk-unified-ideographs/"&gt;https://symbl.cc/en/unicode/blocks/cjk-unified-ideographs/&lt;/a&gt; unicode table&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.flokoe.de/posts/database-character-sets-and-collations-explained/"&gt;https://www.flokoe.de/posts/database-character-sets-and-collations-explained/&lt;/a&gt; mysql中character set和collation解释&lt;br&gt;&lt;br&gt;
&lt;a href="https://gitlab.pyicu.org/main/pyicu"&gt;https://gitlab.pyicu.org/main/pyicu&lt;/a&gt; python版本icu仓库&lt;br&gt;
&lt;a href="https://github.com/dverite/icu_ext/tree/master"&gt;https://github.com/dverite/icu_ext/tree/master&lt;/a&gt; postgresql icu扩展&lt;br&gt;
&lt;a href="https://unicode.org/reports/tr10/"&gt;https://unicode.org/reports/tr10/&lt;/a&gt; unicode官方collation算法文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.unicode.org/reports/tr35/tr35-collation.html"&gt;https://www.unicode.org/reports/tr35/tr35-collation.html&lt;/a&gt; CLDR collation algorithm，collation算法的补充文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://peter.eisentraut.org/blog/2023/03/14/how-collation-works"&gt;https://peter.eisentraut.org/blog/2023/03/14/how-collation-works&lt;/a&gt; collation工作机制描述&lt;br&gt;&lt;br&gt;
&lt;a href="https://peter.eisentraut.org/blog/2023/04/12/how-collation-of-punctuation-and-whitespace-works"&gt;https://peter.eisentraut.org/blog/2023/04/12/how-collation-of-punctuation-and-whitespace-works&lt;/a&gt; 同上&lt;br&gt;&lt;br&gt;
&lt;a href="https://peter.eisentraut.org/blog/2023/05/16/overview-of-icu-collation-settings#colalternate%C2%A0icu%E4%B8%ADcollation%E8%AE%BE%E7%BD%AE%E5%8F%82%E6%95%B0%E6%8F%8F%E8%BF%B0"&gt;https://peter.eisentraut.org/blog/2023/05/16/overview-of-icu-collation-settings#colalternate icu中collation设置参数描述&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://peter.eisentraut.org/blog/2023/06/13/overview-of-icu-collation-settings-part-2"&gt;https://peter.eisentraut.org/blog/2023/06/13/overview-of-icu-collation-settings-part-2&lt;/a&gt; 同上&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.postgresql.org/docs/current/collation.html"&gt;https://www.postgresql.org/docs/current/collation.html&lt;/a&gt; pg中collation官方文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://unicode-org.github.io/icu/userguide/collation/"&gt;https://unicode-org.github.io/icu/userguide/collation/&lt;/a&gt; icu中collation文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://gist.github.com/dpk/8325992"&gt;https://gist.github.com/dpk/8325992&lt;/a&gt; pyicu cheatsheet&lt;br&gt;&lt;br&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator"&gt;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator&lt;/a&gt; 前端国际化对象Intl的collator支持文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/unicode-org/icu/blob/main/docs/userguide/collation/customization/index.md"&gt;https://github.com/unicode-org/icu/blob/main/docs/userguide/collation/customization/index.md&lt;/a&gt; icu collation自定义文档&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc4647.html"&gt;https://www.rfc-editor.org/rfc/rfc4647.html&lt;/a&gt; Unicode中collation的locale标准，html中lang也是使用这个&lt;br&gt;&lt;br&gt;
&lt;a href="https://aticleworld.com/strxfrm-in-c/"&gt;https://aticleworld.com/strxfrm-in-c/&lt;/a&gt; glibc strxfrm示例&lt;br&gt;&lt;br&gt;
&lt;a href="https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html"&gt;https://postgresql.verite.pro/blog/2018/08/27/glibc-upgrade.html&lt;/a&gt; glibc升级对pg带来的影响&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/awslabs/compat-collation-for-glibc/tree/2.17-326.el7"&gt;https://github.com/awslabs/compat-collation-for-glibc/tree/2.17-326.el7&lt;/a&gt; aws应对glibc不同版本影响collate新建的库&lt;br&gt;&lt;br&gt;
&lt;a href="https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/don-t-let-collation-versions-corrupt-your-postgresql-indexes/ba-p/1978394"&gt;https://techcommunity.microsoft.com/t5/azure-database-for-postgresql/don-t-let-collation-versions-corrupt-your-postgresql-indexes/ba-p/1978394&lt;/a&gt; collation版本对pg索引的影响&lt;br&gt;&lt;br&gt;
&lt;a href="https://xobo.org/unicode-normalization-nfd-nfc-nfkd-nfkc/"&gt;https://xobo.org/unicode-normalization-nfd-nfc-nfkd-nfkc/&lt;/a&gt; Unicode normalization中文示例&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.w3.org/International/articles/language-tags/"&gt;https://www.w3.org/International/articles/language-tags/&lt;/a&gt; Language tags描述&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry"&gt;https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry&lt;/a&gt; Language subtag注册表&lt;br&gt;&lt;br&gt;
&lt;a href="https://www.w3.org/International/questions/qa-choosing-language-tags"&gt;https://www.w3.org/International/questions/qa-choosing-language-tags&lt;/a&gt; 如何选择language tags&lt;br&gt;&lt;br&gt;
&lt;a href="https://icu4c-demos.unicode.org/icu-bin/collation.html"&gt;https://icu4c-demos.unicode.org/icu-bin/collation.html&lt;/a&gt; Unicode官方collation在线例子&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/unicode-org/icu-demos/tree/main/webdemo/collation"&gt;https://github.com/unicode-org/icu-demos/tree/main/webdemo/collation&lt;/a&gt; Unicode官方collation例子仓库  &lt;/p&gt;

</description>
      <category>icu</category>
      <category>collation</category>
      <category>postgres</category>
      <category>locale</category>
    </item>
    <item>
      <title>Ubuntu上默认证书库是怎么回事</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Sat, 01 Jun 2024 10:58:06 +0000</pubDate>
      <link>https://forem.com/shouhua_57/ubuntushang-mo-ren-zheng-shu-ku-shi-zen-yao-hui-shi-28kc</link>
      <guid>https://forem.com/shouhua_57/ubuntushang-mo-ren-zheng-shu-ku-shi-zen-yao-hui-shi-28kc</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;最近整&lt;code&gt;HTTP3&lt;/code&gt;客户端时候，需要验证服务端证书，中间出了点小插曲，老是报错见上篇&lt;a href="https://dev.to/shouhua_57/http3zhi-quictlsbao-cuo-unable-to-get-local-issuer-certificate-14ic"&gt;文章&lt;/a&gt;，问题解决了但是想到另外个问题，浏览器和客户端都是怎么验证证书的，我们都知道服务端给出证书，客户端根据本地的或者用户提供的根证书校验，但是系统根证书在哪儿，为什么我没有设置，他们就自动找到了？本文旨在根据这个问题，梳理Ubuntu系统自带证书库和浏览器的证书库。本文主要聚焦Ubuntu系统，但是其他系统的流程大致相似。&lt;/p&gt;

&lt;h2&gt;
  
  
  系统证书库
&lt;/h2&gt;

&lt;p&gt;Ubuntu系统的证书目录位于&lt;code&gt;/etc/ssl/certs&lt;/code&gt;, 根证书是&lt;code&gt;/etc/ssl/certs/ca-certificates.crt&lt;/code&gt;。 其他平台的默认证书可以查看&lt;a href="https://go.dev/src/crypto/x509/root_linux.go"&gt;golang源码文件&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;查看&lt;code&gt;/etc/ssl/certs&lt;/code&gt;发现里面有很多&lt;code&gt;pem&lt;/code&gt;格式证书，这里就是系统根证书目录，我的机器上面有部分软链接到&lt;code&gt;/usr/share/ca-certificates/mozilla&lt;/code&gt;（这个是系统读取&lt;code&gt;/etc/ca-certificates.conf&lt;/code&gt;配置加入的）。文件&lt;code&gt;/etc/ssl/certs/ca-certificates.crt&lt;/code&gt;是这些证书的合集，可以使用如下命令查看所有证书的&lt;code&gt;subject&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'openssl x509 -noout -subject'&lt;/span&gt; &lt;span class="s1"&gt;'/BEGIN/ {close(cmd)}; {print | cmd}'&lt;/span&gt; &amp;lt; /etc/ssl/certs/ca-certificates.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  系统证书更新
&lt;/h3&gt;

&lt;p&gt;系统不建议我们自己手动证书到这个目录和文件中，那我们如何添加证书到系统呢？系统提供了命令&lt;code&gt;update-ca-certificates&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;man 8 update-ca-certificates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;根据manpage介绍，&lt;code&gt;update-ca-certificates&lt;/code&gt;更新&lt;code&gt;etc/ssl/certs&lt;/code&gt;和&lt;code&gt;ca-certificates.crt&lt;/code&gt;。我们将&lt;code&gt;pem&lt;/code&gt;格式证书以&lt;code&gt;.crt&lt;/code&gt;后缀放到&lt;code&gt;/usr/local/share/ca-certificates&lt;/code&gt;，一个证书一个文件，然后执行命令就会自动添加到根目录证书中。&lt;/p&gt;

&lt;h3&gt;
  
  
  寻找证书
&lt;/h3&gt;

&lt;p&gt;刚才不建议咱们自己添加证书，因为其中有些步骤是看不见的，比如&lt;a href="https://www.openssl.org/docs/manmaster/man1/openssl-rehash.html"&gt;c_rehash&lt;/a&gt;过程。寻找证书通过证书的&lt;code&gt;subject&lt;/code&gt;，如果通过打开文件找到&lt;code&gt;subject&lt;/code&gt;比对，那这样效率太低了，所以通过将证书的&lt;code&gt;subject&lt;/code&gt;的hash值作为文件名，就可以提高寻找效率了，&lt;code&gt;rehash&lt;/code&gt;就是干这个事。比如，我的系统中有个&lt;code&gt;Go_Daddy_Class_2_CA.pem&lt;/code&gt;证书&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ll /etc/ssl/certs | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'Go_Daddy_Class_2_CA'&lt;/span&gt;
&lt;span class="c"&gt;# f081611a.0 -&amp;gt; Go_Daddy_Class_2_CA.pem 这个是rehash生成文件&lt;/span&gt;
&lt;span class="c"&gt;# Go_Daddy_Class_2_CA.pem -&amp;gt; /usr/share/ca-certificates/mozilla/Go_Daddy_Class_2_CA.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以通过以下命令看下hash值是否正确(当然是正确的)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl x509 &lt;span class="nt"&gt;-hash&lt;/span&gt; &lt;span class="nt"&gt;-fingerprint&lt;/span&gt; &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /etc/ssl/certs/Go_Daddy_Class_2_CA.pem&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# f081611a&lt;/span&gt;
&lt;span class="c"&gt;# SHA1 Fingerprint=27:96:BA:E6:3F:18:01:E2:77:26:1B:A0:D7:77:70:02:8F:20:EE:E4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;最后的&lt;code&gt;.0&lt;/code&gt;是为了防止hash碰撞产生一样的值。&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSSL
&lt;/h2&gt;

&lt;p&gt;OpenSSL默认情况使用系统证书库&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl version &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="c"&gt;# 打印OpenSSL目录&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;openssl version &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'"'&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# 查看OpenSSL目录内容&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;通过查看OpenSSL目录，里面的&lt;code&gt;certs&lt;/code&gt;文件夹默认是软链接到&lt;code&gt;/etc/ssl/certs&lt;/code&gt;。因此OpenSSL编写代码使用默认证书库就是系统证书库&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;SSL_CTX_set_default_verify_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssl_ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  浏览器
&lt;/h2&gt;

&lt;p&gt;浏览器一般使用图形界面设置，但是我就觉得纳闷了，我在浏览器中设置了，但是根目录证书中没有，猜想肯定使用自己证书库，查找后确定是这样的。&lt;br&gt;&lt;br&gt;
&lt;code&gt;Firefox&lt;/code&gt;和&lt;code&gt;Chrome&lt;/code&gt;都使用&lt;code&gt;sqlite&lt;/code&gt;存储用户导入证书，具体放在文件&lt;code&gt;cert9.db&lt;/code&gt;中，早期放在&lt;code&gt;cert8.db&lt;/code&gt;，这里咱们不溯源历史，有兴趣可以查看相关&lt;a href="https://wiki.mozilla.org/NSS_Shared_DB"&gt;文档&lt;/a&gt;。同样，这个数据库不建议直接操作，浏览器都使用了&lt;a href="https://wiki.mozilla.org/NSS_Shared_DB"&gt;NSS_Shared_DB&lt;/a&gt;管理数据库，提供了命令&lt;code&gt;certutil&lt;/code&gt;操作，Ubuntu需要安装&lt;code&gt;libnss3-tools&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libnss3-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md#add-a-certificate"&gt;Chromium源码文档&lt;/a&gt;中有提供如何操作。&lt;/p&gt;

&lt;h3&gt;
  
  
  查找浏览器证书库地址
&lt;/h3&gt;

&lt;p&gt;命令操作没啥问题，但是关键是制定证书的数据库地址。Ubuntu一般使用&lt;code&gt;snap&lt;/code&gt;(尽管不想使用)按照&lt;code&gt;Chrome&lt;/code&gt;，会放在snap目录&lt;code&gt;$HOME/snap/[chromium|firefox]&lt;/code&gt;，如果没有snap目录，可以查看个人目录隐藏文件夹&lt;code&gt;$HOME/.pki/nssdb&lt;/code&gt;，可能会不一样，可以通过查找方式查询：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find &lt;span class="nv"&gt;$HOME&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'cert9.db'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意浏览器都是以用户(profile)形式生成&lt;code&gt;cert9.db&lt;/code&gt;的，Chromium系列明显亲民点，可以使用比如&lt;code&gt;$HOME/snap/chromium/current&lt;/code&gt;软链接到当前用户目录。&lt;/p&gt;

&lt;h2&gt;
  
  
  JKS(Java Key Store)
&lt;/h2&gt;

&lt;p&gt;在前面查看&lt;code&gt;/etc/ssl/certs&lt;/code&gt;目录时，看到有个文件夹&lt;code&gt;java&lt;/code&gt;，翻看资料后发现，&lt;code&gt;JAVA&lt;/code&gt;平台可以使用自己的证书库，支持多种存储格式，JKS的store位于&lt;code&gt;$JAVA_HOME/lib/security/cacerts&lt;/code&gt;或者&lt;code&gt;$JAVA_HOME/jre/lib/security/cacerts&lt;/code&gt;，JKS使用JDK提供的工具&lt;code&gt;keytool&lt;/code&gt;管理。JKS的store默认密码是&lt;code&gt;changeit&lt;/code&gt;。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# find JAVA_HOME&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;readlink&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which java&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/\/bin\/java//'&lt;/span&gt;

&lt;span class="c"&gt;# List&lt;/span&gt;
keytool &lt;span class="nt"&gt;-list&lt;/span&gt; &lt;span class="nt"&gt;-keystore&lt;/span&gt; &lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;/lib/security/cacerts &lt;span class="nt"&gt;-storepass&lt;/span&gt; changeit | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;span class="c"&gt;# OR&lt;/span&gt;
keytool &lt;span class="nt"&gt;-list&lt;/span&gt; &lt;span class="nt"&gt;-cacerts&lt;/span&gt; &lt;span class="nt"&gt;-storepass&lt;/span&gt; changeit | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;

&lt;span class="c"&gt;# Add&lt;/span&gt;
keytook &lt;span class="nt"&gt;-import&lt;/span&gt; &lt;span class="nt"&gt;-alias&lt;/span&gt; testCert &lt;span class="nt"&gt;-keystore&lt;/span&gt; &lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;/lib/security/cacerts &lt;span class="nt"&gt;-file&lt;/span&gt; ca_cert.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  有用知识
&lt;/h2&gt;

&lt;p&gt;工具&lt;a href="https://github.com/FiloSottile/mkcert"&gt;mkcert&lt;/a&gt;是cert操作相关的库，里面有上面的相关信息，有兴趣可以仔细查看阅读。&lt;/p&gt;

</description>
      <category>openssl</category>
      <category>certificate</category>
      <category>chrome</category>
      <category>certutil</category>
    </item>
    <item>
      <title>HTTP3之key update</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:25:30 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-key-update-nod</link>
      <guid>https://forem.com/shouhua_57/http3zhi-key-update-nod</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;Key update属于传输协议(QUIC)的特点之一，必须在handshake完成以后进行。&lt;br&gt;
TLS1.3的&lt;a href="https://www.rfc-editor.org/rfc/rfc8446.html#section-4.6.3"&gt;key update&lt;/a&gt;使用专门的握手消息通知对方，我这边要修改密钥了，下次我们使用新的密钥沟通，起到密钥同步效果。&lt;br&gt;
而&lt;a href="https://www.rfc-editor.org/rfc/rfc9001#name-key-update"&gt;QUIC-TLS&lt;/a&gt;中则使用short header packet中的Key Phase bit是否翻转(toggled)来通知key update。1-RTT包中默认为0，不兼容TLS1.3的key update message。&lt;br&gt;
另外，QUIC-TLS要求双方同时更新keys,而TLS1.3双方是独立的处理。&lt;/p&gt;

&lt;h2&gt;
  
  
  实现
&lt;/h2&gt;

&lt;p&gt;实现使用ngtcp2库&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先一方endpoing使用接口初始化key udpate, 告诉库应用程序想要更新keys，告知对方要key update
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_initiate_key_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;print_debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ngtcp2_conn_initiate_key_update: %s(The previous key update has not been confirmed yet; or key update is too frequent; or new keys are not available yet.)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;使用ngtcp2的key update callback，他用于库告诉应用程序你需要key update了
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_crypto_ctx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;crypto_ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_get_crypto_ctx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_crypto_aead&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;aead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crypto_ctx&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;aead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;keylen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_crypto_aead_keylen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ivlen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_crypto_packet_protection_ivlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aead&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;rx_key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tx_key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngtcp2_crypto_update_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;rx_aead_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;rx_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx_iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_aead_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx_iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_rx_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_tx_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secretlen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print_debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ERROR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ngtcp2_crypto_update_key failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>http3</category>
      <category>keyupdate</category>
      <category>quic</category>
    </item>
    <item>
      <title>HTTP3之QUIC协议early data</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:24:57 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-quicxie-yi-early-data-3899</link>
      <guid>https://forem.com/shouhua_57/http3zhi-quicxie-yi-early-data-3899</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;上回实现了QUIC协议的Connection Migration，本章实现early data。首先要浓情以这个概念，early data跟0-RTT不是一个概念，前者基于后者，在首次发送初始frame时候就将请求内容一起发送过去，这里涉及到PSK和session ticket概念，这个可以通过过QUIC协议整明白，网上也有文章说明，后面有机会整理。&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9000"&gt;https://datatracker.ietf.org/doc/html/rfc9000&lt;/a&gt;&lt;br&gt;
&lt;a href="https://datatracker.ietf.org/doc/html/rfc9001#name-0-rtt"&gt;https://datatracker.ietf.org/doc/html/rfc9001#name-0-rtt&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  实现
&lt;/h2&gt;

&lt;p&gt;本文主要使用ngtcp2和nghttp3实现early data请求，下面主要描述主要代码步骤，如果不明白，可以参考全部&lt;a href="https://github.com/Shouhua/aioquic/blob/note/note/ngtcp2/http3_client.c"&gt;文件&lt;/a&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;OpenSSL session管理&lt;br&gt;
TLS1.3目前已经抛弃前面版本使用的&lt;a href="https://datatracker.ietf.org/doc/html/rfc8446#section-2.2"&gt;Session IDs&lt;/a&gt;, 转而使用PSK(Pre Shared Key)。OpenSSL库还是使用Session的概念管理TLS1.3的Session Resumption。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;OpenSSL可以配置使用外部pem文件保存session数据&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* 在成功新建SSL Context后配置callback保存PSK数据 */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// session stored externally by hand in callback function&lt;/span&gt;
     &lt;span class="n"&gt;SSL_CTX_set_session_cache_mode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ssl_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SSL_SESS_CACHE_CLIENT&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;SSL_SESS_CACHE_NO_INTERNAL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;SSL_CTX_sess_set_new_cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ssl_ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_session_cb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Session callback中先使用max_early_data参数判断当前连接是否支持early data, 然后保存session数据
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;max_early_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;max_early_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SSL_SESSION_get_max_early_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;UINT32_MAX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"max_early_data_size is not 0xffffffff: %#x&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_early_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;BIO&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BIO_new_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Could not write TLS session in %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;PEM_write_bio_SSL_SESSION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Unable to write TLS session to file&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;BIO_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;新建SSL对象后, 加载session数据
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;BIO&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BIO_new_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cm"&gt;/* open BIO file failed */&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"BIO_new_file: Could not read TLS session file %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;SSL_SESSION&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PEM_read_bio_SSL_SESSION&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;BIO_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PEM_read_bio_SSL_SESSION: Could not read TLS session file %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;session_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="k"&gt;else&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;SSL_set_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"SSL_set_session: Could not set session&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;disable_early_data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SSL_SESSION_get_max_early_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;early_data_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
               &lt;span class="n"&gt;SSL_set_quic_early_data_enabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="n"&gt;SSL_SESSION_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;保存QUIC协议中的Transport Prameters为pem格式文件，用于下次early data时发送，可以在ngtcp2的handshake_completed的callback中保存
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* save quic transport parameters */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
     &lt;span class="n"&gt;ngtcp2_ssize&lt;/span&gt; &lt;span class="n"&gt;datalen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_encode_0rtt_transport_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datalen&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Could not encode 0-RTT transport parameters: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datalen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;write_transport_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datalen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Could not write transport parameters in %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;在应用代码侧, 如果可以使用early data功能, 传递上次保存的Quic Transport Parameters,
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* load quic transport parameters */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;early_data_enabled&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;datalen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;read_pem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tp_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"transport parameters"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"QUIC TRANSPORT PARAMETERS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;datalen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"client quic init early data read pem failed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;early_data_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="k"&gt;else&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_decode_and_set_0rtt_transport_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;size_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;datalen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ngtcp2_conn_decode_and_set_0rtt_transport_params failed: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rv&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
               &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;early_data_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;make_stream_early&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// setup nghttp3 connection and populate http3 request&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// free memory which allocated in read_pem function&lt;/span&gt;
               &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="n"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// free memory which allocated in read_pem function&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  后续
&lt;/h2&gt;

&lt;p&gt;下次继续QUIC特性key update。&lt;/p&gt;

</description>
      <category>http3</category>
      <category>quic</category>
      <category>earlydata</category>
    </item>
    <item>
      <title>HTTP3之QUIC协议中的Connection Migration</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:24:17 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-quicxie-yi-zhong-de-connection-migration-1ehc</link>
      <guid>https://forem.com/shouhua_57/http3zhi-quicxie-yi-zhong-de-connection-migration-1ehc</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc9000#name-connection-migration"&gt;Connection Migration&lt;/a&gt;是&lt;code&gt;QUIC&lt;/code&gt;协议的特性之一。协议通信一般依赖网络标识比如(IP和port), 如果网络切换，通信需要重新开始，这对于频繁网络切换场景体验很差，比如无线网络使用APP时，网络切换可能需要重新登陆，终端用户可能莫名其妙。&lt;code&gt;QUIC&lt;/code&gt;协议设计的时候，使用&lt;code&gt;Connection ID&lt;/code&gt;标识对方，网络切换只要保证&lt;code&gt;Connection ID&lt;/code&gt;正确就可以。&lt;code&gt;Connection Migration&lt;/code&gt;就是描述这一过程交互规范。&lt;/p&gt;

&lt;h2&gt;
  
  
  细节
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Connection Migration&lt;/code&gt;需要在&lt;code&gt;Handshake&lt;/code&gt;完成后才能执行。发送方可能发送&lt;code&gt;PATH_CHALLANGE&lt;/code&gt;帧给对方，对方可能发送&lt;code&gt;PATH_RESPONSE&lt;/code&gt;帧回应。发送方也可以不发送probe frame, 直接切换继续通信。&lt;/p&gt;

&lt;h2&gt;
  
  
  实现
&lt;/h2&gt;

&lt;p&gt;本地实验最好的方式就是变更port,比如从&lt;code&gt;9000&lt;/code&gt;变为&lt;code&gt;9001&lt;/code&gt;。下面使用&lt;a href="https://github.com/ngtcp2"&gt;ngtcp2&lt;/a&gt;库实现&lt;code&gt;Connection Migration&lt;/code&gt;的代码片段。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;生成新的&lt;code&gt;UDP socket&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr_in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin_addr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;s_addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;htonl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;INADDR_ANY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin_family&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;htons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9001&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AF_INET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SOCK_DGRAM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_UDP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;remote_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;getsockname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;local_len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;使用&lt;code&gt;ngtcp2&lt;/code&gt;提供的&lt;code&gt;Connection Migration&lt;/code&gt;接口。
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_initiate_immediate_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngtcp2_conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_path&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_tstamp&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_initiate_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngtcp2_conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_path&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_tstamp&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ngtcp2&lt;/code&gt;提供了两种接口，都会发送&lt;code&gt;PATH_CHALLANGE&lt;/code&gt;, 但是前者不会等待对方的&lt;code&gt;PATH_RESPONSE&lt;/code&gt;就迁移。后者会等待对端的&lt;code&gt;PATH_RESPONSE&lt;/code&gt;帧才迁移本地的网络路径。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;ngtcp2_addr&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;ngtcp2_addr_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;local_len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// nat rebinding&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;ngtcp2_conn_set_local_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
     &lt;span class="n"&gt;ngtcp2_conn_set_path_user_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;ngtcp2_path&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
               &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;remote_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;};&lt;/span&gt;
     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_conn_initiate_immediate_migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="c1"&gt;// if ((res = ngtcp2_conn_initiate_migration(conn, &amp;amp;path, timestamp())) != 0)&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ngtcp2_conn_initiate_immediate_migration: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;处理&lt;code&gt;path validation callback&lt;/code&gt;，比如对端回复了&lt;code&gt;PATH_RESPONSE&lt;/code&gt;帧
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;path_validation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ngtcp2_conn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_path&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;ngtcp2_path&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;old_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;ngtcp2_path_validation_result&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;get_ip_port&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr_storage&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;old_path&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;", old local: %s:%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;NGTCP2_PATH_VALIDATION_FLAG_PREFERRED_ADDR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remote_addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addrlen&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remote_addrlen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addrlen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  最后
&lt;/h2&gt;

&lt;p&gt;可以参考&lt;code&gt;ngtcp2&lt;/code&gt;的example(&lt;a href="https://github.com/ngtcp2/ngtcp2/tree/main/examples"&gt;https://github.com/ngtcp2/ngtcp2/tree/main/examples&lt;/a&gt;), 如果想看简单的，可以参考我的&lt;a href="https://github.com/Shouhua/aioquic/blob/note/note/ngtcp2/http3_client.c"&gt;http3 client&lt;/a&gt;&lt;/p&gt;

</description>
      <category>http3</category>
      <category>quic</category>
      <category>connectionmigration</category>
    </item>
    <item>
      <title>HTTP3之编译nginx</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:23:38 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-bian-yi-nginx-42g9</link>
      <guid>https://forem.com/shouhua_57/http3zhi-bian-yi-nginx-42g9</guid>
      <description>&lt;h2&gt;
  
  
  关于
&lt;/h2&gt;

&lt;p&gt;nginx目前最新版本提供了HTTP3服务，为了测试环境，本文记录从源码编译nginx的过程，其中包括依赖的编译。&lt;/p&gt;

&lt;h2&gt;
  
  
  环境
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="c"&gt;# Distributor ID: Ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# Description:    Ubuntu 22.04.4 LTS&lt;/span&gt;
&lt;span class="c"&gt;# Release:        22.04&lt;/span&gt;
&lt;span class="c"&gt;# Codename:       jammy&lt;/span&gt;

gcc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  编译Nginx
&lt;/h2&gt;

&lt;h3&gt;
  
  
  编译或安装依赖
&lt;/h3&gt;

&lt;h4&gt;
  
  
  SSL Library
&lt;/h4&gt;

&lt;p&gt;Nginx实现HTTP3底层依赖SSL库，可以选择BoringSSL, LibreSSL, QuicTLS，&lt;a href="https://nginx.org/en/docs/quic.html"&gt;如果选择OpenSSL兼容层将不会提供&lt;code&gt;early data&lt;/code&gt;的功能&lt;/a&gt;。本次我们选择QuicTLS，她也是基于OpenSSL修改的版本。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt; 1 &lt;span class="nt"&gt;-b&lt;/span&gt; openssl-3.1.5+quic https://github.com/quictls/openssl
&lt;span class="nb"&gt;cd &lt;/span&gt;openssl
./config enable-tls1_3
make
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;如果Linux机器上已经安装了&lt;code&gt;libssl&lt;/code&gt;和&lt;code&gt;libssl-dev&lt;/code&gt;，会有冲突和报错，本质问题及解决方法参考另外一篇&lt;a href="https://juejin.cn/post/7337491274457841727"&gt;文章&lt;/a&gt;， 如果那边整不明白，可以查看&lt;code&gt;man ldconfig&lt;/code&gt;，或者直接添加相关库的ld config文件，相信走到这里的，动态库这些个问题应该都差不多了:)。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;编译默认动态库位于&lt;code&gt;/usr/local/lib64&lt;/code&gt;，&lt;code&gt;include&lt;/code&gt;文件位于&lt;code&gt;/usr/local/include&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  其他依赖
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;build-essential libpcre3 libpcre3-dev zlib1g zlib1g-dev libxml2 libxml2-dev libxslt1-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  编译Nginx
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./configure &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/michael/nginx &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-debug&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-http_ssl_module&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-http_v2_module&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-http_v3_module&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-cc-opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-I/usr/local/include"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-ld-opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-L/usr/local/lib64"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--with-cc-opt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-DNGX_QUIC_DEBUG_PACKETS -DNGX_QUIC_DEBUG_FRAMES -DNGX_QUIC_DEBUG_ALLOC -DNGX_QUIC_DEBUG_CRYPTO"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--add-dynamic-module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/download/njs-0.8.4/nginx"&lt;/span&gt; &lt;span class="c"&gt;# 添加njs模块&lt;/span&gt;
make
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;默认情况下，nginx会安装到&lt;code&gt;/root/nginx&lt;/code&gt;，进入文件夹后，默认会有如下文件夹&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conf 里面有默认的配置文件nginx.conf，可以按照自己要求修改
html 里面对应的默认的index.html页面，可以按照自己要求修改
logs 里面对应nginx的access和error日志
sbin 包括nginx等命令
moudles 如果有添加模块编译比如njs，会有这个目录，包括模块的动态链接库
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;建议将nginx命令加入到path中&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export PATH="${PATH}:/root/nginx/sbin"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  测试Nginx
&lt;/h2&gt;

&lt;p&gt;使用如下命令启动nginx，再访问试下，看是否正常&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nginx &lt;span class="nt"&gt;-V&lt;/span&gt; &lt;span class="c"&gt;#查看详细信息&lt;/span&gt;
nginx &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="c"&gt;#测试配置是否正常&lt;/span&gt;
nginx &lt;span class="nt"&gt;-s&lt;/span&gt; start &lt;span class="c"&gt;# 启动nginx&lt;/span&gt;
curl localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  配置HTTP3
&lt;/h2&gt;

&lt;h3&gt;
  
  
  自签证书
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /usr/bin/env bash&lt;/span&gt;

&lt;span class="c"&gt;# Generate self signed ca and server cert for localhost test&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-eou&lt;/span&gt; pipefail

&lt;span class="nv"&gt;CA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ca.pem"&lt;/span&gt;
&lt;span class="nv"&gt;CA_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ca_key.pem"&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_CERT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"server_cert.pem"&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"server_key.pem"&lt;/span&gt;
&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;
&lt;span class="nv"&gt;IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;

&lt;span class="c"&gt;# NOTICE quictls&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/lib64
openssl version

&lt;span class="c"&gt;# clean&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$CA&lt;/span&gt; &lt;span class="nv"&gt;$CA_KEY&lt;/span&gt; &lt;span class="nv"&gt;$SERVER_CERT&lt;/span&gt; &lt;span class="nv"&gt;$SERVER_KEY&lt;/span&gt;

&lt;span class="c"&gt;# 1. Generate self-signed certificate and private key&lt;/span&gt;
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-keyout&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=CN/ST=Hubei/L=Wuhan/O=QUIC/OU=QUICUNIT/CN=localhost/emailAddress=ca@example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noenc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CA's self-signed certificate DONE"&lt;/span&gt;
&lt;span class="c"&gt;# openssl x509 -in "${CA}" -noout -text&lt;/span&gt;

&lt;span class="c"&gt;# 2. Generate server cert and private key&lt;/span&gt;
openssl req &lt;span class="nt"&gt;-x509&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-newkey&lt;/span&gt; rsa:4096 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-keyout&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-out&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-subj&lt;/span&gt; &lt;span class="s2"&gt;"/C=CN/ST=Hubei/L=Wuhan/O=QUIC/OU=QUICUNIT/CN=localhost/emailAddress=server@example.com"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-addext&lt;/span&gt; &lt;span class="s2"&gt;"subjectAltName=DNS:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,IP:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CA&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-CAkey&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-copy_extensions&lt;/span&gt; copyall &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-days&lt;/span&gt; 365 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-noenc&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Server's certificate DONE"&lt;/span&gt;
&lt;span class="c"&gt;# openssl x509 -in "${SERVER_CERT}" -noout -text&lt;/span&gt;

&lt;span class="c"&gt;# 6. Verify server certificate&lt;/span&gt;
openssl verify &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-verbose&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-show_chain&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-trusted&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CA&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SERVER_CERT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;脚本里面的&lt;code&gt;IP&lt;/code&gt;和&lt;code&gt;HOST&lt;/code&gt;，将生成的&lt;code&gt;server_cert.pem&lt;/code&gt;和&lt;code&gt;server_key.pem&lt;/code&gt;放到前面nginx的安装目录&lt;code&gt;/root/nginx/certs&lt;/code&gt;，并且将&lt;code&gt;ca_cert.pem&lt;/code&gt;添加到信任列表(浏览器可以直接导入)。&lt;/p&gt;

&lt;h3&gt;
  
  
  Nginx配置文件
&lt;/h3&gt;

&lt;p&gt;修改/root/nginx/conf/nginx.conf中的server块添加如下内容&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;quic&lt;/span&gt; &lt;span class="s"&gt;reuseport&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;http2&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;server_name&lt;/span&gt;  &lt;span class="s"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;ECDHE-ECDSA-AES256-GCM-SHA384&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_conf_command&lt;/span&gt; &lt;span class="s"&gt;Ciphersuites&lt;/span&gt; &lt;span class="s"&gt;TLS_CHACHA20_POLY1305_SHA256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/root/nginx/certs/server_cert.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/root/nginx/certs/server_key.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;重新启动Nginx, 测试HTTP3服务。这里&lt;code&gt;ssl_cihpers&lt;/code&gt;和&lt;code&gt;ssl_conf_command Ciphersuites&lt;/code&gt;跟&lt;code&gt;man openssl-cihpers&lt;/code&gt;一致。&lt;/p&gt;

&lt;h3&gt;
  
  
  测试
&lt;/h3&gt;

&lt;h3&gt;
  
  
  浏览器
&lt;/h3&gt;

&lt;p&gt;firefox可以直接使用http3服务, 关于浏览器导入自签证书，后面整个专门文章介绍。&lt;/p&gt;

&lt;h3&gt;
  
  
  curl
&lt;/h3&gt;

&lt;p&gt;curl需要自编译添加http3服务，是另外一个话题了，curl官网关于编译写的很清晰&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--http3&lt;/span&gt; &lt;span class="nt"&gt;--cacert&lt;/span&gt; ca_cert.pem &lt;span class="nt"&gt;-v&lt;/span&gt; https://localhost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>http3</category>
      <category>quic</category>
      <category>nginx</category>
    </item>
    <item>
      <title>HTTP3之QUICTLS编译冲突问题</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:22:46 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-quictlsbian-yi-chong-tu-wen-ti-1lg1</link>
      <guid>https://forem.com/shouhua_57/http3zhi-quictlsbian-yi-chong-tu-wen-ti-1lg1</guid>
      <description>&lt;h3&gt;
  
  
  背景
&lt;/h3&gt;

&lt;p&gt;本文主要介绍QUICTLS依赖库编译时与OpenSSL冲突问题和使用时找不到依赖库问题。&lt;/p&gt;

&lt;h3&gt;
  
  
  环境
&lt;/h3&gt;

&lt;p&gt;操作系统: &lt;code&gt;Ubuntu 22.04.4 LTS&lt;/code&gt;&lt;br&gt;&lt;br&gt;
编译工具: &lt;code&gt;gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  QUICTLS库描述
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;OpenSSL&lt;/code&gt;官方支持&lt;code&gt;QUIC&lt;/code&gt;协议进度太慢，于是出现了基于OpenSSL库改的&lt;a href="https://github.com/quictls/openssl"&gt;QUICTLS&lt;/a&gt;库，原因是QUIC协议不是使用原生的&lt;a href="https://datatracker.ietf.org/doc/html/rfc8446"&gt;TLS1.3协议&lt;/a&gt;。QUICTLS是基于OpenSSL修改了TLS1.3相关内容的库，OpenSSL用户态命令还是一样。&lt;/p&gt;
&lt;h3&gt;
  
  
  问题描述
&lt;/h3&gt;

&lt;p&gt;本机上原先通过&lt;code&gt;apt&lt;/code&gt;安装了&lt;code&gt;OpenSSL3.x&lt;/code&gt;, &lt;a href="https://curl.se/docs/http3.html"&gt;编译QUICTLS&lt;/a&gt;后, QUICTLS默认的&lt;code&gt;openssl&lt;/code&gt;命令位于&lt;code&gt;/usr/local/bin/openssl&lt;/code&gt;, 官方OpenSSL位于&lt;code&gt;/usr/bin/openssl&lt;/code&gt;, 环境变量&lt;code&gt;PATH&lt;/code&gt;中也是按照这个目录顺序。&lt;br&gt;&lt;br&gt;
如果键入&lt;code&gt;openssl version&lt;/code&gt;，使用的是QUICTLS版本的&lt;code&gt;openssl&lt;/code&gt;命令, 这时会报错:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;openssl: error while loading shared libraries: libssl.so.81.3: cannot open shared object file: No such file or directory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  问题原因
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;openssl&lt;/code&gt; 命令运行时找不到依赖的动态库, 使用&lt;code&gt;ldd&lt;/code&gt;命令可以查看依赖共享库情况:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ldd &lt;span class="si"&gt;$(&lt;/span&gt;which openssl&lt;span class="si"&gt;)&lt;/span&gt;
     linux-vdso.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fff7&lt;span class="k"&gt;****&lt;/span&gt;000&lt;span class="o"&gt;)&lt;/span&gt;
     libssl.so.81.3 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; not found
     libcrypto.so.81.3 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; not found
     libc.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libc.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fb650&lt;span class="k"&gt;****&lt;/span&gt;00&lt;span class="o"&gt;)&lt;/span&gt;
     /lib64/ld-linux-x86-64.so.2 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fb65&lt;span class="k"&gt;****&lt;/span&gt;000&lt;span class="o"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以看到&lt;code&gt;libssl.so.81.3&lt;/code&gt;和&lt;code&gt;libcrypto.so.81.3&lt;/code&gt;找不到, 官方OpenSSL的动态库是&lt;code&gt;libssl.so.3&lt;/code&gt;和&lt;code&gt;libcrypto.so.3&lt;/code&gt;, 位于&lt;code&gt;/usr/lib/x86_64-linux-gnu&lt;/code&gt;。QUICTLS动态库名称中 &lt;strong&gt;81&lt;/strong&gt; 是 &lt;strong&gt;Q&lt;/strong&gt; 的ASCII码值, 以示区分:&lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /usr/lib/x86_64-linux-gnu | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'libssl|libcrypto'&lt;/span&gt;
     &lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;  1 root root   9098630  2月  1 02:43 libcrypto.a
     lrwxrwxrwx  1 root root        14  2月  1 02:43 libcrypto.so -&amp;gt; libcrypto.so.3
     &lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;  1 root root   4451632  2月  1 02:43 libcrypto.so.3
     &lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;  1 root root    418464  2月 17  2023 libssl3.so
     &lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;  1 root root   1231268  2月  1 02:43 libssl.a
     lrwxrwxrwx  1 root root        11  2月  1 02:43 libssl.so -&amp;gt; libssl.so.3
     &lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;  1 root root    667864  2月  1 02:43 libssl.so.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  解决过程
&lt;/h3&gt;

&lt;p&gt;解决过程也是理解Linux动态库加载流程的过程, 当程序执行时, 会通过一定的顺序寻找共享库加载。通过阅读&lt;a href="https://man7.org/linux/man-pages/man8/ld.so.8.html"&gt;ld.so的manpage文档&lt;/a&gt; , 如果共享库没有包含slash, 按照以下的顺序寻找, 下面是原文:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;If a shared object dependency does not contain a slash, then it
       is searched for in the following order:

       (1)  Using the directories specified in the DT_RPATH dynamic
            section attribute of the binary if present and DT_RUNPATH
            attribute does not exist.  Use of DT_RPATH is deprecated.

       (2)  Using the environment variable LD_LIBRARY_PATH, unless the
            executable is being run in secure-execution mode (see
            below), in which case this variable is ignored.

       (3)  Using the directories specified in the DT_RUNPATH dynamic
            section attribute of the binary if present.  Such
            directories are searched only to find those objects required
            by DT_NEEDED (direct dependencies) entries and do not apply
            to those objects' children, which must themselves have their
            own DT_RUNPATH entries.  This is unlike DT_RPATH, which is
            applied to searches for all children in the dependency tree.

       (4)  From the cache file /etc/ld.so.cache, which contains a
            compiled list of candidate shared objects previously found
            in the augmented library path.  If, however, the binary was
            linked with the -z nodefaultlib linker option, shared
            objects in the default paths are skipped.  Shared objects
            installed in hardware capability directories (see below) are
            preferred to other shared objects.

       (5)  In the default path /lib, and then /usr/lib.  (On some
            64-bit architectures, the default paths for 64-bit shared
            objects are /lib64, and then /usr/lib64.)  If the binary was
            linked with the -z nodefaultlib linker option, this step is
            skipped.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;设置ELF文件的DT_RPATH, 上面文档指出这个参数过时了, 但是依然很多在使用。编译时指定GCC的相关参数, 比如&lt;code&gt;-Wl,-rpath=/usr/local/lib64&lt;/code&gt;, 默认情况下文档说是设置&lt;code&gt;DT_RPATH&lt;/code&gt;,
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;man 1 ld
...
--enable-new-dtags
--disable-new-dtags
     This linker can create the new dynamic tags in ELF. But the older ELF systems
     may not understand them. If you specify --enable-new-dtags, the new dynamic tags
     will be created as needed and older dynamic tags will be omitted.  If you 
     specify --disable-new-dtags, no new dynamic tags will be created. By default, the 
     new dynamic tags are not created. Note that those options are only available for ELF systems.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但是在我机器 &lt;code&gt;Ubuntu22.04, GCC11.04&lt;/code&gt; 验证是默认设置的&lt;code&gt;DT_RUNPATH&lt;/code&gt;, 如果要设置&lt;code&gt;DT_RPATH&lt;/code&gt;, 需要显式关闭开关&lt;code&gt;-Wl,--disable-new-dtags&lt;/code&gt;, 编译完成后可以使用如下命令检验: &lt;br&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;readelf &lt;span class="nt"&gt;-d&lt;/span&gt; build/client | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'RUNPATH|RPATH'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;可以设置&lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;, 比如&lt;code&gt;LD_LIBRARY_PATH="/usr/local/lib64" openssl version&lt;/code&gt;能正确运行&lt;/li&gt;
&lt;li&gt;设置DT_RUNPATH, 方法同1, 但是需要她的使用顺序以及她只应用与DT_NEEDED的依赖库, 他们的子依赖不会使用这个参数指定的地址, 这也是争议的地方, DT_RPATH说是过时了，而且存在安全争议，但是在检索第一位管用&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/etc/ld.so.cache&lt;/code&gt;本地缓存，这个需要在机器上自己设置，一般在目录 &lt;code&gt;/etc/ld.so.conf.d/&lt;/code&gt; 添加配置文件，然后刷新缓存: 
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/lib64"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/ld.so.conf.d/quictls.conf &lt;span class="c"&gt;# 添加配置文件&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ldconfig &lt;span class="c"&gt;# 刷新 ld.so.cache&lt;/span&gt;
openssl version &lt;span class="c"&gt;# 现在能正常执行&lt;/span&gt;
&lt;span class="c"&gt;# OpenSSL 3.1.4+quic 24 Oct 2023 (Library: OpenSSL 3.1.4+quic 24 Oct 2023)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  解决办法
&lt;/h3&gt;

&lt;p&gt;上述已经列出了所有的解决思路和相应解决办法，一般使用最后一种，会在本机添加多个config文件，比如，我的添加了依赖库QUITLS和ngtcp2的配置文件，配置文件内容也简单，就是依赖库所在地址， 即上述的第四种方式。&lt;/p&gt;

&lt;h3&gt;
  
  
  例子
&lt;/h3&gt;

&lt;p&gt;下面根据QUIC-ECHO工程依赖quictls的例子解释下GCC编译参数, 具体可以参见相关的 &lt;a href="https://github.com/Shouhua/quic-echo/blob/main/Makefile"&gt;Makefile&lt;/a&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gcc &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nt"&gt;-Wall&lt;/span&gt; &lt;span class="nt"&gt;-Wextra&lt;/span&gt; &lt;span class="nt"&gt;-DDEBUG&lt;/span&gt; &lt;span class="nt"&gt;-pedantic&lt;/span&gt; &lt;span class="nt"&gt;-Wl&lt;/span&gt;,-rpath&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/lib64  &lt;span class="nt"&gt;-o&lt;/span&gt; build/client client.c connection.c quictls.c stream.c utils.c  &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-L&lt;/span&gt;/usr/local/lib64  &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# 影响后面的-lssl -lcrypto, 使她们使用quictls而不是openssl的共享库&lt;/span&gt;
        &lt;span class="nt"&gt;-lssl&lt;/span&gt; &lt;span class="nt"&gt;-lcrypto&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# 使用前面-L指定目录找到的依赖libssl.so.81.3 libcrypto.so.81.3&lt;/span&gt;
        &lt;span class="nt"&gt;-lngtcp2&lt;/span&gt; &lt;span class="nt"&gt;-lngtcp2_crypto_quictls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  TIPS
&lt;/h3&gt;

&lt;p&gt;ld 默认搜索的动态库路径可以通过如下途径查看：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ld &lt;span class="nt"&gt;--verbose&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;SEARCH_DIR | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s1"&gt;' ;'&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt;
&lt;span class="c"&gt;# OR&lt;/span&gt;
ldconfig &lt;span class="nt"&gt;-v&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'^/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>http3</category>
      <category>quic</category>
      <category>quictls</category>
      <category>openssl</category>
    </item>
    <item>
      <title>文件描述符和Bash中的重定向</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:21:51 +0000</pubDate>
      <link>https://forem.com/shouhua_57/wen-jian-miao-shu-fu-he-bashzhong-de-zhong-ding-xiang-2lca</link>
      <guid>https://forem.com/shouhua_57/wen-jian-miao-shu-fu-he-bashzhong-de-zhong-ding-xiang-2lca</guid>
      <description>&lt;h2&gt;
  
  
  前言
&lt;/h2&gt;

&lt;p&gt;Linux中的文件描述符(File Descritor)和Bash中的重定向(Redirections)网上资料有很多描述，各种各样的都有，有的描述文档内容，有的很深入，但是很理论，本文将理论和实践结合，使用各种例证思考Bash的重定向操作实质。&lt;/p&gt;

&lt;h2&gt;
  
  
  文件描述符
&lt;/h2&gt;

&lt;h3&gt;
  
  
  底层关系结构
&lt;/h3&gt;

&lt;p&gt;一切皆文件，文件描述符自然举足轻重，下面上一张经典图片&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxitpgpjc3as7da4fpyhc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxitpgpjc3as7da4fpyhc.png" alt="Image description" width="800" height="596"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;很清晰的描述了文件描述符底层运作。最左边一列为进程范围内的数据结构，每个进程都有一张文件描述符结构表(&lt;code&gt;struct fdtable&lt;/code&gt;)，每一栏包含&lt;code&gt;close_on_exec&lt;/code&gt;的flags和指向OFD(Open File Descriptor)的指针；第二栏(OFD)和第三栏(Inode Table)是系统级别的，其中第二栏包含打开的文件描述符的属性，状态和inode指针，第三栏包含文件的物理信息等。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;每个进程都有自己的进程表，&lt;code&gt;ls -l /dev/fd&lt;/code&gt;或者&lt;code&gt;ls -l /proc/$$/fd&lt;/code&gt;显示当前进程下打开的文件描述符，如下图所示，标准输入输出和错误都指向当前&lt;code&gt;$(tty)&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lrwx------ 1 username usergroup 64 10月 26 14:01 0 -&amp;gt; /dev/pts/0
lrwx------ 1 username usergroup 64 10月 26 14:01 1 -&amp;gt; /dev/pts/0
lrwx------ 1 username usergroup 64 10月 26 14:01 2 -&amp;gt; /dev/pts/0
lrwx------ 1 username usergroup 64 10月 26 14:01 255 -&amp;gt; /dev/pts/0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果想过滤fd，除了使用上述命令扩展下外，还可以使用比如&lt;code&gt;lsof -P -n -p $$ -a -d 0,1,2,10&lt;/code&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;查看打开的文件表描述符信息&lt;code&gt;cat /proc/$$/fdinfo/2&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pos:    0
flags:  02000002
mnt_id: 26
ino:    3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  各种操作的影响
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;dup&lt;/code&gt;操作属于process内部fd操作，根据输入fd复制新建fd，如图所示比如会在第一列&lt;code&gt;ProcessA&lt;/code&gt;中新生成一行，新旧的&lt;code&gt;file ptr&lt;/code&gt;指向OFD同一栏，比如&lt;code&gt;fd1&lt;/code&gt;和&lt;code&gt;fd20&lt;/code&gt;都指向23，所有她们具有相同的文档&lt;code&gt;offset&lt;/code&gt;等属性。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fork&lt;/code&gt;会生成子进程，子进程默认继承父进程的描述符表，比如&lt;code&gt;ProcessA&lt;/code&gt;的&lt;code&gt;fd2&lt;/code&gt;和&lt;code&gt;ProcessB&lt;/code&gt;的&lt;code&gt;fd2&lt;/code&gt;，同时指向73的OFD。&lt;/li&gt;
&lt;li&gt;不同进程中的&lt;code&gt;open&lt;/code&gt;操作会生成各自的OFD行，但是会同时指向相同的inode信息，比如&lt;code&gt;ProcessA&lt;/code&gt;中的&lt;code&gt;fd0&lt;/code&gt;和&lt;code&gt;ProcessB&lt;/code&gt;中的&lt;code&gt;fd3&lt;/code&gt;，最后都指向了inode table中的1976，所以他们具有不同的file offset信息。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面使用&lt;code&gt;dup&lt;/code&gt;举例说明，打开一个文件，&lt;code&gt;dup&lt;/code&gt;后，使用后者修改后，查看前者信息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// gcc -Wall -Wextra -pedantic -o exapmle example.c&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;fcntl.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;string.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdlib.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;errno.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F_GETFL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;O_APPEND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%d has O_APPEND&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"%d doesn't have O_APPEND attribute&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SEEK_CUR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"lseek failed: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"file offset: %ld&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"------------&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Usage: %s file_path&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
        &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;fd1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;fd2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fd1: %d, fd2: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F_GETFL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="n"&gt;O_APPEND&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;fcntl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F_SETFL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SEEK_SET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"lseek set failed: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strerror&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errno&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bash中的重定向(Redirectons)
&lt;/h2&gt;

&lt;p&gt;Bash中的重定向就是操作文件和描述符的关系，自己的理解，符号化文件操作，天才设计。一行简单的命令，文件描述符的处理在命令执行之前。&lt;/p&gt;

&lt;h3&gt;
  
  
  顺序问题
&lt;/h3&gt;

&lt;p&gt;官方文档关于重定向有个&lt;a href="https://www.gnu.org/software/bash/manual/bash.html#Redirections"&gt;命令&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dirlist 2&amp;gt;&amp;amp;1
&lt;span class="nb"&gt;ls &lt;/span&gt;2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dirlist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上述两个命令重定向先后顺序重要性，后者是不能满足1，2定向到dirlist文件要求，为什么呢，讲的很清楚，正确的前者是先执行&lt;code&gt;&amp;gt; dirlist&lt;/code&gt;，将output指向&lt;code&gt;dirlist&lt;/code&gt;文件，然后将error指向output，而此时output指向了&lt;code&gt;dirlist&lt;/code&gt;，所有1，2均指向了文件&lt;code&gt;dirlist&lt;/code&gt;。前面说到，标准的1，2，3是软链接到&lt;code&gt;tty&lt;/code&gt;的，错误的命令先将2指向1，就是说2指向了1的终极指向tty，后面再将1指向了dirlist，没有达到目的。下面咱们验证下理论：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/fd/ &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test1.txt 2&amp;gt;&amp;amp;1
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/fd/ 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test2.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat test1.txt
lrwx------ 1 username usergroup 64 10月 26 20:18 0 -&amp;gt; /dev/pts/0
l-wx------ 1 username usergroup 64 10月 26 20:18 1 -&amp;gt; /home/shouhua/test.txt
l-wx------ 1 username usergroup 64 10月 26 20:18 2 -&amp;gt; /home/shouhua/test.txt
lr-x------ 1 username usergroup 64 10月 26 20:18 3 -&amp;gt; /proc/7756/fd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat test2.txt
lrwx------ 1 username usergroup 64 10月 26 20:18 0 -&amp;gt; /dev/pts/0
l-wx------ 1 username usergroup 64 10月 26 20:18 1 -&amp;gt; /home/shouhua/test.txt
lrwx------ 1 username usergroup 64 10月 26 20:18 2 -&amp;gt; /dev/pts/0
lr-x------ 1 username usergroup 64 10月 26 20:18 3 -&amp;gt; /proc/7759/fd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意上述使用&lt;code&gt;/dev/fd/&lt;/code&gt;而不是&lt;code&gt;/proc/$$/fd/&lt;/code&gt;，前者是unix系统先出现的，后者算是部分系统支持，Bash操作的是前者，如果使用后者，则没有变化。  &lt;/p&gt;

&lt;h3&gt;
  
  
  文件offset影响
&lt;/h3&gt;

&lt;p&gt;使用文件描述符处理文件需要注意file offset(pos)，比如下面例子','加到了offset=6的地方：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo &lt;/span&gt;hello world &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.txt
&lt;span class="nb"&gt;exec &lt;/span&gt;10&amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; test.txt
&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5 &lt;span class="nt"&gt;-u&lt;/span&gt; 10
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$REPLY&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/&lt;span class="nv"&gt;$$&lt;/span&gt;/fdinfo/10 &lt;span class="c"&gt;# 查看输出的pos参数&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;10
&lt;span class="nb"&gt;cat &lt;/span&gt;test.txt
&lt;span class="nb"&gt;exec &lt;/span&gt;10&amp;gt;&amp;amp;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  redirection各种操作
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&amp;amp;&lt;/code&gt;用于分割&lt;code&gt;&amp;gt;&lt;/code&gt;和&lt;code&gt;&amp;lt;&lt;/code&gt;与fd，不然就不知道解析&lt;code&gt;1&amp;gt;2&lt;/code&gt;，为算数表达式？还是重定向？所以涉及到两端为数字时候，想到&lt;code&gt;&amp;amp;&lt;/code&gt;，有个特殊的情况，&lt;code&gt;echo hello &amp;gt;&amp;amp; test.txt&lt;/code&gt;，但是这种情况也可以这样 &lt;code&gt;echo hello &amp;amp;&amp;gt; test.txt&lt;/code&gt;。默认情况下10以上的fd可能被系统占用。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 基础文件操作&lt;/span&gt;
&lt;span class="nb"&gt;echo &lt;/span&gt;hello &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;test.txt
&lt;span class="nb"&gt;echo &lt;/span&gt;hello &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;test.txt
&lt;span class="nb"&gt;echo &lt;/span&gt;hello &amp;amp;&amp;gt;test.txt
&lt;span class="nb"&gt;cat&lt;/span&gt; &amp;lt; test.txt
&lt;span class="c"&gt;# here document&lt;/span&gt;
&lt;span class="c"&gt;# here string&lt;/span&gt;
&lt;span class="c"&gt;# duplicate file descriptor，注意下面的`&amp;amp;`，下面两个效果一样，不同的是当11没有写的时候，前者默认0，后者默认1&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;11&amp;lt;&amp;amp;10
&lt;span class="nb"&gt;exec &lt;/span&gt;11&amp;gt;&amp;amp;10
&lt;span class="c"&gt;# move file descriptor&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;11&amp;gt;&amp;amp;10- &lt;span class="c"&gt;# 复制10到11， 并且关闭10&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;11&amp;lt;&amp;amp;10-
&lt;span class="c"&gt;# open file for reading and writing&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;10&amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;test.txt
&lt;span class="c"&gt;# 系统分配fd&lt;/span&gt;
&lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;fd&lt;span class="o"&gt;}&lt;/span&gt;&amp;lt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;test.txt
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$fd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  重定向对应文件描述符各种操作
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;exec 11&amp;gt;&amp;amp;10&lt;/code&gt;复制，相当于dup，复制后，具有相同的OFD(Open File Descriptor)项，因此具有相同的offset&lt;/li&gt;
&lt;li&gt;不同的terminal同时使用同一个fd打开相同的文件，类似第三种情况，不同的OFD项，但是指向相同的inode table项&lt;/li&gt;
&lt;li&gt;使用&lt;code&gt;ls -l /dev/fd/ &amp;gt; test.txt&lt;/code&gt;时，类似上面第二种fork情况，ls会使用子进程执行，复制父进程fd table，所以能通过test.txt文件看到父进程中的fd信息&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>bash</category>
      <category>redirection</category>
      <category>descriptor</category>
    </item>
    <item>
      <title>Bash中子进程解惑</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:14:03 +0000</pubDate>
      <link>https://forem.com/shouhua_57/bashzhong-zi-jin-cheng-jie-huo-4k68</link>
      <guid>https://forem.com/shouhua_57/bashzhong-zi-jin-cheng-jie-huo-4k68</guid>
      <description>&lt;h2&gt;
  
  
  问题
&lt;/h2&gt;

&lt;p&gt;Bash使用或者读各种文档时候，提到很多情况使用子进程执行，另外有的文档说执行命令时候，直接新建子进程，然后在子进程中执行，父进程等待知道子进程执行结束，对此有些疑惑，本文使用&lt;code&gt;strace&lt;/code&gt;查看子进程和信号情况。本次使用Bash版本为&lt;code&gt;5.1.x&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  背景知识
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;新建进程和执行过程。Linux进程执行先使用&lt;code&gt;fork&lt;/code&gt;库函数(glibc)调用系统调用&lt;code&gt;clone&lt;/code&gt;新建子进程，然后在子进程中使用exec各种库函数(glibc)(比如&lt;code&gt;execl&lt;/code&gt;等)调用&lt;code&gt;execve&lt;/code&gt;系统调用执行命令。上面提到的两个系统调用&lt;code&gt;clone&lt;/code&gt;和&lt;code&gt;execve&lt;/code&gt;可以使用&lt;code&gt;strace&lt;/code&gt;查看。&lt;/li&gt;
&lt;li&gt;Bash有多种情况会使用子进程执行，比如&lt;code&gt;$(command)&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;, 外部命令等&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://man7.org/linux/man-pages/man1/strace.1.html"&gt;strace&lt;/a&gt;工具用于trace系统调用和信号，官方的man文档可以很快的掌握。本次主要使用如下命令查看：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 2763是Bash进程pid&lt;/span&gt;
&lt;span class="c"&gt;# `-f` 跟踪子进程&lt;/span&gt;
&lt;span class="c"&gt;# `-v` 查看详细参数&lt;/span&gt;
&lt;span class="c"&gt;# 如果想过滤掉signal，比如SIGCHLD，使用`-e signal=none`&lt;/span&gt;
&lt;span class="c"&gt;# 如果想过滤掉exit和attach消息，使用`-qq`或者自定义`--quiet=exit`&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;strace &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; execve &lt;span class="nt"&gt;-p&lt;/span&gt; 2763
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  实操
&lt;/h2&gt;

&lt;h3&gt;
  
  
  使用&lt;code&gt;strace&lt;/code&gt;查看子进程和信号情况
&lt;/h3&gt;

&lt;p&gt;打开两个terminal，一个用于执行Bash命令，一个执行&lt;code&gt;strace&lt;/code&gt;查看结果。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;获取前者的进程id
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt; &lt;span class="c"&gt;# 获取Bash pid，比如2763&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;在另一个terminal中输入上面所述的&lt;code&gt;strace&lt;/code&gt;命令，使用获取的pid替换&lt;/li&gt;
&lt;li&gt;在Bash终端中输入&lt;code&gt;echo hello&lt;/code&gt;, 会发现strace终端没有输出&lt;/li&gt;
&lt;li&gt;在Bash终端中输入&lt;code&gt;echo hello | xargs printf "%s\n"&lt;/code&gt;, 后者会有类似输出
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strace: Process 2763 attached
strace: Process 3016 attached
strace: Process 3017 attached
[pid  3016] +++ exited with 0 +++
[pid  3017] execve("/usr/bin/xargs", ["xargs", "printf", "%s\\n"], 0x5575977a96d0 /* 55 vars */) = 0
strace: Process 3018 attached
[pid  3018] execve("/home/shouhua/.local/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid  3018] execve("/usr/local/sbin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid  3018] execve("/usr/local/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid  3018] execve("/usr/sbin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = -1 ENOENT (No such file or directory)
[pid  3018] execve("/usr/bin/printf", ["printf", "%s\\n", "hello"], 0x7fffdf063448 /* 55 vars */) = 0
[pid  3018] +++ exited with 0 +++
[pid  3017] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3018, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid  3017] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3016, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上述输出很清晰涉及到三个进程，&lt;code&gt;pipe&lt;/code&gt;，&lt;code&gt;xargs&lt;/code&gt;和&lt;code&gt;printf&lt;/code&gt;，每个进程退出时发送&lt;code&gt;SIGCHLD&lt;/code&gt;信号&lt;/p&gt;

&lt;h3&gt;
  
  
  使用&lt;code&gt;strace&lt;/code&gt;查看执行细节
&lt;/h3&gt;

&lt;p&gt;上述输出日志中可以看到各种执行时参数，这对于调式某些shell expansion情况特别有用，比如参数是否expansion等。比如&lt;code&gt;ls *.txt&lt;/code&gt;和&lt;code&gt;ls "*.txt"&lt;/code&gt;。&lt;br&gt;
前者的输出，可以看到&lt;code&gt;*.txt&lt;/code&gt;的使用pattern match进行了filename expansion&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strace: Process 2763 attached
strace: Process 3055 attached
[pid  3055] execve("/usr/bin/ls", ["ls", "--color=auto", "plain.txt", "request.txt", "test.txt"], 0x5575977a96d0 /* 55 vars */) = 0
[pid  3055] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3055, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;后者输出，对比之下，发现double quote下的没有进行filename expansion，并且Bash报错，&lt;code&gt;'*.txt': No such file or directory&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strace: Process 2763 attached
strace: Process 3071 attached
[pid  3071] execve("/usr/bin/ls", ["ls", "--color=auto", "*.txt"], 0x5575977a96d0 /* 55 vars */) = 0
[pid  3071] +++ exited with 2 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3071, si_uid=1000, si_status=2, si_utime=0, si_stime=0} ---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;还可以通过给&lt;code&gt;strace&lt;/code&gt;添加&lt;code&gt;-v&lt;/code&gt;查看子进程继承的环境变量。当然上述也可以通过命令&lt;code&gt;set -x&lt;/code&gt;输出调试结果。&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;bash -c&lt;/code&gt;执行情况
&lt;/h3&gt;

&lt;p&gt;默认情况下会新建进程执行里面的命令，然后里面的命令如果是builtin命令在本进程执行，如果是外部命令则在子进程中执行，但是最后一个命令会在本进程执行。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strace: Process 2763 attached
strace: Process 3088 attached
[pid  3088] execve("/usr/bin/bash", ["bash", "-c", "echo hello; sleep .5; ls; hostna"...], 0x5575977a96d0 /* 55 vars */) = 0
strace: Process 3089 attached
[pid  3089] execve("/usr/bin/sleep", ["sleep", ".5"], 0x564ff0d5df60 /* 55 vars */) = 0
[pid  3089] +++ exited with 0 +++
[pid  3088] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3089, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 3090 attached
[pid  3090] execve("/usr/bin/ls", ["ls"], 0x564ff0d5df60 /* 55 vars */) = 0
[pid  3090] +++ exited with 0 +++
[pid  3088] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3090, si_uid=1000, si_status=0, si_utime=0, si_stime=1} ---
[pid  3088] execve("/usr/bin/hostname", ["hostname"], 0x564ff0d61c00 /* 55 vars */) = 0
[pid  3088] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3088, si_uid=1000, si_status=0, si_utime=0, si_stime=2} ---

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bash不同版本细节差异
&lt;/h3&gt;

&lt;p&gt;在&lt;code&gt;bash -c&lt;/code&gt;中执行的日志中发现，最后hostname命令没有新开子进程执行，而是直接在父进程&lt;code&gt;3088&lt;/code&gt;中执行，但是在Bash另外一个版本&lt;code&gt;4.4.x&lt;/code&gt;中是在子进程中执行的，可以猜测，新版本的Bash在&lt;code&gt;bash -c&lt;/code&gt;执行命令时，如果只有一个命令或者命令组中最后一个命令的情况会在当前进程中执行，不新建子进程。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以下为输出日志，注意&lt;code&gt;hostname&lt;/code&gt;那行使用进程&lt;code&gt;52098&lt;/code&gt;执行的，此时父进程为&lt;code&gt;51801&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strace: Process 51801 attached
strace: Process 52095 attached
[pid 52095] execve("/bin/bash", ["bash", "-c", "sleep .5; ls; hostname"], 0x5654ba8e1490 /* 31 vars */) = 0
strace: Process 52096 attached
[pid 52096] execve("/bin/sleep", ["sleep", ".5"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52096] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52096, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 52097 attached
[pid 52097] execve("/bin/ls", ["ls"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52097] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52097, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
strace: Process 52098 attached
[pid 52098] execve("/bin/hostname", ["hostname"], 0x55910809ad80 /* 31 vars */) = 0
[pid 52098] +++ exited with 0 +++
[pid 52095] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52098, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
[pid 52095] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=52095, si_uid=3001, si_status=0, si_utime=0, si_stime=0} ---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  调试&lt;code&gt;trap&lt;/code&gt;命令
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s2"&gt;"echo child exit"&lt;/span&gt; SIGCHLD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>bash</category>
      <category>strace</category>
      <category>process</category>
    </item>
    <item>
      <title>HTTP3之QUICTLS报错: "unable to get local issuer certificate"</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:12:57 +0000</pubDate>
      <link>https://forem.com/shouhua_57/http3zhi-quictlsbao-cuo-unable-to-get-local-issuer-certificate-14ic</link>
      <guid>https://forem.com/shouhua_57/http3zhi-quictlsbao-cuo-unable-to-get-local-issuer-certificate-14ic</guid>
      <description>&lt;h2&gt;
  
  
  2024-06-06更新
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/openssl/openssl/issues/9436"&gt;https://github.com/openssl/openssl/issues/9436&lt;/a&gt; 有人在OpenSSL仓库提出了这个问题，但是官方也没有时间整这个。&lt;/p&gt;

&lt;p&gt;看了评论后，我感觉这个问题确实不是官方的问题，当然是我的观点哈。为什么呢？因为人家仓库默认出来空的，正常，况且那么多平台，每个平台的证书库都不一样，如果随着操作系统迭代，那这个库就有点太不正经了。。。，所以最好是让各个平台自己编译成平台包的时候添加就好，比如我们使用Ubuntu等系统的时候，默认(目录位于&lt;code&gt;/usr/lib/ssl&lt;/code&gt;)会跟系统的证书库(&lt;code&gt;/etc/ssl&lt;/code&gt;)链接在一起了，这些都是在系统包维护者编译包的时候干的。  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://salsa.debian.org/debian/openssl/-/blob/debian/unstable/debian/rules#L122"&gt;https://salsa.debian.org/debian/openssl/-/blob/debian/unstable/debian/rules#L122&lt;/a&gt; 这个地址是debian编译OpenSSL使用的rules文件，其中122行明确的新建了软链接到debian平台的官方证书库(&lt;code&gt;/etc/ssl&lt;/code&gt;)。  &lt;/p&gt;

&lt;p&gt;所以如果自己编译certs文件夹是空的，正常，别慌，但是通过问题找到这个问题就已经成功了。。。 :)&lt;/p&gt;

&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;最近使用QUICTLS整HTTP3时候，将自签证书加入到系统默认cert store,然后加载默认证书路径时总是报错:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"unable to get local issuer certificate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;QUICTLS在以前文章中介绍过。总的来说，就是为了让OpenSSL支持QUIC协议，基于OpenSSL仓库修改的仓库。系统原本安装了OpenSSL，通过编译QUICTLS后也安装了OpenSSL。注意&lt;strong&gt;依赖库时要分清楚使用哪个动态库&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;本文是在使用QUICTLS动态库时导致的问题，QUICTLS依赖库默认位于&lt;code&gt;/usr/local/lib64&lt;/code&gt;，下面代码打印出证书相关默认目录。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// test.c&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;openssl/x509.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509_get_default_cert_file&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
     &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dir_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509_get_default_cert_dir&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
     &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509_get_default_cert_file_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
     &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file_env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;X509_get_default_cert_dir_env&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
     &lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"file: %s, dir: %s, file_env: %s, dir_env: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir_env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// 打印原生OpenSSL相关证书地址&lt;/span&gt;
&lt;span class="c1"&gt;// gcc -o test test.c -lcrypto &amp;amp;&amp;amp; ./test&lt;/span&gt;
&lt;span class="c1"&gt;// file: /usr/lib/ssl/cert.pem, dir: /usr/lib/ssl/certs, file_env: SSL_CERT_FILE, dir_env: SSL_CERT_DIR&lt;/span&gt;

&lt;span class="c1"&gt;// 打印QUICTLS相关证书地址&lt;/span&gt;
&lt;span class="c1"&gt;// gcc -o test test.c -L/usr/local/lib64 -lcrypto &amp;amp;&amp;amp; ./test&lt;/span&gt;
&lt;span class="c1"&gt;// file: /usr/local/ssl/cert.pem, dir: /usr/local/ssl/certs, file_env: SSL_CERT_FILE, dir_env: SSL_CERT_DIR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  根本原因
&lt;/h2&gt;

&lt;p&gt;根本原因是因为使用&lt;code&gt;quictls&lt;/code&gt;后, &lt;code&gt;OpenSSL&lt;/code&gt;使用了新的配置目录&lt;code&gt;/usr/local/ssl&lt;/code&gt;, 里面只有&lt;code&gt;certs&lt;/code&gt;文件夹, 而且里面是空的。&lt;br&gt;&lt;br&gt;
是的，怎么也没有想到是这么回事，就这个问题整了好长时间！！！&lt;br&gt;&lt;br&gt;
在进行证书验证时候, 找不到最终的根证书(找得到才怪), 就会报错: &lt;code&gt;"unable to get local issuer certificate"&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  解决办法
&lt;/h2&gt;

&lt;p&gt;解决办法是跟原生的&lt;code&gt;OpenSSL&lt;/code&gt;目录(&lt;code&gt;/usr/lib/ssl&lt;/code&gt;)里面设置的一样就好。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;QUICTLS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/local/ssl
&lt;span class="nb"&gt;sudo rmdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/certs"&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/ssl/certs &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/certs"&lt;/span&gt;

&lt;span class="nb"&gt;sudo rmdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/private"&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/ssl/private &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/private"&lt;/span&gt;

&lt;span class="nb"&gt;sudo rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/openssl.cnf"&lt;/span&gt;
&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/ssl/openssl.cnf &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUICTLS_DIR&lt;/span&gt;&lt;span class="s2"&gt;/openssl.cnf"&lt;/span&gt;

/usr/local/bin/openssl version &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="c"&gt;# QUICTLS的各种配置&lt;/span&gt;
openssl version &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="c"&gt;# 原生OpenSSL的各种地址, 特别是OPENSSLDIR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;另外，&lt;code&gt;openssl&lt;/code&gt;命令始终使用原生的, 因为原生是&lt;code&gt;/usr/bin/openssl&lt;/code&gt;, &lt;code&gt;QUICTLS&lt;/code&gt;的&lt;code&gt;openssl&lt;/code&gt;命令位于&lt;code&gt;/usr/local/bin/openssl&lt;/code&gt;。&lt;/p&gt;

</description>
      <category>http3</category>
      <category>quictls</category>
      <category>certificate</category>
    </item>
    <item>
      <title>Ubuntu热点问题</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:10:16 +0000</pubDate>
      <link>https://forem.com/shouhua_57/ubunture-dian-wen-ti-lpf</link>
      <guid>https://forem.com/shouhua_57/ubunture-dian-wen-ti-lpf</guid>
      <description>&lt;p&gt;最近从公司出来了，自己电脑重新启动，升级到Ubuntu24.04LTS, 目前基本稳定。使用过程中碰到一个需求，需要开启热点供手机联网。虽然是最新版本LTS, 其他版本应该也能使用。下面就遇到的问题整理下。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;开启热点注意点&lt;br&gt;
开启热点不能使用wifi, 所以如果要开启热点上网需要使用有线连接，以前使用MAC有类似经历，所以这个对我不是问题。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;笔记本网络正常，开启热点正常，手机能连上，但是显示“无网络连接”&lt;br&gt;
这个在网上找了半天答案，发现是由于防火墙问题，&lt;del&gt;临时打开防火墙就好，完事后开启就好&lt;/del&gt;。&lt;br&gt;
&lt;strong&gt;经过网友提醒，ufw可以不用关闭，添加相关ufw策略就好，主要是运行无线网卡数据进入，物理网卡可以路由数据，因为无线网卡数据会通过物理网卡流出。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on wlp4s0

&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status numbered
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw delete 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;连上网络后，我们希望手机流量全部通过SOCKS5流量到server端口，比如本地的1080端口。这需要借助redsocks和iptables, 前者相当于本地的SOCKS5的客户端，接受手机流量，后者将手机流量全部导入到redsocks,通过他发送到SOCKS5服务端。
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. 安装和设置redsocks&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;redsocks
&lt;span class="c"&gt;# 修改redsocks配置，主要是客户端和服务端接口&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /etc/redsocks.conf 
&lt;span class="c"&gt;# 客户端监听所有网卡&lt;/span&gt;
&lt;span class="c"&gt;# local_ip = 0.0.0.0&lt;/span&gt;
&lt;span class="c"&gt;# 服务端接口设置&lt;/span&gt;
&lt;span class="c"&gt;# port = 1080&lt;/span&gt;

&lt;span class="c"&gt;# 2. iptable设置&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-N&lt;/span&gt; REDSOCKS &lt;span class="c"&gt;# 新建链&lt;/span&gt;

&lt;span class="c"&gt;# 将无线网卡流量全部交给新链处理&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-A&lt;/span&gt; PREROUTING &lt;span class="nt"&gt;-i&lt;/span&gt; wlp4s0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-j&lt;/span&gt; REDSOCKS 

&lt;span class="c"&gt;# 在nat表中添加新链处理，所有新链流量给到redsocks设置的客户端端口&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-A&lt;/span&gt; REDSOCKS &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-j&lt;/span&gt; REDIRECT &lt;span class="nt"&gt;--to-port&lt;/span&gt; 12345

&lt;span class="c"&gt;# 相关删除操作，看着处理，最好有点iptables知识&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-L&lt;/span&gt; PREROUTING &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;--line-numbers&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-D&lt;/span&gt; PREROUTING 2 &lt;span class="c"&gt;# 数字需要看上面打印出来的行号&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-D&lt;/span&gt; REDSOCKS 1 &lt;span class="c"&gt;# 数字需要看上面打印出来的行号&lt;/span&gt;
&lt;span class="c"&gt;# OR&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-F&lt;/span&gt; REDSOCKS &lt;span class="c"&gt;# 清空自定义链&lt;/span&gt;
&lt;span class="c"&gt;# 规则全部删除完毕后，才能删除自定义链&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-X&lt;/span&gt; REDSOCKS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  自动化脚本
&lt;/h1&gt;

&lt;p&gt;暂时不贴上来了，就是上面的放在一起，另外可以使用nmcli来自动化开启和删除hotspot&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nmcli dev wifi hotspot con-name MyHotspot ssid ubuntu password 88888888
nmcli connection show | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'MyHotspot'&lt;/span&gt;
nmcli connection delete MyHotspot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>hotspot</category>
      <category>ubuntu</category>
      <category>proxy</category>
    </item>
    <item>
      <title>DNS到底怎么运作的</title>
      <dc:creator>Michael</dc:creator>
      <pubDate>Wed, 29 May 2024 07:09:07 +0000</pubDate>
      <link>https://forem.com/shouhua_57/dnsdao-di-zen-yao-yun-zuo-de-4lhc</link>
      <guid>https://forem.com/shouhua_57/dnsdao-di-zen-yao-yun-zuo-de-4lhc</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/shouhua_57/ubunture-dian-wen-ti-lpf"&gt;上文&lt;/a&gt;讲到Ubuntu中热点遇到的一些问题，涉及到了Ubuntu环境中网络管理的各个组件，总是很模糊。本文主要通过梳理DNS管理来整理Ubuntu中网络管理组件分工，以及DNS管理细节，本文不会涉及DNS协议细节。&lt;/p&gt;

&lt;h2&gt;
  
  
  环境
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;system：Ubuntu24.04
libc: glibc2.39
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Ubuntu24.04网络管理
&lt;/h2&gt;

&lt;p&gt;Linux环境中有多个网络管理组件，比如&lt;code&gt;Network Manager&lt;/code&gt;, &lt;code&gt;systemd-networkd&lt;/code&gt;等，不同组件有不同的上下文概念和配置方式，这对于网络管理和配置来说很有挑战性。后面推出了&lt;a href="https://netplan.io"&gt;netplan&lt;/a&gt;，作为网络管理抽象层，使用YAML文件配置，使用上面列到的组建作为渲染器，一份配置文件适配多种渲染器，目前支持两种渲染器：&lt;code&gt;Network Manager&lt;/code&gt;和&lt;code&gt;systemd-networkd&lt;/code&gt;。  &lt;/p&gt;

&lt;p&gt;Ubuntu上面默认使用前者作为网络管理工具，可以查看&lt;code&gt;man 5 NetworkManager.conf&lt;/code&gt;查看相关配置，配置文件位于&lt;code&gt;/etc/NetworkManager/NetworkManager.conf&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;网络有很多部分，比如无线，有线，DNS, 蓝牙等，NetworkManager使用不同的软件管理这些组件。无线部分使用&lt;code&gt;wpa_supplicant&lt;/code&gt;(见&lt;code&gt;wifi.backend&lt;/code&gt;), dhcp client端也有多种选择，默认使用internal。&lt;/p&gt;

&lt;p&gt;其中DNS配置很有意思，根据&lt;code&gt;manpage&lt;/code&gt;文档，DNS管理使用&lt;code&gt;systemd-resolved&lt;/code&gt;(&lt;code&gt;/etc/resolv.conf&lt;/code&gt;文件是软连接至&lt;code&gt;/run/systemd/resolve/stub-resolv.conf&lt;/code&gt;), 也可以设置使用&lt;code&gt;dnsmasq&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;
  
  
  Ubuntu24.04 DNS管理
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.cloudflare.com/learning/dns/what-is-dns/"&gt;DNS基础知识 Cloudflare&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="http://www.ruanyifeng.com/blog/2016/06/dns.html"&gt;DNS基础知识 ruanyifeng&lt;/a&gt;&lt;br&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;resolvectl&lt;/code&gt;作为&lt;code&gt;systemd-resolved&lt;/code&gt;的命令行管理工具管理包括DNS等。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;systemd-resolved&lt;/code&gt;会在本地53端口起个DNS服务，并且将&lt;code&gt;nameserver 127.0.0.53&lt;/code&gt;写入&lt;code&gt;/etc/resolv.conf&lt;/code&gt;文件，那其他软件DNS查找这个文件，都是访问本地53端口服务(这个成为&lt;code&gt;stub resolver&lt;/code&gt;)，但是真正的DNS服务server会藏在&lt;code&gt;systemd-resolved&lt;/code&gt;的相关配置文件中，可以使用如下命令查看：&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;resolvectl status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;它会列出所有网络设备的resolve信息，比如无线网卡的信息&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    Link 3 (wlp4s0)
        Current Scopes: DNS
             Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
    Current DNS Server: 192.168.0.1
           DNS Servers: 192.168.0.1
            DNS Domain: lan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上述的&lt;code&gt;192.168.0.1&lt;/code&gt;作为DNS server一般是&lt;code&gt;DHCP&lt;/code&gt;分配的，局域网内部的DNS server, 会去请求ISP的DNS server(DNS的&lt;strong&gt;迭代模式(&lt;code&gt;Iterate&lt;/code&gt;)&lt;/strong&gt;), 最后会进入&lt;strong&gt;递归模式&lt;/strong&gt;，不断从&lt;code&gt;ROOT(.)&lt;/code&gt;, &lt;code&gt;TLD nameserver(Top Level Domain)(.com)&lt;/code&gt;, &lt;code&gt;Authoritative nameserver(baidu.com)&lt;/code&gt;获取DNS信息，返回信息有各种所谓的&lt;code&gt;Record&lt;/code&gt;(上述是全流程，当然可能存在缓存就不用走这些流程了，不然每次请求都消耗很多时间，另外服务器负载也太大了), 下面是常见的&lt;a href="https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/"&gt;Record&lt;/a&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    A - IPV4
    AAAA - IPV6
    NS - Authoritative Name Server
    CNAME - 别名，比如www.baidu.com -&amp;gt; www.shifen.com
    MX - Mail
    TXT - Human readable document
    PTR - reverse DNS lookup, 由ip查域名
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  查看systemd-resolved的DNS缓存
&lt;/h3&gt;

&lt;p&gt;使用&lt;code&gt;sudo resolvectl statistic&lt;/code&gt;可以查看缓存情况，比如缓存个数，命中个数等。如果想查看缓存内容，使用&lt;code&gt;sudo pkill -USR1 systemd-resolve&lt;/code&gt;, 然后在&lt;code&gt;syslog&lt;/code&gt;中查看 &lt;code&gt;sudo journalctl -u systemd-resolved &amp;gt; ~/resolved.txt&lt;/code&gt;。&lt;strong&gt;注意上面打印的数据是unique的数据，记录中可能有多个相同域名指向多个IP&lt;/strong&gt;。&lt;/p&gt;

&lt;h3&gt;
  
  
  清空systemd-resolved的DNS缓存
&lt;/h3&gt;

&lt;p&gt;清空所有cache&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pkill &lt;span class="nt"&gt;-USR2&lt;/span&gt; systemd-resolve
&lt;span class="c"&gt;# OR&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;resolvectl flush-caches
&lt;span class="nb"&gt;sudo &lt;/span&gt;resolvectl statistic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  glibc的DNS服务nsswitch和ncsd
&lt;/h3&gt;

&lt;p&gt;为什么提到了glibc的DNS服务呢？因为在程序中使用比如函数&lt;code&gt;getaddrinfo&lt;/code&gt;进行域名解析时候就使用了glibc提供的域名解析服务。下面梳理glibc的DNS相关流程。&lt;br&gt;&lt;br&gt;
glibc提供了各类名称解析服务，比如用户名-&amp;gt;&lt;code&gt;USER ID&lt;/code&gt;, 组名-&amp;gt;&lt;code&gt;GROUP ID&lt;/code&gt;, 域名-&amp;gt;&lt;code&gt;IP&lt;/code&gt;等，其中使用的模块是&lt;code&gt;NSS(Name Service Switch)&lt;/code&gt;, &lt;strong&gt;这个不要跟Firefox的NSS(Network Security Services)模块弄混淆了&lt;/strong&gt;。  &lt;/p&gt;
&lt;h4&gt;
  
  
  NSSwitch
&lt;/h4&gt;

&lt;p&gt;可以使用&lt;code&gt;getent database key&lt;/code&gt;从数据库中获取NSS支持类型的存储数据, 比如&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;getent ahosts &lt;span class="c"&gt;# get A record host&lt;/span&gt;
getent group &lt;span class="c"&gt;# get group resolve record&lt;/span&gt;
man 5 nss
man 5 nsswitch.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;getent ahost&lt;/code&gt;先使用缓存没有就使用getaddrinfo开始网络请求，详情见&lt;code&gt;man getent&lt;/code&gt;。&lt;br&gt;&lt;br&gt;
&lt;code&gt;nsswitch&lt;/code&gt;的配置文件见&lt;code&gt;/etc/nsswitch.conf&lt;/code&gt;，我们关心DNS相关的是&lt;code&gt;hosts&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hosts:          files mdns4_minimal &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;NOTFOUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; dns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中&lt;code&gt;hosts&lt;/code&gt;值规定使用&lt;code&gt;DNS&lt;/code&gt;查询的顺序，&lt;code&gt;files&lt;/code&gt;代表&lt;code&gt;/etc/hosts&lt;/code&gt;; &lt;code&gt;mdns4\_minimal&lt;/code&gt;使用系统提供的&lt;code&gt;multicast DNS&lt;/code&gt;解析服务，如果有这个服务，调用成功但是没有我们要的结果，就返回，如果没有这个服务，就使用系统提供的DNS服务查询(&lt;code&gt;/etc/resolv.conf&lt;/code&gt;)。&lt;br&gt;
使用&lt;code&gt;nsswitch&lt;/code&gt;查询&lt;code&gt;DNS&lt;/code&gt;整个过程，可以通过&lt;code&gt;strace&lt;/code&gt;查看到上述描述细节&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;strace &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;open,openat,connect &lt;span class="nt"&gt;-f&lt;/span&gt; ping &lt;span class="nt"&gt;-c1&lt;/span&gt; www.baidu.com
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/ld.so.cache"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/lib/x86_64-linux-gnu/libcap.so.2"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/lib/x86_64-linux-gnu/libidn2.so.0"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/lib/x86_64-linux-gnu/libc.so.6"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/lib/x86_64-linux-gnu/libunistring.so.5"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/usr/lib/locale/locale-archive"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 3
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
&lt;span class="c"&gt;# nscd缓存&lt;/span&gt;
     connect&lt;span class="o"&gt;(&lt;/span&gt;5, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;sa_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AF_UNIX, &lt;span class="nv"&gt;sun_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/run/nscd/socket"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, 110&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; ENOENT &lt;span class="o"&gt;(&lt;/span&gt;没有那个文件或目录&lt;span class="o"&gt;)&lt;/span&gt;
     connect&lt;span class="o"&gt;(&lt;/span&gt;5, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;sa_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AF_UNIX, &lt;span class="nv"&gt;sun_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var/run/nscd/socket"&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;, 110&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; ENOENT &lt;span class="o"&gt;(&lt;/span&gt;没有那个文件或目录&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# 使用了getaddrinfo, 所以会调用nsswitch查询dns&lt;/span&gt;
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/nsswitch.conf"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
&lt;span class="c"&gt;# hosts中配置files第一顺序&lt;/span&gt;
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/host.conf"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/resolv.conf"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/hosts"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/ld.so.cache"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
&lt;span class="c"&gt;# 使用multicast DNS本地局域网所有example.local机器咨询DNS, 没有这个机器服务，下一个dns系统查询&lt;/span&gt;
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
&lt;span class="c"&gt;# hosts配置中的dns, 使用系统提供的dns服务, 即systemd-resolved&lt;/span&gt;
     connect&lt;span class="o"&gt;(&lt;/span&gt;5, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;sa_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AF_INET, &lt;span class="nv"&gt;sin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;htons&lt;span class="o"&gt;(&lt;/span&gt;53&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="nv"&gt;sin_addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inet_addr&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.53"&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;, 16&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/gai.conf"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
     ...
     connect&lt;span class="o"&gt;(&lt;/span&gt;5, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;sa_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AF_INET, &lt;span class="nv"&gt;sin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;htons&lt;span class="o"&gt;(&lt;/span&gt;1025&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="nv"&gt;sin_addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inet_addr&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"183.2.172.42"&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;, 16&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
     PING www.a.shifen.com &lt;span class="o"&gt;(&lt;/span&gt;183.2.172.42&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
     openat&lt;span class="o"&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class="s2"&gt;"/etc/hosts"&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 5
     connect&lt;span class="o"&gt;(&lt;/span&gt;5, &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;sa_family&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AF_INET, &lt;span class="nv"&gt;sin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;htons&lt;span class="o"&gt;(&lt;/span&gt;53&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="nv"&gt;sin_addr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;inet_addr&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.53"&lt;/span&gt;&lt;span class="o"&gt;)}&lt;/span&gt;, 16&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 0
     64 bytes from 183.2.172.42: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;51 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;25.3 ms

     &lt;span class="nt"&gt;---&lt;/span&gt; www.a.shifen.com ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
     1 packets transmitted, 1 received, 0% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;0ms
     rtt min/avg/max/mdev &lt;span class="o"&gt;=&lt;/span&gt; 25.305/25.305/25.305/0.000 ms
     +++ exited with 0 +++
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;其中可以修改&lt;code&gt;/etc/nsswitch.conf&lt;/code&gt;中的&lt;code&gt;hosts: file dns&lt;/code&gt;就不会有&lt;code&gt;multicast DNS&lt;/code&gt;请求了, &lt;strong&gt;修改是自动刷新的&lt;/strong&gt;。&lt;br&gt;&lt;br&gt;
&lt;strong&gt;NOTICE：上述&lt;code&gt;strace&lt;/code&gt;监听openat系统调用，open可能已经没有用了&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  ncsd
&lt;/h4&gt;

&lt;p&gt;其中&lt;code&gt;nscd(Name Service Cache Daemon)&lt;/code&gt;是全局各种解析服务的缓存，数据库中存储了各种解析类型映射的缓存。&lt;a href="https://man7.org/linux/man-pages/man8/nscd.8.html"&gt;nscd manpage&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;
网上看到这篇&lt;a href="https://cloud.tencent.com/developer/article/2242697"&gt;文章&lt;/a&gt;总结的不错。&lt;br&gt;
注意，/etc/nscd.conf文件注释必须是首个字符为#，不然会服务可能启动失败。&lt;br&gt;
主要提下调试，有两种方式，一种是使用日志文件，一种是在终端打印日志查看&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;使用日志文件
修改&lt;code&gt;/etc/nscd.conf&lt;/code&gt;中日志相关项, &lt;code&gt;logfile(/var/log/nscd.log)&lt;/code&gt;和&lt;code&gt;debug-file(0-5, 可以选择5)&lt;/code&gt;, 然后重启nscd, 就可以查看(&lt;code&gt;tail -f /var/log/nscd.log&lt;/code&gt;), 当&lt;code&gt;ping&lt;/code&gt;或者&lt;code&gt;getent hosts&lt;/code&gt;就会更新日志。&lt;/li&gt;
&lt;li&gt;终端查看
先暂停&lt;code&gt;sudo systemctl stop nscd&lt;/code&gt;, 然后&lt;code&gt;sudo nscd -g&lt;/code&gt;, 就会在终端打印日志了，但是也要设置日志等级。&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  总结
&lt;/h4&gt;

&lt;p&gt;使用&lt;code&gt;getent&lt;/code&gt;, &lt;code&gt;ping&lt;/code&gt;这些命令都会走glibc提供的DNS服务，底层使用&lt;code&gt;getaddrinfo&lt;/code&gt;函数; 其他命令如&lt;code&gt;host(man 1 host)&lt;/code&gt;, &lt;code&gt;dig&lt;/code&gt;, &lt;code&gt;nslookup&lt;/code&gt;走的是全局DNS解析服务，但是也可以自己配置DNS服务器直接网络请求。查看manpage, 会发现都是&lt;code&gt;BIND 9&lt;/code&gt;提供的命令，在找&lt;code&gt;BIND 9&lt;/code&gt;你就会发现他们为什么不走glibc的了 :)&lt;/p&gt;

&lt;h2&gt;
  
  
  编程语言DNS服务
&lt;/h2&gt;

&lt;p&gt;目前有很多库提供DNS服务，比如&lt;a href="http://www.wangafu.net/~nickm/libevent-book/Ref9_dns.html"&gt;libevent&lt;/a&gt;, &lt;a href="https://github.com/c-ares/c-ares"&gt;c-ares&lt;/a&gt;。&lt;br&gt;&lt;br&gt;
很多&lt;a href="https://juejin.cn/post/7264949719276437538"&gt;语言&lt;/a&gt;如果底层使用使用glibc的函数也是走NSS那套，比如python，java等，但是也有的语言提供独立的DNS服务，比如自举后的golang。&lt;br&gt;&lt;br&gt;
NodeJS底层使用&lt;code&gt;c-ares&lt;/code&gt;提供DNS服务。&lt;br&gt;
libcurl可以使用自己提供DNS server,但是需要编译时候添加c-ares库; 默认走glibc那套。&lt;/p&gt;

&lt;h3&gt;
  
  
  Nodejs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://nodejs.org/docs/latest/api/dns.html#implementation-considerations"&gt;Nodejs文档&lt;/a&gt;真心不错，最后说明了两种方式的特点和注意点。&lt;br&gt;
默认情况(使用dns.lookup())，使用getaddrinfo走系统那套查询方式; 也可以使用 &lt;code&gt;dns.resolve()&lt;/code&gt;, &lt;code&gt;dns.resolve*()&lt;/code&gt;, and &lt;code&gt;dns.reverse()&lt;/code&gt;，底层使用c-ares,直接网络请求DNS服务器获取解析结果。&lt;/p&gt;

&lt;h2&gt;
  
  
  小结
&lt;/h2&gt;

&lt;p&gt;又整理了一篇无用知识，发现&lt;code&gt;strace&lt;/code&gt;真是个好工具，解决问题都要想到她。&lt;/p&gt;

</description>
      <category>dns</category>
      <category>ubuntu</category>
      <category>resolve</category>
    </item>
  </channel>
</rss>
