<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://lantian.pub</id>
    <title>Lan Tian @ Blog</title>
    <updated>2026-05-03T09:10:22.768Z</updated>
    <generator>Astro v6.2.1</generator>
    <author>
        <name>Lan Tian</name>
        <uri>https://lantian.pub</uri>
    </author>
    <link rel="alternate" href="https://lantian.pub"/>
    <link rel="self" href="https://lantian.pub/feed.xml"/>
    <subtitle>Lan Tian @ Blog</subtitle>
    <icon>https://lantian.pub/favicon.ico</icon>
    <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    <entry>
        <title type="html"><![CDATA[Modifying FileZilla to Workaround Bambu 3D Printer's FTP Issue]]></title>
        <id>https://lantian.pub/en/article/modify-computer/modify-filezilla-workaround-bambu-3d-printer-ftp-issue.lantian/</id>
        <link href="https://lantian.pub/en/article/modify-computer/modify-filezilla-workaround-bambu-3d-printer-ftp-issue.lantian/"/>
        <updated>2026-04-13T23:28:02.000Z</updated>
        <content type="html"><![CDATA[<p>I recently bought a Bambu A1 Mini 3D printer to try out 3D printing. This printer offers a FTP server, allowing users to use FTP clients like FileZilla or WinSCP to upload model files for printing, and download timelapse videos.</p>
<p>However, when I tried connecting to the printer with FileZilla, I found that although the username and password were correct and login was successful, I couldn't retrieve the file list:</p>
<p><picture><source srcset="/usr/uploads/202604/filezilla-error.png.webp" type="image/webp"><source srcset="/usr/uploads/202604/filezilla-error.png.avif" type="image/avif"><source srcset="/usr/uploads/202604/filezilla-error.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202604/filezilla-error.png" alt="FileZilla error when retrieving file list - connection refused"></picture></p>
<p>Some users on the Bambu official forum have also reported this issue, such as <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/7" rel="noopener noreferrer" target="_blank">this reply</a> and <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/15" rel="noopener noreferrer" target="_blank">this reply</a>.</p>
<p>Some users mentioned that <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/137" rel="noopener noreferrer" target="_blank">WinSCP works</a>, but I use Linux daily and don't want to switch to Windows just to connect to the printer's FTP service. So I investigated the cause of the problem and found a solution for Linux.</p>
<h1 id="introduction-to-ftp-protocol">Introduction to FTP Protocol</h1>
<p>To understand this problem, we first need to understand how the FTP protocol works. FTP (File Transfer Protocol) is an ancient file transfer protocol, born in 1971. It uses multiple TCP connections to separate control commands and data transfer:</p>
<ul>
<li>Control connection: The client actively connects to the server (usually on port 21), establishing a persistent TCP connection. All commands (such as login, change directory, list files) and server responses are transmitted through this connection.</li>
<li>Data connection: Whenever file content needs to be transferred or file lists need to be retrieved, the client and server establish a new TCP connection. After the transfer is complete, this connection is closed.</li>
</ul>
<p>Based on how the data connection is established, FTP can be divided into Active Mode and Passive Mode:</p>
<h2 id="active-mode">Active Mode</h2>
<ol>
<li>The client sends a <code>PORT</code> command on the control connection, telling the server the IP and port it's listening on.</li>
<li>The server actively connects from port 20 to the IP and port specified by the client.</li>
<li>After data transfer is complete, the connection is closed.</li>
</ol>
<p>The format of the <code>PORT</code> command is <code>PORT h1,h2,h3,h4,p1,p2</code>, where <code>h1-h4</code> are the four bytes of the IP address, and <code>p1-p2</code> form the port number (<code>p1*256+p2</code>). For example:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">PORT</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 192,168,1,100,4,1</span></span></code></pre>
<p>This means the client is waiting for a connection at <code>192.168.1.100:1025</code> (4*256+1=1025).</p>
<p>The problem with active mode is that if the client is behind NAT or a firewall, the server cannot actively connect to the client. Therefore, modern FTP clients use passive mode by default. Bambu 3D printers also don't support active mode - attempting to use the <code>PORT</code> command will simply return an error.</p>
<h2 id="passive-mode">Passive Mode</h2>
<ol>
<li>The client sends a <code>PASV</code> command on the control connection.</li>
<li>The server responds with a <code>227</code> status code, telling the client the IP and port it's listening on.</li>
<li>The client actively connects to the IP and port specified by the server.</li>
<li>After data transfer is complete, the connection is closed.</li>
</ol>
<p>The format of the <code>PASV</code> response is the same as the <code>PORT</code> command, for example:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">227</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (192,168,1,1,7,232)</span></span></code></pre>
<p>This means the server is listening for a connection at <code>192.168.1.1:2024</code> (7*256+232=2024).</p>
<p>Passive mode solves the problem of clients being behind NAT, since the connection is initiated by the client. However, if the IP address returned by the server is incorrect (for example, returning a private IP or invalid IP), the client won't be able to establish a data connection.</p>
<h1 id="bambu-printer-firmware-issue">Bambu Printer Firmware Issue</h1>
<p>If we take another look of FileZilla's output, we can find that Bambu's FTP server returned some weird response for the PASV command:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">> PASV</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C; 227 (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">0,0,0,0,7,232</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span></code></pre>
<p>The first four segments of the return value are all 0, corresponding to the IP address <code>0.0.0.0</code>, meaning Bambu's FTP server instructs the client to connect to this IP address instead of the printer's actual IP address.</p>
<p><code>0.0.0.0</code> is a special IP address, typically used to represent "all IP addresses on this machine". According to <a href="https://datatracker.ietf.org/doc/html/rfc1122" rel="noopener noreferrer" target="_blank">RFC 1122</a>, <code>0.0.0.0</code> as a destination address is invalid, and can only be used as a special source address.</p>
<p>Different operating systems behave differently when connecting to <code>0.0.0.0</code>:</p>
<ul>
<li>On Windows, connecting to <code>0.0.0.0</code> will fail, returning a <code>WSAEADDRNOTAVAIL</code> error ("The remote address is not a valid address").</li>
<li>On macOS and Linux, connections to <code>0.0.0.0</code> are automatically redirected to the local machine, equivalent to <code>127.0.0.1</code>.</li>
</ul>
<p>Therefore, regardless of the operating system, when an FTP client receives <code>0.0.0.0</code> in a PASV response, it cannot correctly connect to the actual FTP server. In <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/7" rel="noopener noreferrer" target="_blank">this reply on the Bambu forum</a>, the user was using Windows and got the <code>WSAEADDRNOTAVAIL</code> error. But since I'm using Linux, the error returned was <code>ECONNREFUSED</code> (connection refused), because there's no FTP server on my local computer and no corresponding port is open.</p>
<p>On Windows, you can use WinSCP as an FTP client, and per <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/137" rel="noopener noreferrer" target="_blank">this comment</a> enable the <code>Force IP address for passive connections</code> setting in the options, which essentially ignores the IP address portion returned by the FTP server in the PASV command and only uses the port number.</p>
<p>This feature was designed to support some misconfigured FTP servers that return their private IP (e.g., 192.168.1.1) instead of their public IP in the PASV command. But coincidentally, it also solves the problem in Bambu's case.</p>
<p>However, as a Linux user, I don't have WinSCP available, so I have to figure out how to modify FileZilla.</p>
<h1 id="modifying-filezilla">Modifying FileZilla</h1>
<p>FileZilla also has special handling logic for these misconfigured FTP servers. In the settings under Connection - FTP - Passive tab, you can configure what to do when the FTP server returns a private IP: either force using the server's public IP or switch to active mode.</p>
<p>This logic is implemented in the <code>CFtpRawTransferOpData::ParsePasvResponse()</code> function in <code>src/engine/ftp/rawtransfer.cpp</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">bool</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> CFtpRawTransferOpData</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">ParsePasvResponse</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Omitted code for parsing PASV response content</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // The CFtpRawTransferOpData class defines a host_ variable that stores the IP address returned by the PASV command</span></span>
<span class="line"><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">  std</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::wstring host_;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // peerIP is the server IP address used when FileZilla actively connects to the FTP server</span></span>
<span class="line"><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">  std</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::wstring </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> peerIP = </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">to_wstring</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">controlSocket_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">socket_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">-></span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">peer_ip</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">());</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // The is_routable_address function is located in the libfilezilla library's lib/iputils.cpp file,</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // determining whether an IP address is a public IP (true) or a private IP (false).</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Its logic is: if the IP is in 10.0.0.0/8, 127.0.0.0/8, 192.168.0.0/16,</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 169.254.0.0/16, 172.16.0.0/12, it returns private IP, otherwise public IP.</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Note that it judges 0.0.0.0 as a public IP.</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  //</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // The logic here is: if the FTP server's IP is a public IP, but PASV returns a private IP, then enter special handling logic.</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // Special handling is only applied to public FTP servers because private FTP servers might intentionally return a different IP,</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // for load balancing at the network layer, or to use a second IP when the first IP's 65535 ports are exhausted.</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (!</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">is_routable_address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(host_) &#x26;&#x26; </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">is_routable_address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(peerIP)) {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // If the setting to force using the server's public IP is enabled, use the server IP instead of the PASV-returned IP</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">options_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">get_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(OPTION_PASVREPLYFALLBACKMODE) != </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> || bTriedActive) {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::status, </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Server sent passive reply with unroutable address. Using server address instead."</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">));</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::debug_info, </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">L"  Reply: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">, peer: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, host_, peerIP);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      host_ = peerIP;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // Otherwise, return FTP passive mode failed, and FileZilla will switch to active mode and retry</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    else</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::status, </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Server sent passive reply with unroutable address. Passive mode failed."</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">));</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::debug_info, </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">L"  Reply: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">, peer: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, host_, peerIP);</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">      return</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> false</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // This mode is hidden in the settings interface, users cannot switch to this mode</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">options_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">get_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(OPTION_PASVREPLYFALLBACKMODE) == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // Force using the IP when actively connecting to the FTP server regardless of any situation</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    host_ = peerIP;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>As you can see, FileZilla doesn't treat <code>0.0.0.0</code> as a private IP, causing this logic to not work for Bambu's FTP server. The solution is to modify FileZilla's source code to add special handling for the <code>0.0.0.0</code> IP. Since <code>0.0.0.0</code> is an invalid IP, we can always use the special logic, regardless of whether the server is on a public or private network:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Index: src/engine/ftp/rawtransfer.cpp</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">===================================================================</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#000080">--- a/src/engine/ftp/rawtransfer.cpp  (revision 11406)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#000080">+++ b/src/engine/ftp/rawtransfer.cpp  (working copy)</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">@@ -399,7 +399,11 @@</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">   }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> </span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">   std::wstring const peerIP = fz::to_wstring(controlSocket_.socket_->peer_ip());</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">-  if (!fz::is_routable_address(host_) &#x26;&#x26; fz::is_routable_address(peerIP)) {</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  std::wstring const zeroIP = fz::to_wstring(std::string("0.0.0.0"));</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  if (</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+    std::wcscmp(host_.c_str(), zeroIP.c_str()) == 0</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+    || (!fz::is_routable_address(host_) &#x26;&#x26; fz::is_routable_address(peerIP))</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  ) {</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">     if (options_.get_int(OPTION_PASVREPLYFALLBACKMODE) != 1 || bTriedActive) {</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">       log(logmsg::status, _("Server sent passive reply with unroutable address. Using server address instead."));</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">       log(logmsg::debug_info, L"  Reply: %s, peer: %s", host_, peerIP);</span></span></code></pre>
<p>(Due to my blog system breaking the tab indentation format, the original patch file can be obtained from this link: <a href="https://github.com/xddxdd/nixos-config/blob/7a6abe1a61f84c430c92f3d97eb7be0c45da21d0/patches/filezilla-override-pasv-ip-for-zero-ip.patch" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/7a6abe1a61f84c430c92f3d97eb7be0c45da21d0/patches/filezilla-override-pasv-ip-for-zero-ip.patch</a>)</p>
<p>After applying the above patch, recompile and install FileZilla, then try connecting to the printer again:</p>
<p><picture><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.webp" type="image/webp"><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.avif" type="image/avif"><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202604/filezilla-success-after-fix.png" alt="Modified FileZilla can successfully retrieve file list"></picture></p>
<p>Now you can normally access the printer's FTP service to upload and download files.</p>
<h1 id="appendix-ftp-configuration-for-connecting-to-bambu-3d-printer">Appendix: FTP Configuration for Connecting to Bambu 3D Printer</h1>
<ul>
<li>Host: <code>ftps://192.168.12.34</code>, replace with your printer's IP address</li>
<li>Username: <code>bblp</code></li>
<li>Password: Can be found in the printer's settings - LAN page, it's an 8-digit access code. <strong>Note: You don't need to enable LAN mode to use FTP, enabling LAN mode will cause Bambu cloud features to stop working!</strong></li>
<li>Port: <code>990</code></li>
<li>In FileZilla, you may need to select <code>Require implicit FTP over TLS</code> in the Encryption field.</li>
</ul>]]></content>
        <published>2026-04-13T23:28:02.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[魔改 FileZilla 解决拓竹 3D 打印机的 FTP 问题]]></title>
        <id>https://lantian.pub/article/modify-computer/modify-filezilla-workaround-bambu-3d-printer-ftp-issue.lantian/</id>
        <link href="https://lantian.pub/article/modify-computer/modify-filezilla-workaround-bambu-3d-printer-ftp-issue.lantian/"/>
        <updated>2026-04-13T23:28:02.000Z</updated>
        <content type="html"><![CDATA[<p>我最近为了尝试 3D 打印，买了一台拓竹 A1 Mini 3D 打印机。这台打印机支持 FTP 连接，用户可以使用 FileZilla、WinSCP 等 FTP 客户端上传需要打印的模型文件，以及下载延时摄影录像。</p>
<p>但是我尝试用 FileZilla 连接打印机时，却发现虽然用户名密码都正确，可以完成登录，但无法获取到文件列表：</p>
<p><picture><source srcset="/usr/uploads/202604/filezilla-error.png.webp" type="image/webp"><source srcset="/usr/uploads/202604/filezilla-error.png.avif" type="image/avif"><source srcset="/usr/uploads/202604/filezilla-error.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202604/filezilla-error.png" alt="FileZilla 获取文件列表时报错连接被拒绝"></picture></p>
<p>拓竹官方论坛上也有一些用户报告了这个问题，例如<a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/7" rel="noopener noreferrer" target="_blank">这个回复</a>，以及<a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/15" rel="noopener noreferrer" target="_blank">这个回复</a>。</p>
<p>有用户提到了 <a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/137" rel="noopener noreferrer" target="_blank">WinSCP 可以用</a>，但我日常使用 Linux，不想专门为了连接打印机的 FTP 服务就切换到 Windows，就研究了一下问题的原因，以及 Linux 下的解决办法。</p>
<h1 id="ftp-协议简介">FTP 协议简介</h1>
<p>要理解这个问题，首先要了解 FTP 协议是如何工作的。FTP（File Transfer Protocol）是一种古老的文件传输协议，诞生于 1971 年。它使用多条 TCP 连接来分离控制命令和数据传输：</p>
<ul>
<li>控制连接：客户端主动连接服务器（一般是 21 端口），建立一条持久的 TCP 连接。所有命令（如登录、切换目录、列出文件）和服务器响应都通过这条连接传输。</li>
<li>数据连接：每当需要传输文件内容或获取文件列表时，客户端和服务器会建立一条新的 TCP 连接。传输完成后，这条连接会被关闭。</li>
</ul>
<p>根据数据连接的建立方式，FTP 分为主动模式（Active Mode）和被动模式（Passive Mode）：</p>
<h2 id="主动模式">主动模式</h2>
<ol>
<li>客户端在控制连接上发送 <code>PORT</code> 命令，告知服务器自己监听的 IP 和端口。</li>
<li>服务器主动从 20 端口连接客户端指定的 IP 和端口。</li>
<li>数据传输完成后，连接关闭。</li>
</ol>
<p><code>PORT</code> 命令的格式为 <code>PORT h1,h2,h3,h4,p1,p2</code>，其中 <code>h1-h4</code> 是 IP 地址的四个字节，<code>p1-p2</code> 组成端口号（<code>p1*256+p2</code>）。例如：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">PORT</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 192,168,1,100,4,1</span></span></code></pre>
<p>表示客户端在 <code>192.168.1.100:1025</code>（4*256+1=1025）端口等待连接。</p>
<p>主动模式的问题是：如果客户端位于 NAT 或防火墙后面，服务器无法主动连接客户端。因此现代 FTP 客户端默认使用被动模式。拓竹 3D 打印机也不支持主动模式，尝试使用 <code>PORT</code> 命令时会直接返回错误。</p>
<h2 id="被动模式">被动模式</h2>
<ol>
<li>客户端在控制连接上发送 <code>PASV</code> 命令。</li>
<li>服务器响应 <code>227</code> 状态码，告知自己监听的 IP 和端口。</li>
<li>客户端主动连接服务器指定的 IP 和端口。</li>
<li>数据传输完成后，连接关闭。</li>
</ol>
<p><code>PASV</code> 响应的格式与 <code>PORT</code> 命令相同，例如：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">227</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (192,168,1,1,7,232)</span></span></code></pre>
<p>表示服务器在 <code>192.168.1.1:2024</code>（7*256+232=2024）端口等待连接。</p>
<p>被动模式解决了客户端位于 NAT 后面的问题，因为连接由客户端主动发起。但如果服务器返回的 IP 地址不正确（例如返回内网 IP 或无效 IP），客户端将无法建立数据连接。</p>
<h1 id="拓竹打印机的固件问题">拓竹打印机的固件问题</h1>
<p>如果我们仔细阅读 FileZilla 的输出，可以发现拓竹的 FTP 服务器在 PASV 命令中的返回值有点奇怪：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">> PASV</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">&#x3C; 227 (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">0,0,0,0,7,232</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span></code></pre>
<p>返回值的前四段都是 0，对应的 IP 地址是 <code>0.0.0.0</code>，意味着拓竹的 FTP 服务器指示客户端连接到这个 IP 地址，而非打印机本身的 IP 地址。</p>
<p><code>0.0.0.0</code> 是一个特殊的 IP 地址，通常用于表示"本机上的所有 IP 地址"。根据 <a href="https://datatracker.ietf.org/doc/html/rfc1122" rel="noopener noreferrer" target="_blank">RFC 1122</a>，<code>0.0.0.0</code> 作为目标地址是无效的，只能作为一个特殊的源地址使用。</p>
<p>不同操作系统对连接 <code>0.0.0.0</code> 的行为有所不同：</p>
<ul>
<li>在 Windows 上，连接 <code>0.0.0.0</code> 会失败，返回 <code>WSAEADDRNOTAVAIL</code> 错误（"The remote address is not a valid address"）。</li>
<li>在 macOS 和 Linux 上，到 <code>0.0.0.0</code> 会被自动重定向到本机，相当于 <code>127.0.0.1</code>。</li>
</ul>
<p>因此，无论在哪个操作系统上，FTP 客户端收到 <code>0.0.0.0</code> 作为 PASV 响应时都无法正确连接到实际的 FTP 服务器。在<a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/7" rel="noopener noreferrer" target="_blank">拓竹论坛上的这个回复</a>中，这名用户使用的是 Windows 系统，报错就是 <code>WSAEADDRNOTAVAIL</code>；而我使用的是 Linux，返回的错误就是 <code>ECONNREFUSED</code> 连接被拒绝，因为我的本地电脑上没有 FTP 服务器，没有开放对应的端口。</p>
<p>如果在 Windows 上，可以用 WinSCP 作为 FTP 客户端，并且可以参照<a href="https://forum.bambulab.com/t/we-can-now-connect-to-ftp-on-the-p1-and-a1-series/6464/137" rel="noopener noreferrer" target="_blank">这个回复</a>，开启设置中的 <code>Force IP address for passive connections</code>（对于被动模式连接强制使用 IP 地址），实际上就是忽略 FTP 服务器在 PASV 命令中返回的 IP 地址部分，只使用端口号。</p>
<p>这个功能是为了支持一些配置错误的 FTP 服务器，在 PASV 命令时返回它们的内网 IP（例如 192.168.1.1）而非公网 IP。但是阴差阳错地也解决了拓竹打印机的问题。</p>
<p>但是我是 Linux 用户，没有 WinSCP 可用，因此只能看看怎么魔改 FileZilla。</p>
<h1 id="魔改-filezilla">魔改 FileZilla</h1>
<p>FileZilla 也对这些配置错误的 FTP 服务器有特殊处理逻辑，在设置中的 Connection - FTP - Passive 选项卡中，可以设置当 FTP 服务器返回了内网 IP 时，是强制使用服务器的公网 IP，还是切换到主动模式。</p>
<p>这段逻辑对应 <code>src/engine/ftp/rawtransfer.cpp</code> 的 <code>CFtpRawTransferOpData::ParsePasvResponse()</code> 函数：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">bool</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> CFtpRawTransferOpData</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">ParsePasvResponse</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">()</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 省略解析 PASV 返回内容的代码</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // CFtpRawTransferOpData 类中定义了 host_ 变量，保存 PASV 命令返回的 IP 地址</span></span>
<span class="line"><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">  std</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::wstring host_;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // peerIP 是 FileZilla 主动连接 FTP 服务器时使用的服务器 IP 地址</span></span>
<span class="line"><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">  std</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::wstring </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">const</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> peerIP = </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">to_wstring</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">controlSocket_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">socket_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">-></span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">peer_ip</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">());</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // is_routable_address 函数位于 libfilezilla 库的 lib/iputils.cpp 文件中，</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 判断 IP 地址是公网 IP（true）还是内网 IP（false）。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 其判断逻辑为如果 IP 位于 10.0.0.0/8，127.0.0.0/8，192.168.0.0/16</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 169.254.0.0/16，172.16.0.0/12 中则返回内网 IP，否则返回公网 IP。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 注意其将 0.0.0.0 判断为了公网 IP。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  //</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 这里的逻辑是：如果 FTP 服务器的 IP 是公网 IP，但是 PASV 返回的是内网 IP，则进入特殊处理逻辑。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 只对公网 FTP 服务器应用特殊处理，是因为内网 FTP 服务器确实有可能故意返回一个不同的 IP，</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 为了在网络层进行负载均衡，或者在第一个 IP 的 65535 个端口用完时使用第二个 IP 继续提供服务。</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (!</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">is_routable_address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(host_) &#x26;&#x26; </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">fz</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">is_routable_address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(peerIP)) {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // 如果设置中开启了强制使用服务器公网 IP，则使用服务器 IP 而不是 PASV 返回的 IP</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">options_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">get_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(OPTION_PASVREPLYFALLBACKMODE) != </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> || bTriedActive) {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::status, </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Server sent passive reply with unroutable address. Using server address instead."</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">));</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::debug_info, </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">L"  Reply: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">, peer: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, host_, peerIP);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      host_ = peerIP;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // 否则返回 FTP 被动模式失败，FileZilla 会切换到主动模式重试</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">    else</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::status, </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Server sent passive reply with unroutable address. Passive mode failed."</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">));</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">logmsg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">::debug_info, </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">L"  Reply: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">, peer: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">%s</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, host_, peerIP);</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">      return</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> false</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  // 这个模式在设置界面上被隐藏了，用户无法切换到这个模式</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">options_</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">get_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(OPTION_PASVREPLYFALLBACKMODE) == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    // 不管任何情况都强制使用主动连接 FTP 服务器时的 IP</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    host_ = peerIP;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">  return</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>可以看到 FileZilla 没有把 <code>0.0.0.0</code> 这个 IP 当成内网 IP，导致这个逻辑对拓竹的 FTP 服务器没有生效。解决办法就是魔改 FileZilla 源码，增加一个对于 <code>0.0.0.0</code> 这个 IP 的特殊判断。由于 <code>0.0.0.0</code> 这个 IP 是无效 IP，因此不管服务器处在公网还是内网，都进入特殊处理逻辑：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Index: src/engine/ftp/rawtransfer.cpp</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">===================================================================</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#000080">--- a/src/engine/ftp/rawtransfer.cpp  (revision 11406)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#000080">+++ b/src/engine/ftp/rawtransfer.cpp  (working copy)</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">@@ -399,7 +399,11 @@</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">   }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">   std::wstring const peerIP = fz::to_wstring(controlSocket_.socket_->peer_ip());</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">-  if (!fz::is_routable_address(host_) &#x26;&#x26; fz::is_routable_address(peerIP)) {</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  std::wstring const zeroIP = fz::to_wstring(std::string("0.0.0.0"));</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  if (</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+    std::wcscmp(host_.c_str(), zeroIP.c_str()) == 0</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+    || (!fz::is_routable_address(host_) &#x26;&#x26; fz::is_routable_address(peerIP))</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">+  ) {</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">     if (options_.get_int(OPTION_PASVREPLYFALLBACKMODE) != 1 || bTriedActive) {</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">       log(logmsg::status, _("Server sent passive reply with unroutable address. Using server address instead."));</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">       log(logmsg::debug_info, L"  Reply: %s, peer: %s", host_, peerIP);</span></span></code></pre>
<p>（由于我的博客系统破坏了 Tab 缩进的格式，原始补丁文件可以在这个链接下载：<a href="https://github.com/xddxdd/nixos-config/blob/7a6abe1a61f84c430c92f3d97eb7be0c45da21d0/patches/filezilla-override-pasv-ip-for-zero-ip.patch" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/7a6abe1a61f84c430c92f3d97eb7be0c45da21d0/patches/filezilla-override-pasv-ip-for-zero-ip.patch</a>）</p>
<p>应用上述补丁后重新编译安装 FileZilla，然后重新尝试连接打印机：</p>
<p><picture><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.webp" type="image/webp"><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.avif" type="image/avif"><source srcset="/usr/uploads/202604/filezilla-success-after-fix.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202604/filezilla-success-after-fix.png" alt="魔改的 FileZilla 可以正常获取文件列表"></picture></p>
<p>就可以正常访问打印机的 FTP 服务，上传下载文件了。</p>
<h1 id="附录连接拓竹-3d-打印机的-ftp-配置">附录：连接拓竹 3D 打印机的 FTP 配置</h1>
<ul>
<li>主机（Host）：<code>ftps://192.168.12.34</code>，替换成你的打印机的 IP 地址</li>
<li>用户名：<code>bblp</code></li>
<li>密码：可以在打印机的设置 - 局域网页面中找到，是一个 8 位数的访问码。<strong>注意：不需要启用局域网模式也能使用 FTP，开启局域网模式会导致拓竹云端功能无法使用！</strong></li>
<li>端口号：<code>990</code></li>
<li>在 FileZilla 中，可能需要在加密（Encryption）一栏中选择 <code>Require implicit FTP over TLS</code>。</li>
</ul>]]></content>
        <published>2026-04-13T23:28:02.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using FlapAlerted to Suppress flapping in DN42]]></title>
        <id>https://lantian.pub/en/article/modify-website/dn42-flapalerted-reduce-flapping.lantian/</id>
        <link href="https://lantian.pub/en/article/modify-website/dn42-flapalerted-reduce-flapping.lantian/"/>
        <link rel="enclosure" href="https://lantian.pub/usr/uploads/202512/im-doing-my-part.png" type="image/png"/>
        <updated>2025-12-07T00:14:28.000Z</updated>
        <content type="html"><![CDATA[<p>DN42, aka Decentralized Network 42, is a large, decentralized VPN-based network. But unlike other traditional VPNs, DN42 itself doesn't provide any VPN exits, which means it doesn't allow you to bypass Internet censorships or unlock streaming services. On the contrary, the goal of DN42 is to simulate another Internet. It uses much of the technology running on modern Internet backbones (BGP, recursive DNS, etc), and is a great replica of a real network environment.</p>
<p>In the real internet, various operators use hardware routers from different manufacturers to exchange routing information with each other, such as Cisco, Juniper, Nokia, Arista, Huawei, etc. Similarly, in DN42, different participants will also choose different BGP software and hardware, with the most commonly used being <a href="https://bird.network.cz/" rel="noopener noreferrer" target="_blank">Bird</a> and <a href="https://frrouting.org/" rel="noopener noreferrer" target="_blank">FRRouting</a>, but some also use Mikrotik, Ubiquiti EdgeRouter, or even real commercial routing hardware.</p>
<p>Because everyone chooses different BGP software and hardware, and even when using the same software, they configure their internal networks in different ways, when everyone's networks are connected together, sometimes strange problems may occur, such as BGP Flapping.</p>
<h1 id="bgp-flapping-in-the-real-internet-and-dn42">BGP Flapping in the Real Internet and DN42</h1>
<p>BGP Flapping refers to a large number of path changes of the same route occurring in a short period of time, generally originating from a network repeatedly advertising and withdrawing the same route. Each time a route is advertised and/or withdrawn, this network will pass this route to all peers connected to it. These peers will calculate new best paths based on this route, and then pass the new paths to their peers, and so on.</p>
<p>In the real internet, the problem of BGP Flapping is not too significant, firstly because hardware routers purchased by various operators at great expense have sufficient computing resources to handle these route changes, or have built-in functions to suppress frequent route changes (BGP Dampening), and secondly because real operators use physical network connections, and the high cost of physical links means that except for the largest ISPs, operators that peer with each other are not too numerous, thus reducing the exponential amplification effect.</p>
<p>However, in DN42, the most commonly used Bird BGP daemon does not support BGP Dampening, which allows flapping routes to continue propagating. Even though FRRouting supports BGP Dampening, it doesn't mean everyone will enable it. At the same time, because DN42 participants use VPN connections, the cost of establishing peers is zero, so it's common for a network to connect to dozens or even hundreds of peers, and to spread flapping routes to dozens or hundreds of peers.</p>
<p>Moreover, because DN42 is an experimental network, different participants often change their network configurations from time to time. Since flapping generally switches between several valid paths and doesn't outright cause disconnection, the participant making changes may not immediately notice the problem.</p>
<p>This leads to frequent large-scale, multi-day flapping within DN42, for example:</p>
<p><picture><source srcset="/usr/uploads/202512/flap-example.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/flap-example.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/flap-example.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/flap-example.png" alt="DN42 flapping Record Chart"></picture></p>
<p>The above chart shows the number of route updates received per second by several of my DN42 nodes. You can see that around November 7th, the number of route changes received increased, stepped up again around November 12th, and didn't decrease until November 13th.</p>
<h2 id="traditional-solutions">Traditional Solutions</h2>
<p>For DN42 participants, the biggest problem caused by flapping is the consumption of CPU resources and traffic. Many participants use VPS with limited computing resources and traffic, and flapping may lead to VPS being restricted in CPU usage, limited in network speed, or even suspended by provider.</p>
<p>However, within DN42, even if flapping is known to exist, it's not always possible to find the source of the flapping problem, let alone solve the root problem:</p>
<ol>
<li>
<p>You can choose to manually disconnect problematic peers, which can immediately solve your CPU and traffic consumption problems, but this is only treating the symptoms, not the root cause. A peer that seems problematic to you might just be forwarding route updates from other peers. By doing this, you might need to disconnect your self from innocent peers, especially large networks connected to dozens or hundreds of peers. Moreover, as connections between different participants change, this flapping might be passed to you through other peers, which you need to disconnect again (and hopefully reconnect the former peers).</p>
</li>
<li>
<p>You can try to contact the problematic peer, but because DN42 participants are distributed worldwide, even if the other party is willing to troubleshoot immediately, it might take up to 24 hours to receive a response after they wake up/get off work. Moreover, the other party might not be the root cause of the problem, and they might need to repeat the same process to contact problematic peers, making the entire process very time-consuming.</p>
<ul>
<li>In the real internet, large operators have a 24-hour on-duty NOC (Network Operation Center) that can immediately troubleshoot problems. But obviously, hobbyist networks like DN42 don't have such things.</li>
</ul>
</li>
<li>
<p>Some people have proposed solutions to rate-limit port 179 of BGP. This can reduce the CPU usage of BGP daemon, but cannot reduce the total traffic consumed (and might even increase it), and will slow down the speed of exchanging a large number of routes when disconnected peers reconnect. The reason is that the BGP protocol is based on TCP. When BGP daemon receives a route update, it will immediately send the updated route to other peers through the TCP-based BGP connection. This route update message will immediately enter the OS-allocated buffer for this TCP connection. As long as the TCP connection remains connected, this message will eventually be sent. Even if the TCP connection is very slow, causing this updated route to change again, the BGP daemon cannot withdraw this instruction from the buffer. No common operating systems such as Linux/BSD/Windows provide this mechanism. Therefore, the actual number of route updates sent is still the same, just at a slower speed.</p>
<ul>
<li>Depending on the rate-limiting method, it might just delay packets before handing them to the BGP daemon (generally called Traffic Shaping), or some might directly drop packets (called Traffic Policing). If packets are directly dropped, the peer needs to retransmit the packets, which causes greater traffic consumption.</li>
<li>In my opinion, it's easier and more effective to directly limiting the CPU usage of BGP daemon.</li>
</ul>
</li>
</ol>
<h1 id="implementing-bgp-dampening-yourself-on-bird">Implementing BGP Dampening Yourself on Bird</h1>
<p>To suppress frequent route changes, BGP Dampening needs to do two things: detect routes that change frequently, and then prevent these changes from propagating to more peers by adjusting route/peer weights, thereby reducing the total amount of route changes in the entire network.</p>
<p>Although Bird completely does not support BGP Dampening and cannot implement either of the above functions alone, the step of "detecting routes that change frequently" can already be done by existing software. Another DN42 participant Kioubit developed <a href="https://github.com/Kioubit/FlapAlerted" rel="noopener noreferrer" target="_blank">FlapAlerted</a>, which can peer with your own BGP daemon and then count the number of changes in each route, thereby finding routes whose changes exceeds a threshold. However, this software can only detect and cannot send these flapping network segments back to the BGP daemon, so it cannot achieve the interception effect.</p>
<blockquote>
<p>Actually, FlapAlerted has a <code>mod_roaFilter</code> plugin, which can use the RPKI mechanism (to be introduced later) to filter an existing ROA record, and remove records for the flapping routes. However, this plugin is disabled by default, and you need to compile FlapAlerted yourself to use it. In addition, you need to have already <a href="https://wiki.dn42.dev/services/RPKI" rel="noopener noreferrer" target="_blank">set up RPKI based on DN42 Wiki</a>, and filter all routes without corresponding RPKI records, which is a high standard to meet.</p>
</blockquote>
<p>However, Bird has supported RPKI/ROA functionality since version 2.0. The ROA function in RPKI can be used to verify whether BGP-advertised routes come from the correct ASN. For example, the route 172.22.76.184/29 that I own in DN42 should come from my ASN 4242422547. Combined with Bird's filter functionality, you can intercept incorrect routes with methods similar to the following:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_INVALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Route comes from incorrect ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  reject</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">} </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_UNKNOWN</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # RPKI does not provide information about this route, so it's unknown whether the route comes from the correct ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">} </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_VALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Route comes from correct ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>So, to intercept routes that change frequently, we can generate fake ROA records based on the information collected by FlapAlerted, hijacking these frequently changing routes to invalid ASNs (such as AS0). This way, when routing software like Bird receives these routes, it will consider them to come from incorrect ASNs and intercept them.</p>
<p><a href="https://github.com/Kioubit/FlapAlerted/pull/10" rel="noopener noreferrer" target="_blank">I submitted a PR to the FlapAlerted project</a> to enable it to generate such fake ROA records. This functionality has been included in <a href="https://github.com/Kioubit/FlapAlerted/releases/tag/v4.1.5" rel="noopener noreferrer" target="_blank">FlapAlerted v4.1.5</a>.</p>
<p>However, FlapAlerted only provides an API to generate ROA record files and does not support the RPKI to Router protocol used by BGP daemon, so it cannot directly connect to Bird. For this, we need to use <a href="https://github.com/bgp/stayrtr" rel="noopener noreferrer" target="_blank">StayRTR</a>, which can read and periodically update ROA record files of the same format from the real internet or generated by FlapAlerted, and then send them to Bird through the RPKI to Router protocol.</p>
<p><!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->

<!-- Generated by graphviz version 14.1.4 (0)
 -->
<!-- Title: FlapAlerted_Workflow Pages: 1 -->
<svg width="218pt" height="400pt" viewBox="0.00 0.00 218.00 400.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 396)">
<title>FlapAlerted_Workflow</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-396 213.95,-396 213.95,4 -4,4"></polygon>
<!-- FlapAlerted -->
<g id="node1" class="node">
<title>FlapAlerted</title>
<polygon fill="lightgrey" stroke="black" points="206.09,-392 0,-392 0,-333.6 206.09,-333.6 206.09,-392"></polygon>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-375.4" font-family="Times,serif" font-size="14.00">FlapAlerted</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-358.6" font-family="Times,serif" font-size="14.00">Detect frequently changing routes</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-341.8" font-family="Times,serif" font-size="14.00">Generate ROA configuration</text>
</g>
<!-- StayRTR -->
<g id="node2" class="node">
<title>StayRTR</title>
<polygon fill="lightgrey" stroke="black" points="183.57,-280.8 22.52,-280.8 22.52,-222.4 183.57,-222.4 183.57,-280.8"></polygon>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-264.2" font-family="Times,serif" font-size="14.00">StayRTR</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-247.4" font-family="Times,serif" font-size="14.00">Read ROA configuration</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-230.6" font-family="Times,serif" font-size="14.00">Convert to RPKI protocol</text>
</g>
<!-- FlapAlerted&#45;&gt;StayRTR -->
<g id="edge1" class="edge">
<title>FlapAlerted->StayRTR</title>
<path fill="none" stroke="black" d="M103.05,-333.11C103.05,-320.69 103.05,-305.95 103.05,-292.5"></path>
<polygon fill="black" stroke="black" points="106.55,-292.61 103.05,-282.61 99.55,-292.61 106.55,-292.61"></polygon>
<text xml:space="preserve" text-anchor="middle" x="133.58" y="-303" font-family="Times,serif" font-size="14.00">HTTP API</text>
</g>
<!-- Bird -->
<g id="node3" class="node">
<title>Bird</title>
<polygon fill="lightgrey" stroke="black" points="175.6,-169.6 30.5,-169.6 30.5,-111.2 175.6,-111.2 175.6,-169.6"></polygon>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-153" font-family="Times,serif" font-size="14.00">Bird</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-136.2" font-family="Times,serif" font-size="14.00">Obtain ROA records</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-119.4" font-family="Times,serif" font-size="14.00">through RPKI protocol</text>
</g>
<!-- StayRTR&#45;&gt;Bird -->
<g id="edge2" class="edge">
<title>StayRTR->Bird</title>
<path fill="none" stroke="black" d="M103.05,-221.91C103.05,-209.49 103.05,-194.75 103.05,-181.3"></path>
<polygon fill="black" stroke="black" points="106.55,-181.41 103.05,-171.41 99.55,-181.41 106.55,-181.41"></polygon>
<text xml:space="preserve" text-anchor="middle" x="155.93" y="-191.8" font-family="Times,serif" font-size="14.00">Send ROA records</text>
</g>
<!-- Filter -->
<g id="node4" class="node">
<title>Filter</title>
<polygon fill="lightgrey" stroke="black" points="179.85,-58.4 26.24,-58.4 26.24,0 179.85,0 179.85,-58.4"></polygon>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-41.8" font-family="Times,serif" font-size="14.00">Bird filter</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-25" font-family="Times,serif" font-size="14.00">Check route source</text>
<text xml:space="preserve" text-anchor="middle" x="103.05" y="-8.2" font-family="Times,serif" font-size="14.00">Intercept flapping routes</text>
</g>
<!-- Bird&#45;&gt;Filter -->
<g id="edge3" class="edge">
<title>Bird->Filter</title>
<path fill="none" stroke="black" d="M103.05,-110.71C103.05,-98.29 103.05,-83.55 103.05,-70.1"></path>
<polygon fill="black" stroke="black" points="106.55,-70.21 103.05,-60.21 99.55,-70.21 106.55,-70.21"></polygon>
<text xml:space="preserve" text-anchor="middle" x="156.5" y="-80.6" font-family="Times,serif" font-size="14.00">Internal processing</text>
</g>
</g>
</svg>
</p>
<h2 id="installing-flapalerted">Installing FlapAlerted</h2>
<p>We first need to install FlapAlerted and connect it to our own BGP daemon, so that FlapAlerted can obtain frequently changing routes.</p>
<p>Of course, you can also choose to directly use someone else's FlapAlerted instance, such as the one I set up at <a href="https://flapalerted.lantian.pub" rel="noopener noreferrer" target="_blank">https://flapalerted.lantian.pub</a>, or the one set up by Burble at <a href="https://flaps.collector.dn42" rel="noopener noreferrer" target="_blank">https://flaps.collector.dn42</a> (needs to be accessed from within DN42).</p>
<p>If you use Docker, you can refer to the following Docker compose configuration:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  flapalerted</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    image</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">ghcr.io/kioubit/flapalerted</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    network_mode</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">host</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    command</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--asn"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"4242422547"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Change to your own ASN</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--bgpListenAddress"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:1790"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # BGP session listening port, your BGP daemon needs to connect to this port later</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--httpAPIListenAddress"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8080"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # HTTP API listening port, StayRTR needs to connect to this port later</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-routeChangeCounter"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"120"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Number of times a route path needs to change within one minute to be included in the prefix list. Default is 600, but I think it's too high, I use 120</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-overThresholdTarget"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # How many consecutive minutes the rate reaches or exceeds routeChangeCounter to trigger an event. Default is 10, I changed it to a stricter 5</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-underThresholdTarget"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"30"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # How many consecutive minutes the rate is below routeChangeCounter to remove an event. Default is 15, I changed it to a stricter 30</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">unless-stopped</span></span></code></pre>
<p>If you use NixOS, you can directly refer to my configuration: <a href="https://github.com/xddxdd/nixos-config/blob/42801296ce4f7dc6216e3b87d2b695895b8f2fa2/nixos/optional-apps/flapalerted.nix" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/42801296ce4f7dc6216e3b87d2b695895b8f2fa2/nixos/optional-apps/flapalerted.nix</a></p>
<p>Once FlapAlerted starts successfully, you can modify the BGP daemon configuration to forward routing information to FlapAlerted. If you use Bird, you can refer to the following configuration:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">protocol</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> flapalerted</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  local</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> as</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> 4242422547</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;  </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Change to your own ASN</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Change to the ASN and BGP IP/port set by FlapAlerted.</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Here we use the same ASN as our own network, since BGP protocol does not forward routes from</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # iBGP (i.e., routes from your other nodes) to iBGP peers. Unless you enable the add paths option,</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # routes from your other nodes will only contain the optimal routes. If flapping occurs on</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # suboptimal routes, it will be hidden. Therefore, it is recommended that users with multiple</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # nodes establish separate connections with FlapAlerted on each node.</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  neighbor</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 127.0.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> as</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 4242422547</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> port</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1790</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  ipv4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # Enable add paths option to send non-optimal routes to FlapAlerted as well, making suboptimal</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # route flapping visible.</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    add</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> paths</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    export</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> all</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    import</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> none</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># No need to receive any routes from FlapAlerted</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  ipv6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    add</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> paths</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    export</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> all</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    import</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> none</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>Confirm that Bird is properly connected to FlapAlerted, and confirm that FlapAlerted's ROA API is accessible, for example: <code>curl http://127.0.0.1:8080/flaps/active/roa</code></p>
<p>Continue to the next step after confirming everything is correct.</p>
<h2 id="installing-stayrtr">Installing StayRTR</h2>
<p>The next step is to install StayRTR to send the ROA information generated by FlapAlerted to Bird.</p>
<p>If you use Docker, you can refer to the following Docker compose configuration:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  stayrtr</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    image</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">rpki/stayrtr</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    network_mode</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">host</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    command</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--bind"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8083"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Listening address for RPKI-to-Router protocol</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--metrics.addr"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8084"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Listening address for Prometheus format statistics API</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--cache"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"http://127.0.0.1:8080/flaps/active/roa"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Change to your FlapAlerted server address</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.expire"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"3600"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # How long to retain existing information if FlapAlerted server is offline</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.refresh"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"300"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # How often to refresh information from FlapAlerted server</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.retry"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"300"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # How long to retry if FlapAlerted server is offline</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">unless-stopped</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    depends_on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">flapalerted</span></span></code></pre>
<p>If you use NixOS, you can directly refer to my configuration: <a href="https://github.com/xddxdd/nixos-config/blob/c709166104dc0bf2d8c2798ff338fa84a6c4a85a/nixos/minimal-apps/bird/stayrtr-rpki.nix" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/c709166104dc0bf2d8c2798ff338fa84a6c4a85a/nixos/minimal-apps/bird/stayrtr-rpki.nix</a></p>
<p>After StayRTR starts successfully, you can modify the BGP daemon configuration to connect it to StayRTR. It should be noted here that if you <a href="https://wiki.dn42.dev/services/RPKI" rel="noopener noreferrer" target="_blank">already enabled RPKI referring to DN42 Wiki</a>, you must store the ROA information sent by FlapAlerted in a separate ROA table and check routes based on this ROA table separately. The reason is that if a route has multiple corresponding ASNs according to ROA information, any of these ASNs can advertise this route. Since FlapAlerted only generates information to hijack routes to invalid ASN (AS0), if mixed with normal ROA information, effectively both the original ASN and AS0 can advertise this route, which fails to achieve the filtering effect.</p>
<p>If you use Bird, you can refer to the following configuration:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Create new ROA tables dedicated to FlapAlerted</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v4</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v6</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">protocol</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> rpki</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> rpki_flapalerted</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  roa4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v4</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; };</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  roa6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v6</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; };</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  remote</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 127.0.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> port</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 8083</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Change to the port monitored by StayRTR</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  max</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> version</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  retry</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> keep</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># If connection is interrupted, reconnect every 10 seconds</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">};</span></span></code></pre>
<p>Continue to the next step after confirming that Bird is properly connected to StayRTR. If your FlapAlerted has not yet detected flapping routes, the ROA information is empty, and Bird will display a <code>Cache-Error-No-Data-Available</code> error, which is normal and can be ignored.</p>
<p>When FlapAlerted detects flapping routes, you can use the <code>birdc show route table roa_flap_v4</code> command to check whether ROA information has actually been received.</p>
<h2 id="intercepting-routes-in-bird-filters">Intercepting Routes in Bird Filters</h2>
<p>With ROA information, we can add instructions to check ROA information in the filters of the corresponding protocols in Bird.</p>
<p>If you want to minimize CPU consumption, you can choose to filter out these routes at the Import Filter stage when receiving routes, but you won't be able to access these routes either. In addition, your FlapAlerted instance will also stop receiving these routes, and repeat the process of unblocking them after some time - seeing the flapping routes again - filtering them again.</p>
<p>If you just want to reduce the impact on the DN42 network, you can choose to filter them out at the Export Filter stage when sending routes, with the side effect that your peers won't be able to access these routes through you.</p>
<p>Add to your Filter filter:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Change roa_flap_v4 to the corresponding ROA table name above, use roa_flap_v6 for IPv6</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_flap_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_INVALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Route changes frequently, hijacked by FlapAlerted to AS0, Bird considers the route to</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # come from incorrect ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  reject</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># In other cases, roa_check will return ROA_UNKNOWN, because FlapAlerted does not provide</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># information about other routes, and Bird does not know whether the route source is correct</span></span></code></pre>
<p>After reloading Bird, you will no longer spread these frequently changing routes to your peers, reducing the traffic consumption of you and your peers.</p>
<h1 id="summary">Summary</h1>
<p><picture><source srcset="/usr/uploads/202512/flap-suppression-example.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/flap-suppression-example.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/flap-suppression-example.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/flap-suppression-example.png" alt="BGP Dampening Example Chart"></picture></p>
<p>This chart shows the effect after I configured BGP Dampening in my network. Around 18:00, although flapping occurred within the DN42 network and my nodes received these route changes through multiple peers, FlapAlerted subsequently detected these flapping and blocked these routes through the above process. Therefore, although flapping continued until around 23:00, the routes sent by my network quickly declined after a brief spike, successfully suppressing flapping for my peers.</p>
<p>As you can see, BGP Dampening cannot prevent you from receiving flapping routes, but it can help you save CPU resources, or save some network traffic for you and your peers. Therefore, in addition to configuring BGP Dampening in your network, if other networks send you flapping routes, you can also suggest these networks take similar measures, thereby suppressing flapping on a larger scale and saving traffic for all DN42 participants.</p>
<p><picture><source srcset="/usr/uploads/202512/im-doing-my-part.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/im-doing-my-part.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/im-doing-my-part.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/im-doing-my-part.png" alt="I&#x27;m doing my part meme"></picture></p>]]></content>
        <published>2025-12-07T00:14:28.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[在 DN42 中使用 FlapAlerted 抑制 Flapping]]></title>
        <id>https://lantian.pub/article/modify-website/dn42-flapalerted-reduce-flapping.lantian/</id>
        <link href="https://lantian.pub/article/modify-website/dn42-flapalerted-reduce-flapping.lantian/"/>
        <link rel="enclosure" href="https://lantian.pub/usr/uploads/202512/im-doing-my-part.png" type="image/png"/>
        <updated>2025-12-07T00:14:28.000Z</updated>
        <content type="html"><![CDATA[<p>DN42 全称 Decentralized Network 42（42 号去中心网络），是一个大型的 VPN 网络。但是与其它传统 VPN 不同的是，DN42 使用了大量在互联网骨干上应用的技术（例如 BGP），可以很好的模拟一个真实的网络环境。</p>
<p>在真实的互联网中，各个运营商会使用不同厂商的硬件路由器互相交换路由信息，例如思科（Cisco）、瞻博（Juniper）、诺基亚（Nokia）、Arista、华为等。类似的，在 DN42 中，不同参与者也会选择不同的 BGP 软硬件，最常用的是 <a href="https://bird.network.cz/" rel="noopener noreferrer" target="_blank">Bird</a> 和 <a href="https://frrouting.org/" rel="noopener noreferrer" target="_blank">FRRouting</a>，但也有使用 Mikrotik、Ubiquiti EdgeRouter 甚至真正的商用路由硬件。</p>
<p>由于大家选择的 BGP 软硬件不同，并且即使使用同一款软件也会用不同的方式配置内网，所以当大家的网络连接在一起，有时候就会出一些奇怪的问题，例如 BGP Flapping。</p>
<h1 id="真实互联网和-dn42-中的-bgp-flapping">真实互联网和 DN42 中的 BGP Flapping</h1>
<p>BGP Flapping 指的是同一条路由的路径在短时间内发生大量变化，一般源于一个网络反复广播、撤销广播这一条路由。每次广播或撤销路由时，这个网络会把这条路由传递给所有与它相连的 Peer，这些 Peer 会根据这条路由计算出新的最佳路径，然后把新路径传递给它们的 Peer，与此类推。</p>
<p>在真实互联网中，BGP Flapping 的问题不算太大，一是因为各个运营商斥巨资购买的硬件路由器有足够的计算资源处理这些路由变更，或者内置了抑制路由频繁变更 （BGP Dampening）的功能，二是因为真实的运营商之间使用物理网络连接，物理链路的高价使得除了最大的几家运营商以外，与每个运营商互相 Peer 的运营商不会太多，也就减少了指数放大的效果。</p>
<p>但在 DN42 中，大家最常用的 Bird BGP 软件不支持 BGP Dampening，这使得正在 Flapping 的路由能够一直传递下去。即使 FRRouting 软件支持 BGP Dampening，也不意味着所有用户都会开。同时，由于 DN42 的参与者之间使用 VPN 互联，建立 Peer 的成本为 0，因此一个网络接入几十、上百个 Peer 完全不是问题，把 Flapping 的路由扩散给几十上百个 Peer 也完全不是问题。</p>
<p>而且由于 DN42 是一个实验性网络，不同参与者会经常调整网络配置。由于 Flapping 一般是在几条有效路径之间切换，不会造成断网，所以调整配置的参与者不一定能第一时间发现问题。</p>
<p>这就导致 DN42 内经常出现大规模、持续数日的 Flapping，例如：</p>
<p><picture><source srcset="/usr/uploads/202512/flap-example.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/flap-example.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/flap-example.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/flap-example.png" alt="DN42 Flapping 记录图表"></picture></p>
<p>上图是我的几个 DN42 节点收到的每秒路由更新的数量，可以看到在 11 月 7 日左右，收到的路由变更数量有一个提升，在 11 月 12 日左右又上了一个台阶，直到 11 月 13 日才下降。</p>
<h2 id="传统解决方案">传统解决方案</h2>
<p>对于 DN42 参与者来说，Flapping 造成的最大问题是消耗 CPU 资源以及流量。有很多参与者用的是计算资源和流量有限的 VPS，Flapping 可能导致 VPS 被限制 CPU 使用、限制网络速率甚至被停机。</p>
<p>但是，在 DN42 内，即使知道 Flapping 存在，也不一定能找到 Flapping 问题的源头，更不一定能解决根本问题：</p>
<ol>
<li>
<p>你可以选择人工切断有问题的 Peer，这可以立即解决你的 CPU 和流量消耗问题，但这只是治标不治本。在你看来有问题的 Peer 有可能只是在转发其它 Peer 的路由更新。这样做很容易误伤其它 Peer，尤其是接了几十上百个 Peer 的大型网络。而且，随着不同参与者之间的连接发生变化，这个 Flapping 可能会通过其它的 Peer 传递给你，此时你需要再次切断新的 Peer（并最好把之前的 Peer 接回来）。</p>
</li>
<li>
<p>你可以尝试联系有问题的 Peer，但由于 DN42 参与者分布在全世界，即使对方愿意立即排查问题，也可能需要至多 24 小时，等对方醒了/下班了才能收到回复。而且，对方不一定是问题根源，对方可能也要重复同样的过程联系有问题的 Peer，导致整个流程非常耗时。</p>
<ul>
<li>在真实互联网中，大型运营商都有一个 24 小时值班的 NOC（Network Operation Center，网络运维中心），可以立即排查问题。但 DN42 这样的爱好者网络中显然不会有这种东西。</li>
</ul>
</li>
<li>
<p>也有人提出过给 BGP 的 179 端口限速的方案。这样做可以降低 BGP 软件的 CPU 占用，但无法降低总共消耗的流量（甚至可能增加），并且会延长断开 Peer 重连时交换大量路由的速度。原因在于，BGP 协议是基于 TCP 的。BGP 软件收到一条路由更新时，会立即把更新的路由通过基于 TCP 的 BGP 连接发给其它 Peer。这条更新路由的消息会立即进入操作系统给这条 TCP 连接分配的缓冲区里。只要 TCP 一直保持连接，那么这条消息迟早会发送出去。即使 TCP 连接非常缓慢，导致这条更新的路由再次发生变更，BGP 软件也无法从缓冲区中撤销这条指令，常见的 Linux/BSD/Windows 等操作系统均不提供相应的机制。因此，最终实际发送的路由更新数量还是一样的，只不过速度更慢。</p>
<ul>
<li>根据限速方式不同，有可能只是把数据包延迟之后交给 BGP 软件（一般称为 Traffic Shaping），也有些是直接丢包（称为 Traffic Policing）。如果是直接丢包，对端则需要重传数据包，反而造成了更大的流量消耗。</li>
<li>我认为，这样做还不如直接限制 BGP 软件的 CPU 占用更有效。</li>
</ul>
</li>
</ol>
<h1 id="在-bird-上自己实现-bgp-dampening">在 Bird 上自己实现 BGP Dampening</h1>
<p>BGP Dampening 为了抑制路由频繁变更，要做的事有两件：检测频繁发生变更的路由，然后通过调整路由/Peer 权重等方式，不让这些变更传播给更多的 Peer，从而减少整个网络中的路由变更总量。</p>
<p>尽管 Bird 完全不支持 BGP Dampening，也无法单独实现上述任一功能，但「检测频繁发生变更的路由」这一步已经有现有的软件可以做了。另一名 DN42 参与者 Kioubit 开发了 <a href="https://github.com/Kioubit/FlapAlerted" rel="noopener noreferrer" target="_blank">FlapAlerted</a> 这款软件，可以 Peer 上你自己的 BGP 软件，然后统计各条路由变化的次数，从而找出路由变化量超过阈值的路由。但这款软件只能检测，无法方便地把这些发生 Flapping 的网段发回给 BGP 软件，因此无法实现拦截的效果。</p>
<blockquote>
<p>实际上 FlapAlerted 有一个 <code>mod_roaFilter</code> 插件，可以利用稍后会介绍的 RPKI 功能，过滤一份现有的 ROA 记录，删除 Flapping 网段的记录。但这个功能默认禁用，需要手动编译 FlapAlerted 开启这个插件。而且你需要已经<a href="https://wiki.dn42.dev/services/RPKI" rel="noopener noreferrer" target="_blank">参照 DN42 Wiki 开启了 RPKI</a>，并且过滤所有没有对应 RPKI 记录的路由，门槛比较高。</p>
</blockquote>
<p>不过，Bird 自 2.0 版本开始支持了 RPKI/ROA 功能。RPKI 中的 ROA 功能可以用来验证 BGP 广播的路由是否来自正确的 ASN。例如，我在 DN42 中拥有的 172.22.76.184/29 这段路由应该来自我的 ASN 4242422547。再配合上 Bird 的过滤器功能，就可以用类似下面的方法拦截错误的路由：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_INVALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 路由来自错误的 ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  reject</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">} </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_UNKNOWN</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # RPKI 没有提供这条路由的信息，因此不知道路由是否来自正确的 ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">} </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">else</span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB"> if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_VALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 路由来自正确的 ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>那么，为了拦截频繁发生变更的路由，我们可以基于 FlapAlerted 收集到的信息，生成虚假的 ROA 记录，把这些频繁变更的路由劫持到无效的 ASN 上（例如 AS0）。这样 Bird 等路由软件收到这些路由时，就会认为它们来自错误的 ASN，并拦截它们。</p>
<p><a href="https://github.com/Kioubit/FlapAlerted/pull/10" rel="noopener noreferrer" target="_blank">我向 FlapAlerted 项目提交了一个 PR</a>，让它能够生成这样的虚假 ROA 记录。这个功能已经包含在 <a href="https://github.com/Kioubit/FlapAlerted/releases/tag/v4.1.5" rel="noopener noreferrer" target="_blank">FlapAlerted v4.1.5</a> 版本中。</p>
<p>不过，FlapAlerted 只是提供了一个生成 ROA 记录文件的 API，它并不支持 BGP 软件使用的 RPKI to Router 协议，因此无法直接连接到 Bird 上。为此，我们需要用到 <a href="https://github.com/bgp/stayrtr" rel="noopener noreferrer" target="_blank">StayRTR</a> 软件，它可以读取并定时更新真实互联网中的，或者 FlapAlerted 生成的相同格式的 ROA 记录文件，然后将它们通过 RPKI to Router 协议发送给 Bird。</p>
<p><!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->

<!-- Generated by graphviz version 14.1.4 (0)
 -->
<!-- Title: FlapAlerted_Workflow Pages: 1 -->
<svg width="144pt" height="400pt" viewBox="0.00 0.00 144.00 400.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 396)">
<title>FlapAlerted_Workflow</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-396 139.95,-396 139.95,4 -4,4"></polygon>
<!-- FlapAlerted -->
<g id="node1" class="node">
<title>FlapAlerted</title>
<polygon fill="lightgrey" stroke="black" points="112.64,-392 2.14,-392 2.14,-333.6 112.64,-333.6 112.64,-392"></polygon>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-375.4" font-family="Times,serif" font-size="14.00">FlapAlerted</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-358.6" font-family="Times,serif" font-size="14.00">检测频繁变更的路由</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-341.8" font-family="Times,serif" font-size="14.00">生成 ROA 配置</text>
</g>
<!-- StayRTR -->
<g id="node2" class="node">
<title>StayRTR</title>
<polygon fill="lightgrey" stroke="black" points="111.09,-280.8 3.69,-280.8 3.69,-222.4 111.09,-222.4 111.09,-280.8"></polygon>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-264.2" font-family="Times,serif" font-size="14.00">StayRTR</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-247.4" font-family="Times,serif" font-size="14.00">读取 ROA 配置</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-230.6" font-family="Times,serif" font-size="14.00">转换为 RPKI 协议</text>
</g>
<!-- FlapAlerted&#45;&gt;StayRTR -->
<g id="edge1" class="edge">
<title>FlapAlerted->StayRTR</title>
<path fill="none" stroke="black" d="M57.39,-333.11C57.39,-320.69 57.39,-305.95 57.39,-292.5"></path>
<polygon fill="black" stroke="black" points="60.89,-292.61 57.39,-282.61 53.89,-292.61 60.89,-292.61"></polygon>
<text xml:space="preserve" text-anchor="middle" x="87.92" y="-303" font-family="Times,serif" font-size="14.00">HTTP API</text>
</g>
<!-- Bird -->
<g id="node3" class="node">
<title>Bird</title>
<polygon fill="lightgrey" stroke="black" points="105.84,-169.6 8.94,-169.6 8.94,-111.2 105.84,-111.2 105.84,-169.6"></polygon>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-153" font-family="Times,serif" font-size="14.00">Bird</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-136.2" font-family="Times,serif" font-size="14.00">通过 RPKI 协议</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-119.4" font-family="Times,serif" font-size="14.00">获取 ROA 记录</text>
</g>
<!-- StayRTR&#45;&gt;Bird -->
<g id="edge2" class="edge">
<title>StayRTR->Bird</title>
<path fill="none" stroke="black" d="M57.39,-221.91C57.39,-209.49 57.39,-194.75 57.39,-181.3"></path>
<polygon fill="black" stroke="black" points="60.89,-181.41 57.39,-171.41 53.89,-181.41 60.89,-181.41"></polygon>
<text xml:space="preserve" text-anchor="middle" x="96.67" y="-191.8" font-family="Times,serif" font-size="14.00">发送 ROA 记录</text>
</g>
<!-- Filter -->
<g id="node4" class="node">
<title>Filter</title>
<polygon fill="lightgrey" stroke="black" points="114.78,-58.4 0,-58.4 0,0 114.78,0 114.78,-58.4"></polygon>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-41.8" font-family="Times,serif" font-size="14.00">Bird 过滤器</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-25" font-family="Times,serif" font-size="14.00">检查路由来源</text>
<text xml:space="preserve" text-anchor="middle" x="57.39" y="-8.2" font-family="Times,serif" font-size="14.00">拦截 Flapping 路由</text>
</g>
<!-- Bird&#45;&gt;Filter -->
<g id="edge3" class="edge">
<title>Bird->Filter</title>
<path fill="none" stroke="black" d="M57.39,-110.71C57.39,-98.29 57.39,-83.55 57.39,-70.1"></path>
<polygon fill="black" stroke="black" points="60.89,-70.21 57.39,-60.21 53.89,-70.21 60.89,-70.21"></polygon>
<text xml:space="preserve" text-anchor="middle" x="78.39" y="-80.6" font-family="Times,serif" font-size="14.00">内部处理</text>
</g>
</g>
</svg>
</p>
<h2 id="安装-flapalerted">安装 FlapAlerted</h2>
<p>我们首先需要安装 FlapAlerted 并将它与自己的 BGP 软件连接，从而让 FlapAlerted 获取频繁变更的路由。</p>
<p>当然，你也可以选择直接使用别人搭建好的 FlapAlerted，例如我搭建的 <a href="https://flapalerted.lantian.pub" rel="noopener noreferrer" target="_blank">https://flapalerted.lantian.pub</a>，或者 Burble 搭建的 <a href="https://flaps.collector.dn42" rel="noopener noreferrer" target="_blank">https://flaps.collector.dn42</a>（需要从 DN42 内部访问）。</p>
<p>如果你使用 Docker，可以参考下面的 Docker compose 配置：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  flapalerted</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    image</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">ghcr.io/kioubit/flapalerted</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    network_mode</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">host</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    command</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--asn"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"4242422547"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 修改成你自己的 ASN</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--bgpListenAddress"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:1790"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # BGP 会话监听端口，稍后你的 BGP 软件需要连接到这个端口</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--httpAPIListenAddress"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8080"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # HTTP API 监听端口，稍后 StayRTR 需要连接到这个端口</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-routeChangeCounter"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"120"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 路由路径在一分钟内需要变更的次数才会被列入前缀列表。默认值是 600，但我认为太高了，我使用的是 120</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-overThresholdTarget"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 连续多少分钟速率达到或超过 routeChangeCounter 才会触发事件。默认是 10，我改成了更严格的 5</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"-underThresholdTarget"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"30"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 连续多少分钟速率低于 routeChangeCounter 才会移除事件。默认是 15，我改成了更严格的 30</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">unless-stopped</span></span></code></pre>
<p>如果你使用 NixOS，可以直接参考我的配置：<a href="https://github.com/xddxdd/nixos-config/blob/42801296ce4f7dc6216e3b87d2b695895b8f2fa2/nixos/optional-apps/flapalerted.nix" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/42801296ce4f7dc6216e3b87d2b695895b8f2fa2/nixos/optional-apps/flapalerted.nix</a></p>
<p>在 FlapAlerted 启动成功后，你就可以修改 BGP 软件的配置，将路由信息转发给 FlapAlerted。如果你用的是 Bird，可以参考下面的配置：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">protocol</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> flapalerted</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  local</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> as</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> 4242422547</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;  </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 修改成你自己的 ASN</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 修改成 FlapAlerted 设置的 ASN 和 BGP IP/端口。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 这里我们使用和自己网络相同的 ASN，是为了利用 BGP 协议不会把来自 iBGP 的路由（即自己其它节点的路由）转发给 iBGP Peer 的特点。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 除非你开启了 add paths 选项，否则来自自己其它节点的路由只会包含最优的路由，如果 Flapping 发生在次优路由就会被隐藏。</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 因此建议有多个节点的用户在每个节点上都单独和 FlapAlerted 建立连接。</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  neighbor</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 127.0.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> as</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 4242422547</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> port</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1790</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  ipv4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # 开启 add paths 选项，把非最优路由也发给 FlapAlerted，让次优路由 Flapping 也可见。</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    add</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> paths</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    export</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> all</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    import</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> none</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 不需要从 FlapAlerted 接收任何路由</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  ipv6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    add</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> paths</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    export</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> all</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    import</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> none</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>确认 Bird 正常连接到 FlapAlerted，并确认 FlapAlerted 的 ROA API 可以正常访问，例如：<code>curl http://127.0.0.1:8080/flaps/active/roa</code></p>
<p>确认无误后继续下一步。</p>
<h2 id="安装-stayrtr">安装 StayRTR</h2>
<p>下一步是安装 StayRTR，将 FlapAlerted 生成的 ROA 信息发送给 Bird。</p>
<p>如果你使用 Docker，可以参考下面的 Docker compose 配置：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  stayrtr</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    image</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">rpki/stayrtr</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    network_mode</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">host</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    command</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--bind"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8083"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # RPKI-to-Router 协议的监听地址</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--metrics.addr"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1:8084"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Prometheus 格式统计信息 API 的监听地址</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--cache"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"http://127.0.0.1:8080/flaps/active/roa"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 修改成你的 FlapAlerted 服务器地址</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.expire"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"3600"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 如果 FlapAlerted 服务器离线，保留现有的信息多长时间</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.refresh"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"300"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 多长时间从 FlapAlerted 服务器刷新一次信息</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"--rtr.retry"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"300"</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 如果 FlapAlerted 服务器离线，多长时间后重试</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">unless-stopped</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    depends_on</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">flapalerted</span></span></code></pre>
<p>如果你使用 NixOS，可以直接参考我的配置：<a href="https://github.com/xddxdd/nixos-config/blob/c709166104dc0bf2d8c2798ff338fa84a6c4a85a/nixos/minimal-apps/bird/stayrtr-rpki.nix" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nixos-config/blob/c709166104dc0bf2d8c2798ff338fa84a6c4a85a/nixos/minimal-apps/bird/stayrtr-rpki.nix</a></p>
<p>在 StayRTR 启动成功后，你就可以修改 BGP 软件的配置，让它连接到 StayRTR。这里需要注意的是，如果你<a href="https://wiki.dn42.dev/services/RPKI" rel="noopener noreferrer" target="_blank">原本就参考 DN42 Wiki 启用了 RPKI</a>，则必须把 FlapAlerted 发来的 ROA 信息单独存在一张 ROA 表里面，并单独基于这个 ROA 表检查一次路由。这样做的原因是，如果 ROA 信息中一条路由有多条对应的 ASN，那么这些 ASN 中的任何一个都可以广播这条路由。由于 FlapAlerted 只是生成了将路由劫持到无效 ASN（AS0）的信息，如果和正常的 ROA 信息混在一起，效果就是原本的 ASN 和 AS0 都可以广播这条路由，就起不到过滤效果了。</p>
<p>如果你用的是 Bird，可以参考下面的配置：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 新建专用于 FlapAlerted 的 ROA 表</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v4</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v6</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">protocol</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> rpki</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> rpki_flapalerted</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  roa4</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v4</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; };</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  roa6</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> roa_flap_v6</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; };</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  remote</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 127.0.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> port</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 8083</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 修改成 StayRTR 监听的端口</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  max</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> version</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  retry</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> keep</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 如果连接中断，每 10 秒重连一次</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">};</span></span></code></pre>
<p>确认 Bird 正常连接到 StayRTR 后继续下一步。如果你的 FlapAlerted 尚未检测到频繁更新的路由时，ROA 信息是空的，此时 Bird 会显示 <code>Cache-Error-No-Data-Available</code> 错误，是正常现象，可以忽略。</p>
<p>在 FlapAlerted 检测到频繁更新的路由时，你可以用 <code>birdc show route table roa_flap_v4</code> 命令检查是否实际收到了 ROA 信息。</p>
<h2 id="在-bird-过滤器中拦截路由">在 Bird 过滤器中拦截路由</h2>
<p>有了 ROA 信息，我们就可以在 Bird 对应协议的过滤器中添加检查 ROA 信息的指令了。</p>
<p>如果你希望尽量减少 CPU 消耗，可以选择在 Import Filter 接收路由阶段就过滤掉这些路由，但副作用是你也就无法访问这些路由了，并且你的 FlapAlerted 也会收不到这些路由，并重复在一段时间后解封 - 再次收到 Flapping 路由 - 再次过滤这个流程。</p>
<p>如果你只是希望减少对 DN42 网络的影响，可以选择在 Export Filter 发送路由阶段过滤掉它们，副作用是你的 Peer 将无法通过你访问这些路由。</p>
<p>在你的 Filter 过滤器中添加：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># roa_flap_v4 修改成对应上面的 ROA table 名称，IPv6 则使用 roa_flap_v6</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">roa_check(roa_flap_v4,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> net,</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bgp_path.last</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ROA_INVALID</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 路由频繁变更，被 FlapAlerted 劫持去了 AS0，Bird 认为路由来自错误的 ASN</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  reject</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 其它情况下，roa_check 会返回 ROA_UNKNOWN，因为 FlapAlerted 不会提供其它路由的信息，Bird 不知道路由来源是否正确</span></span></code></pre>
<p>Reload Bird 之后，你就不会把这些频繁变更的路由进一步扩散给你的 Peer 了，降低了你的和你的 Peer 的流量消耗。</p>
<h1 id="总结">总结</h1>
<p><picture><source srcset="/usr/uploads/202512/flap-suppression-example.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/flap-suppression-example.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/flap-suppression-example.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/flap-suppression-example.png" alt="BGP Dampening 示例图表"></picture></p>
<p>这张图展示了我在我的网络配置完 BGP Dampening 之后的效果。在 18 时左右，尽管 DN42 网络内部发生了 Flapping，并且我的节点通过多个 Peer 接收到了这些路由变更，但 FlapAlerted 随后检测到了这些 Flapping 并通过上面的流程屏蔽了这些路由。因此，尽管 Flapping 持续到了 23 时左右，但我的网络发送的路由在短暂的尖峰后就迅速下降，成功地为我的 Peer 抑制了 Flapping。</p>
<p>可以看到，BGP Dampening 无法阻止你收到 Flapping 路由，但可以帮你节省 CPU 资源，或者为你和你的 Peer 节省部分网络流量。因此，除了在你的网络配置 BGP Dampening 之外，如果其它网络向你发送 Flapping 路由，你也可以建议这些网络采取类似的措施，从而在更大的范围抑制 Flapping，为所有 DN42 的参与者节省流量。</p>
<p><picture><source srcset="/usr/uploads/202512/im-doing-my-part.png.webp" type="image/webp"><source srcset="/usr/uploads/202512/im-doing-my-part.png.avif" type="image/avif"><source srcset="/usr/uploads/202512/im-doing-my-part.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202512/im-doing-my-part.png" alt="I&#x27;m doing my part 梗图"></picture></p>]]></content>
        <published>2025-12-07T00:14:28.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[Legal LTE Network at Home with Open5GS]]></title>
        <id>https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-with-open5gs.lantian/</id>
        <link href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-with-open5gs.lantian/"/>
        <updated>2025-07-20T12:38:31.000Z</updated>
        <content type="html"><![CDATA[<p>In my <a href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/">previous post</a>, I built a legal LTE network using the US CBRS band and <a href="https://magmacore.org/" rel="noopener noreferrer" target="_blank">Magma LTE core network software</a>.</p>
<blockquote>
<p>Regarding "legal": I am not a lawyer or a wireless expert. Based on my research into the relevant policies and regulations, my entire setup should be legal. However, I take no responsibility if you encounter any legal issues after following the instructions in this post.</p>
</blockquote>
<p>I chose Magma at the time because the CBRS LTE base station I bought was originally used for the Helium Mobile network, and <a href="https://github.com/helium/HIP/blob/main/0139-phase-out-cbrs.md#what-to-do-with-cbrs-radios" rel="noopener noreferrer" target="_blank">Nova Labs/Helium Mobile uses Magma for its CBRS core network</a>. This ensured that Magma was compatible with my base station. However, from the perspective of building a self-hosted core network in a Homelab, Magma has these issues:</p>
<ul>
<li>Magma's core network relies on Docker or Kubernetes for deployment, making it difficult to deploy outside of containers using conventional methods (e.g., systemd services). As a NixOS user, I prefer to avoid bloated Docker containers and manage services on the system using systemd.</li>
<li>Magma's Access Gateway can only be installed on Ubuntu 20.04, which has a completely different system management approach from my usual NixOS. This means I would need to manually manage the Access Gateway machine's configuration and system upgrades, without being able to reuse my existing NixOS configuration.</li>
<li>Magma sometimes has strange issues, such as:
<ul>
<li>Android phones always failing to connect to the base station while iPhones work fine;</li>
<li>Phones unable to properly obtain the network name, always displaying MCC/MNC <code>315 010</code> instead of the actual configured network name <code>Lan Tian Mobile</code>;</li>
<li>The Access Gateway connected to the core network and synchronized configurations normally, but the core network management interface showed that the Access Gateway had not been connected for a long time.</li>
</ul>
</li>
</ul>
<p>Therefore, after finishing the previous post and confirming the feasibility of building a self-hosted LTE network, I began trying to replace Magma with another open-source LTE core network software, <a href="https://open5gs.org/" rel="noopener noreferrer" target="_blank">Open5GS</a>.</p>
<p>Compared to Magma, Open5GS has these advantages:</p>
<ul>
<li>Open5GS does not distinguish between core network and Access Gateway components; it can be fully deployed on a single machine.</li>
<li>Open5GS packages are already available in Nixpkgs (<code>pkgs.open5gs</code>), so I can install and use it directly on NixOS without needing to package it myself, and without Docker or Ubuntu.</li>
<li>Open5GS does not have the strange issues that Magma has; once set up, it is quite stable.</li>
</ul>
<p>This post documents the process of setting up a core network with Open5GS on NixOS, and connecting a FreedomFi/Sercomm SCE4255W base station to the core network to transmit LTE signals.</p>
<h1 id="installing-open5gs">Installing Open5GS</h1>
<blockquote>
<p>I referenced the following materials during the configuration process:</p>
<ul>
<li><a href="https://open5gs.org/open5gs/docs/" rel="noopener noreferrer" target="_blank">Open5GS official documentation</a></li>
<li>A set of Open5GS (and some add-ons) configurations packaged as ready-to-use Docker containers: <a href="https://github.com/herlesupreeth/docker_open5gs" rel="noopener noreferrer" target="_blank">herlesupreeth/docker_open5gs</a></li>
</ul>
</blockquote>
<h2 id="preparation">Preparation</h2>
<p>This post assumes you have prepared the following hardware or software configurations as described in my <a href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/">previous post</a>. If you have not completed these configurations, you can refer to the corresponding sections in the previous post to configure the software or purchase the hardware:</p>
<ul>
<li>A FreedomFi/Sercomm SCE4255W base station with the web management interface unlocked.</li>
<li>The base station is already registered with the CBRS SAS.</li>
<li>A SIM card programmedwith authentication information (KI, OPC, etc.), and you have recorded this authentication information (for later registration with Open5GS).</li>
</ul>
<p>This post will use NixOS for all configurations, but I also provides some commands for Ubuntu, which users of other Linux distributions can use as a reference.</p>
<h2 id="understanding-open5gs-components">Understanding Open5GS Components</h2>
<p>Open5GS, as its name suggests, is primarily a software that implements a 5G core network (as well as a LTE core network). Since the core network protocols and structure in the 5G era are significantly different from the 4G era, especially for standalone 5G SA networks, Open5GS can roughly be seen as a set of LTE/5G NSA core network software, plus a set of 5G SA core network software, with a small portion of components shared between them.</p>
<p>The LTE/5G NSA part of Open5GS consists of the following components:</p>
<ul>
<li>MME - Mobility Management Entity</li>
<li>HSS - Home Subscriber Server</li>
<li>PCRF - Policy and Charging Rules Function</li>
<li>SGWC - Serving Gateway Control Plane</li>
<li>SGWU - Serving Gateway User Plane</li>
<li>SMF - Session Management Function
<ul>
<li>SMF itself is a 5G core network component, but Open5GS SMF also implements the Packet Gateway Control Plane in the 4G core network structure.</li>
</ul>
</li>
<li>UPF - User Plane Function
<ul>
<li>UPF itself is a 5G core network component, but Open5GS UPF also implements the Packet Gateway User Plane in the 4G core network structure.</li>
</ul>
</li>
<li>SCP - <del>Secure, Contain, Protect</del> Service Communication Proxy
<ul>
<li>SCP itself is a 5G core network component, but SMF depends on it.</li>
</ul>
</li>
<li>NRF - NF Repository Function
<ul>
<li>NRF itself is a 5G core network component, but SCP depends on it.</li>
</ul>
</li>
</ul>
<p>And the 5G SA part consists of the following components:</p>
<ul>
<li>NRF - NF Repository Function</li>
<li>SCP - Service Communication Proxy</li>
<li>SEPP - Security Edge Protection Proxy</li>
<li>AMF - Access and Mobility Management Function</li>
<li>SMF - Session Management Function</li>
<li>UPF - User Plane Function</li>
<li>AUSF - Authentication Server Function</li>
<li>UDM - Unified Data Management</li>
<li>UDR - Unified Data Repository</li>
<li>PCF - Policy and Charging Function</li>
<li>NSSF - Network Slice Selection Function</li>
<li>BSF - Binding Support Function</li>
</ul>
<p>These components communicate with each other in the following structure:</p>
<p><picture><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.webp" type="image/webp"><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.avif" type="image/avif"><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/Open5GS_CUPS-01.jpg" alt="Open5GS Component Architecture Diagram"></picture></p>
<p>(Image source: <a href="https://open5gs.org/open5gs/docs/guide/01-quickstart/" rel="noopener noreferrer" target="_blank">Open5GS official documentation</a>)</p>
<p>The communication between various components of the 4G/5G core network uses the standardized <a href="https://en.wikipedia.org/wiki/Diameter_(protocol)" rel="noopener noreferrer" target="_blank">Diameter protocol</a>, which is based on TCP or <a href="https://en.wikipedia.org/wiki/Stream_Control_Transmission_Protocol" rel="noopener noreferrer" target="_blank">SCTP</a> protocol, exchanging data between various components of the 4G/5G core network. This also means that hardware and software from different vendors, as long as they support the Diameter protocol, can join the same core network and jointly provide services to mobile users.</p>
<p>However, in this post, I will only use Open5GS components, and will not add other components to the core network for now.</p>
<h2 id="installing-open5gs-package">Installing Open5GS Package</h2>
<p>If you are using Ubuntu, you can refer to the <a href="https://open5gs.org/open5gs/docs/guide/01-quickstart/" rel="noopener noreferrer" target="_blank">Open5GS official installation tutorial</a>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Install MongoDB</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://pgp.mongodb.com/server-8.0.asc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -o</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /usr/share/keyrings/mongodb-server-8.0.gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --dearmor</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/sources.list.d/mongodb-org-8.0.list</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> mongodb-org</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Install Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> add-apt-repository</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ppa:open5gs/latest</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs</span></span></code></pre>
<p>During this process, in addition to installing the Open5GS binaries, a set of systemd services corresponding to each Open5GS component is created, and the default Open5GS configuration is copied to <code>/etc</code>.</p>
<p>Since NixOS only has the Open5GS package (<code>pkgs.open5gs</code>) and no corresponding NixOS module, we need to manually create systemd services for Open5GS, mimicking the installation process on other systems like Ubuntu:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{ </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, ... }:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Since we are only building a 4G core network, only enable the services required for 4G core network</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "hss"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "mme"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "nrf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "pcrf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "scp"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "sgwc"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "sgwu"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "smf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "upf"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ];</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">in</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Enable MongoDB, HSS, PCF, PCRF components need MongoDB to save configurations</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">mongodb</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    enable</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    bind_ip</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    package</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">mongodb-ce</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Create systemd services for each Open5GS component</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">listToAttrs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        description</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Open5GS </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">toUpper</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Daemon"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">          # The configuration file in the open5gs folder pointed to here will be created in the next step</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d -c </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.yaml"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          ExecReload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">coreutils</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/kill -HUP $MAINPID"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          LogsDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          Restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"always"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          RestartSec</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          RestartPreventExitStatus</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">services</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  );</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Create a separate user and group for Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    isSystemUser</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">groups</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = { };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Create a TUN interface named ogstun for communication with LTE devices</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">network</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">netdevs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    netdevConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Kind</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"tun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"ogstun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">network</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">networks</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # The IP addresses used here are the same as in the default Open5GS configuration</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "10.45.0.1/16"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "2001:db8:cafe::1/48"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    linkConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      MTUBytes</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1400</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      RequiredForOnline</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">false</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    matchConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">Name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"ogstun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="creating-open5gs-configuration-files">Creating Open5GS Configuration Files</h2>
<p>If you are using Ubuntu, the above installation process should have automatically installed the default configuration files to <code>/etc/freeDiameter</code> and <code>/etc/open5gs</code>. However, in NixOS, this process is not automatic, and we need to manually copy the configuration files or manually specify their paths.</p>
<p>Since the Nixpkgs Open5GS package already comes with a set of default configurations, we can directly copy the default configuration files from this package. First, build the package:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">nix</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nixpkgs#open5gs</span></span></code></pre>
<p>If all goes well, Nix will download the pre-compiled Open5GS from the Binary Cache and symlink it to the <code>result</code> directory. At this point, we can see the default configuration files in the <code>result/etc</code> folder:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">ls</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc</span></span></code></pre>
<p>Then we can copy them to our NixOS configuration for later modification:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -r</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc/freeDiameter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/freeDiameter</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -r</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc/open5gs</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/open5gs</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Files copied from Nix store are read-only by default, add write permissions to them</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">chmod</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -R</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> +w</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/freeDiameter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/open5gs</span></span></code></pre>
<p>For files in the <code>freeDiameter</code> folder, we need to place them under <code>/etc/freeDiameter</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">etc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"freeDiameter"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">source</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./freeDiameter</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>For files in the <code>open5gs</code> folder, you can directly specify the configuration file path using the <code>-c</code> parameter when starting Open5GS:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d -c </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.yaml"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>Not placing them in <code>/etc</code> ensures that Open5GS services will automatically restart after modifying the configuration files.</p>
<h2 id="fixing-paths-in-open5gs-configuration-files-under-nixos">Fixing Paths in Open5GS Configuration Files under NixOS</h2>
<p>Since Open5GS packaged in Nixpkgs is installed by default under a path in <code>/nix/store</code>, its configuration files also references many paths under <code>/nix/store</code> by default.</p>
<p>First, get the actual installation path of Open5GS:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">nix</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nixpkgs#open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --print-out-paths</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --no-link</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Output similar to:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/</span></span></code></pre>
<p>Then search for this path in the copied configuration files. You will see many places containing the full path:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">grep</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Referencing TLS certificates generated by default during Open5GS build</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter/hss.conf:TLS_Cred = "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/hss.crt", "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/hss.key";</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Referencing freeDiameter Extension</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter/hss.conf:LoadExtension = "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/lib/freeDiameter/dbg_msg_dumps.fdx" : "0x8888";</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Default log path is placed in Nix store</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># open5gs/hss.yaml:    path: /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/var/log/open5gs/hss.log</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter configuration file path is set in Nix store</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># open5gs/hss.yaml:  freeDiameter: /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/freeDiameter/hss.conf</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span></code></pre>
<p>Once the Open5GS package or its dependencies are updated, the path of Open5GS in the Nix store will change, causing files specified by absolute paths to become invalid, and preventing Open5GS from starting. Therefore, we need to keep these paths synchronized with the Open5GS path, or point them outside the Nix store, to prevent future issues.</p>
<p>My workaround is to first link a copy of the <code>pkgs.open5gs</code> package to <code>/etc</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">etc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-pkg"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">source</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>Then modify the above paths:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># TLS certificates point to /etc/open5gs-pkg. Although this certificate is downloaded from Nixpkgs Binary Cache and the private key can be considered public, we are deploying on a single machine, and communication does not go through external networks, so proper encryption is not necessary.</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/#/etc/open5gs-pkg/etc/open5gs/tls/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter Extension points to /etc/open5gs-pkg</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/lib/freeDiameter/#/etc/open5gs-pkg/lib/freeDiameter/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Paths in /var point to the actual /var</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/var/#/var/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter configuration file points to /etc/freeDiameter</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/freeDiameter/#/etc/freeDiameter/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<p>After the modification, we will be able to upgrade Open5GS without issues in the future, and our configuration files placed in <code>/etc</code> will take effect normally.</p>
<h2 id="optional-regenerate-diameter-tls-certificates">(Optional) Regenerate Diameter TLS Certificates</h2>
<p>Open5GS packaged in Nixpkgs comes with a TLS certificate generated during the build process. If your Open5GS is downloaded from the Binary Cache instead of being compiled locally, you will be using the same TLS key  others can download from the Binary Cache.</p>
<p>If you deploy on a single machine according to this tutorial, since all communication is local and does not go through external networks, encryption and private key leakage have little impact on security.</p>
<p>However, if you plan to place some components on other machines, or if you do not want to use this leaked key, you can generate a new one using the following script:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs-certs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">with</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; [ </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">openssl</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    script</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">''</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      mkdir -p demoCA</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      if [ ! -f "demoCA/serial" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        echo 01 > demoCA/serial</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      fi</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      touch demoCA/index.txt</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      # CA self certificate</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      if [ ! -f "ca.crt" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        openssl req -new -x509 -days 3650 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          -subj /CN=ca.epc.mnc010.mcc315.3gppnetwork.org/C=KO/ST=Seoul/O=NeoPlane</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      fi</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      for i in amf ausf bsf hss mme nrf scp sepp1 sepp2 sepp3 nssf pcf pcrf smf udm udr</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      do</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        if [ ! -f "$i.crt" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -out $i.key</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl req -new -key $i.key -out $i.csr \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -subj /CN=$i.epc.mnc010.mcc315.3gppnetwork.org/C=KO/ST=Seoul/O=NeoPlane</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl ca -batch -notext -days 3650 \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -keyfile ca.key -cert ca.crt \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -in $i.csr -out $i.crt -outdir .</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        fi</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      done</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    ''</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Type</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"oneshot"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      StateDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-certs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      WorkingDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"/var/lib/open5gs-certs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>When you run <code>systemctl start open5gs-certs.service</code>, this service will automatically generate missing keys in <code>/var/lib/open5gs-certs</code>.</p>
<p>Then you can modify the Open5GS configuration file to point the TLS key path to <code>/var/lib/open5gs-certs</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># If you did not replace the TLS key path in the previous step</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/#/var/lib/open5gs-certs/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># If you have already replaced the TLS key path in the previous step</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/etc/open5gs-pkg/etc/open5gs/tls/#/var/lib/open5gs-certs/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<p>You can also add <code>open5gs-certs.service</code> to the <code>After</code> and <code>Requires</code> of each Open5GS systemd service to ensure that the keys are generated before Open5GS starts.</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "open5gs-certs.service"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "open5gs-certs.service"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="installing-open5gs-web-management-ui">Installing Open5GS Web Management UI</h2>
<p>The above steps configured the Open5GS core network itself, but we also need to install the web management UI to manage SIM card related information registered with Open5GS.</p>
<p>If you are using Ubuntu, you can use the official installation script:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Download Nodesource GPG key</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -y</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ca-certificates</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> curl</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gnupg</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> mkdir</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -p</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/keyrings</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --dearmor</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -o</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/keyrings/nodesource.gpg</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Add NodeJS package repository</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">NODE_MAJOR</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">20</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">$NODE_MAJOR</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.x nodistro main"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/sources.list.d/nodesource.list</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Install NodeJS</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nodejs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -y</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># Install Open5GS WebUI</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://open5gs.org/open5gs/assets/webui/install</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -E</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bash</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span></span></code></pre>
<p>If you are using NixOS, you can install it with the following configuration:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  config</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs-webui</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    description</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Open5GS WebUI"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">with</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; [</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      bash</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      nodejs</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      rsync</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      HOSTNAME</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"0.0.0.0"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      PORT</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"9999"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    preStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">''</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      export HOME=$(pwd)</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      rsync -r --chmod=D755,F755 </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">src</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/webui/ .</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      npm install</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      npm run build</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    ''</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">nodejs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/npm run start"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      CacheDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      WorkingDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"/var/cache/open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"always"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      RestartSec</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="starting-open5gs">Starting Open5GS</h2>
<p>Deploy the above configuration to your NixOS machine, and if everything goes well, these services should start normally without issues.</p>
<p>If you are using Ubuntu, all 4G/5G services should have automatically started when you installed the <code>open5gs</code> package. You can disable the 5G SA related services that we don't need, or you can ignore them; they will not affect subsequent configurations.</p>
<h2 id="creating-default-administrator-for-management-panel">Creating Default Administrator for Management Panel</h2>
<p>Open5GS does not automatically create a default administrator user when it starts, so after the deployment is complete and MongoDB has started, we need to manually run the following command to create an administrator:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cat</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;&#x3C;</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EOF</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">mongosh</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">db = db.getSiblingDB('open5gs')</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">cursor = db.accounts.find()</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">if ( cursor.count() == 0 ) {</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    db.accounts.insertOne({ salt: 'f5c15fa72622d62b6b790aa8569b9339729801ab8bda5d13997b5db6bfc1d997', hash: '402223057db5194899d2e082aeb0802f6794622e1cbc47529c419e5a603f2cc592074b4f3323b239ffa594c8b756d5c70a4e1f6ecd3f9f0d2d7328c4cf8b1b766514effff0350a90b89e21eac54cd4497a169c0c7554a0e2cd9b672e5414c323f76b8559bc768cba11cad2ea3ae704fb36abc8abc2619231ff84ded60063c6e1554a9777a4a464ef9cfdfa90ecfdacc9844e0e3b2f91b59d9ff024aec4ea1f51b703a31cda9afb1cc2c719a09cee4f9852ba3cf9f07159b1ccf8133924f74df770b1a391c19e8d67ffdcbbef4084a3277e93f55ac60d80338172b2a7b3f29cfe8a36738681794f7ccbe9bc98f8cdeded02f8a4cd0d4b54e1d6ba3d11792ee0ae8801213691848e9c5338e39485816bb0f734b775ac89f454ef90992003511aa8cceed58a3ac2c3814f14afaaed39cbaf4e2719d7213f81665564eec02f60ede838212555873ef742f6666cc66883dcb8281715d5c762fb236d72b770257e7e8d86c122bb69028a34cf1ed93bb973b440fa89a23604cd3fefe85fbd7f55c9b71acf6ad167228c79513f5cfe899a2e2cc498feb6d2d2f07354a17ba74cecfbda3e87d57b147e17dcc7f4c52b802a8e77f28d255a6712dcdc1519e6ac9ec593270bfcf4c395e2531a271a841b1adefb8516a07136b0de47c7fd534601b16f0f7a98f1dbd31795feb97da59e1d23c08461cf37d6f2877d0f2e437f07e25015960f63', username: 'admin', roles: [ 'admin' ], "__v" : 0})</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">}</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EOF</span></span></code></pre>
<p>(Source: <a href="https://github.com/open5gs/open5gs/blob/main/docs/assets/webui/mongo-init.js" rel="noopener noreferrer" target="_blank">https://github.com/open5gs/open5gs/blob/main/docs/assets/webui/mongo-init.js</a>)</p>
<p>The above command will create an administrator user with username <code>admin</code> and password <code>1423</code>.</p>
<p>Open <code>http://[Open5GS machine's IP address]:9999</code> in your browser, and log in to the management panel with the above username and password.</p>
<h1 id="modifying-open5gs-configuration-files">Modifying Open5GS Configuration Files</h1>
<p>After Open5GS is installed, you will need to modify the configuration files to match the parameters of our CBRS LTE network. We only need to make the following changes:</p>
<ul>
<li>Change MCC/MNC from the default 999/70 to CBRS's 315/010.</li>
</ul>
<p>Simply search globally for <code>mcc: 999</code> and <code>mnc: 70</code>, and replace them with <code>mcc: 315</code> and <code>mnc: 010</code> respectively:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#mcc: 999#mcc: 315#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#mnc: 70#mnc: 010#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<ul>
<li>Make the MME component listen on <code>eth0</code> (or your actual network card name) interface instead of <code>127.0.0.2</code>, otherwise the base station cannot connect to the core network.</li>
</ul>
<p>Modify <code>open5gs/mme.yaml</code>, change the original configuration under <code>s1ap</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">mme</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  s1ap</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    server</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">127.0.0.2</span></span></code></pre>
<p>To:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">mme</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  s1ap</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    server</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">dev</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">eth0</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Or your actual network card name</span></span></code></pre>
<ul>
<li>(Optional) Customize the network name broadcast by MME.</li>
</ul>
<p>Modify <code>open5gs/mme.yaml</code>, find <code>network_name</code>:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">network_name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  full</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  short</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Next</span></span></code></pre>
<p>Change it to your desired network name, for example:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">network_name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  full</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Lan Tian Mobile</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  short</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">LTMobile</span></span></code></pre>
<p>Finally, restart all Open5GS related services:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">systemctl</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> restart</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs-</span><span style="--shiki-dark:#D7BA7D;--shiki-light:#EE0000">\*</span></span></code></pre>
<h1 id="connecting-freedomfisercomm-base-station-to-open5gs">Connecting FreedomFi/Sercomm Base Station to Open5GS</h1>
<p>First, please ensure that you can log in to the FreedomFi/Sercomm SCE4255 base station's web management panel via IP address. If you cannot access the base station's web management panel, please refer to the <a href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#unlocking-the-management-interface-of-the-indoor-base-station">section on enabling the management panel in my previous post</a>.</p>
<h2 id="disabling-tr-069-remote-management">Disabling TR-069 Remote Management</h2>
<p>FreedomFi's Sercomm base stations by default connect to <code>acs.freedomfi.com</code>, a TR-069 server, to automatically obtain configurations. Although this remote management server was shut down when Helium Mobile discontinued its CBRS network, our base station will still continuously try to connect to this server. When using Magma to build the core network, since the Magma core network itself has TR-069 server functionality, we can keep remote management enabled and simply hijack remote management requests to our TR-069 server. However, Open5GS does not have TR-069 functionality, so we need to disable the base station's TR-069 remote management to avoid unnecessary requests, and prevent the base station's configuration from being accidentally overwritten.</p>
<p>Click <code>TR098</code> at the top of the management interface, then switch to the <code>MgntServer</code> tab to switch to the base station's TR-069 remote management settings page:</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-tr069.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-tr069.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-tr069.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-tr069.png" alt="Sercomm Base Station TR-069 Settings Page"></picture></p>
<p>Uncheck <code>EnableCWMP</code>, then click the <code>Save</code> button to save the settings.</p>
<p>Since the Sercomm base station management panel has some bugs, it is recommended to restart the base station here to ensure the settings take effect. The base station may automatically restart when saving settings, but if it does not, you can manually restart it by clicking the power button in the upper right corner of the management interface, or by manually power cycling it. After restarting, please return to this page and ensure <code>EnableCWMP</code> is unchecked.</p>
<p>At this point, the base station's TR-069 remote management function is disabled, and we can modify settings without fear of being overwritten by remote management.</p>
<h2 id="modifying-base-station-cbrs-sas-connection-configuration">Modifying Base Station CBRS SAS Connection Configuration</h2>
<p>The next step is to connect the base station to the CBRS SAS server to obtain spectrum allocation, thereby avoiding conflicts with other base stations or operator signals, and preventing the FCC from SWATting you. When using the Magma core network, the CBRS SAS connection is automatically configured by Magma's TR-069 server, but since Open5GS does not have TR-069 functionality, this needs to be done manually.</p>
<p>First, ensure your base station is registered with the SAS. You can refer to the <a href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#connecting-the-base-station-to-sas">section on connecting to SAS in my previous post</a>.</p>
<p>Then, click <code>Manage</code> at the top of the base station management interface, then switch to the <code>SAS Configuration</code> tab:</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas.png" alt="Sercomm Base Station SAS Settings Page"></picture></p>
<ul>
<li>Check the <code>Enable</code> option.</li>
<li>Enter <code>0</code> for the <code>Method</code> option.</li>
<li>Select <code>Commercial-Google</code> for <code>Server</code>, corresponding to Google SAS. At this point, <code>Server Url</code> should be automatically populated.</li>
<li>Enter your Google Cloud Project ID for <code>UserID</code>, which can be found on the console homepage: <a href="https://console.cloud.google.com" rel="noopener noreferrer" target="_blank">https://console.cloud.google.com</a></li>
<li>Select <code>A</code> for <code>Category</code>, corresponding to indoor base stations.</li>
<li>Select <code>GAA</code> for <code>ChannelType</code>, corresponding to the lowest priority of the three types of CBRS users.</li>
<li>Enter <code>/C=TW/O=Sercomm/OU=WInnForum CBSD Certificate/CN=P27-SCE4255W:%s</code> for <code>CertSubject</code>.</li>
</ul>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas-location.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas-location.png" alt="Sercomm Base Station SAS Location Settings Page"></picture></p>
<ul>
<li>Select <code>indoor</code> for <code>Location</code>, corresponding to indoor deployment.</li>
<li>If your base station's location has good GPS signal, <code>Location Source</code> can be set to <code>GPS</code>. However, if the GPS signal is poor, the base station will need to wait for GPS positioning to complete before connecting to CBRS SAS and starting to transmit signals after restarting. In this case, you can select <code>Manual</code> and manually enter the base station's latitude and longitude.</li>
<li><code>Latitude</code> is latitude, with positive values for north of equator, and negative values for south of equator. Note that the unit for Sercomm base station's latitude and longitude is microdegrees (i.e., one millionth of a degree), so if you want to set 40 degrees north of equator, please enter <code>40000000</code>.</li>
<li><code>Longitude</code> is longitude, with positive values for east of meridian, and negative values for west of meridian. Note that the unit for Sercomm base station's latitude and longitude is microdegrees (i.e., one millionth of a degree), so if you want to set 80 degrees west of meridian, please enter <code>-80000000</code>.
<ul>
<li>Please obtain the latitude and longitude using your mobile phone or other devices for actual positioning. The base station's location needs to be relatively precise, otherwise it will affect the CBRS SAS spectrum allocation. This latitude and longitude should also be consistent with the latitude and longitude set on the CBRS SAS platform.</li>
</ul>
</li>
<li>Select <code>AMSL</code> for <code>HeightType</code>, which means height above mean sea level.</li>
<li>Enter the base station's altitude for <code>Elevation</code>, in millimeters, so if you want to set 40 meters above sea level, please enter <code>40000</code>.</li>
</ul>
<p>Save the settings. You don't need to restart the base station yet; you can wait until configuring the base station's connection to the Open5GS core network in the next step.</p>
<h2 id="modifying-base-station-core-network-connection-configuration">Modifying Base Station Core Network Connection Configuration</h2>
<p>The next step is to connect the base station to the Open5GS core network to transmit user information and data traffic.</p>
<p>Click <code>Manage</code> at the top of the base station management interface, then switch to the <code>LTE Basic Setting</code> tab:</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas-location.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas-location.png" alt="Sercomm Base Station LTE Settings Page"></picture></p>
<ul>
<li>
<p>Under <code>Cell Configuration</code>:</p>
<ul>
<li>Check the <code>AdminStats</code> option, which means enabling signal transmission.</li>
<li>Select <code>1</code> for <code>Carrier Number</code>.
<ul>
<li>If you select <code>2</code> and adjust the settings below accordingly, you can enable carrier aggregation to double the bandwidth, but Sercomm's CBRS SAS implementation has some issues that may randomly cause signal transmission interruptions.</li>
</ul>
</li>
<li>Do not check the <code>Carrier Aggregation</code> option.
<ul>
<li>If you want to enable carrier aggregation, check this box.</li>
</ul>
</li>
<li>Select <code>20</code> for <code>BandWidth</code> to maximize bandwidth for highest speed.</li>
<li>Enter <code>0</code> for <code>CellIDentity</code>. If you have multiple base stations, you can enter <code>1</code>, <code>2</code>, etc., sequentially, ensuring no duplication between base stations.
<ul>
<li>If you want to enable carrier aggregation, enter <code>0,1</code>, which means two different IDs separated by a comma.</li>
</ul>
</li>
<li>Enter <code>100</code> for <code>PCI</code>. If you have multiple base stations, you can enter <code>101</code>, <code>102</code>, etc., sequentially, ensuring no duplication between base stations.
<ul>
<li>If you want to enable carrier aggregation, enter <code>100,101</code>, which means two different IDs separated by a comma.</li>
</ul>
</li>
<li>Enter <code>24</code> for <code>TxPower</code>.</li>
</ul>
</li>
<li>
<p>Under <code>S1 Configuration</code>:</p>
<ul>
<li>Select IPv4 for <code>Tunnel Type</code>. At this point, data between the base station and the core network is transmitted in plain text.
<ul>
<li>Since our base station and core network are on the same local area network and are physically controlled by us, the security risk here is small. However, if your base station needs to connect to the core network over Internet, you should try using the <code>IPSEC</code> option, but you will need to additionally configure IPSec tunnel related settings.</li>
</ul>
</li>
<li>Enter the IP address of the <code>Open5GS</code> core network machine for <code>MME IP Address</code>.
<ul>
<li>If different components of your <code>Open5GS</code> core network are installed on different machines, enter the IP address of the machine running the MME component here.</li>
</ul>
</li>
<li>Enter <code>315010</code> for <code>PLMNID</code>, corresponding to CBRS's MCC/MNC.</li>
<li>Enter <code>1</code> for <code>TAC</code>.</li>
</ul>
</li>
<li>
<p>If your base station's location has good GPS signal, <code>Sync Source</code> can be set to <code>GPS</code>. However, if the GPS signal is poor, the base station will need to wait for GPS positioning to complete before starting to transmit signals after restarting. In this case, you can select <code>FREE_RUNNING</code>.</p>
</li>
</ul>
<p>Save the settings. It is recommended to restart the base station once here to ensure the settings take effect. The base station may automatically restart when saving settings, but if it does not, you can manually restart it by clicking the power button in the upper right corner of the management interface, or by manually power cycling it.</p>
<p>After restarting, wait a moment and check the base station's indicator lights; the leftmost LTE status indicator light should be a steady blue, indicating that it's now transmitting LTE signals. This completes the base station configuration.</p>
<p>Take out your phone, select any SIM card, turn off the "Automatic Network Selection" option, and the phone will automatically search for nearby mobile networks. If your phone supports LTE band 48,  you should see a network named <code>Lan Tian Mobile</code> (or your own configured network name), which is the signal transmitted by your base station.</p>
<p>The base station management panel should also display <code>henb running</code>, indicating that the base station is running normally:</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-status.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-status.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-status.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-status.png" alt="Sercomm Base Station Status Page"></picture></p>
<h1 id="registering-sim-card-information-with-open5gs">Registering SIM Card Information with Open5GS</h1>
<p>After the core network and base station are running normally, you can register SIM cards with the core network to allow phones and other devices using these SIM cards to connect to the LTE network.</p>
<p>Prepare a few programmable SIM cards and program authentication information to your SIM cards according to the <a href="https://lantian.pub/en/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#programming-sim-cards">SIM card programming tutorial in the previous post</a>. Record the SIM card's IMSI/KI/OPC information.</p>
<p>Log in to Open5GS's web management panel, then click <code>Add a subscriber</code>:</p>
<p><picture><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/open5gs-add-subscriber.png" alt="Open5GS Add SIM Card Interface"></picture></p>
<ul>
<li>Enter the SIM card's corresponding IMSI information for <code>IMSI</code>.</li>
<li>Enter the SIM card's <code>KI</code> for <code>Subscriber Key</code>.</li>
<li>Enter the SIM card's <code>OPC</code> for <code>Operator Key</code>.</li>
</ul>
<p>Keep all other options at their defaults and click Save.</p>
<p>Insert the SIM card into your phone, wait a moment, and your phone should be able to connect to your mobile network.</p>
<h1 id="summary">Summary</h1>
<p>This post mainly records the steps that differ from the Magma core network when setting up Open5GS, as well as some issues specific to setting it up on NixOS. Compared to Magma, Open5GS has a simpler installation process and does not rely on containerization management tools like Docker. If you are using Ubuntu, most of the above process is actually automatically completed during <code>apt install</code>.</p>
<p>From the perspective of LTE terminal devices (e.g., mobile phones), there is no difference in using these two core network software. Both have similar latency and network bandwidth, mainly limited by LTE communication itself. (Except for the strange bug I encountered with Magma where Android phones could not authenticate properly.)</p>
<p>I switched to Open5GS for the management convenience mentioned at the beginning. You can choose either Open5GS or Magma based on your preference.</p>]]></content>
        <published>2025-07-20T12:38:31.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[用 Open5GS 搭建合法的 LTE 网络]]></title>
        <id>https://lantian.pub/article/modify-computer/legal-lte-network-at-home-with-open5gs.lantian/</id>
        <link href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-with-open5gs.lantian/"/>
        <updated>2025-07-20T12:38:31.000Z</updated>
        <content type="html"><![CDATA[<p><a href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/">在上一篇文章中</a>，我用美国的 CBRS 频段和 <a href="https://magmacore.org/" rel="noopener noreferrer" target="_blank">Magma LTE 核心网软件</a>搭建了一套合法的 LTE 网络。</p>
<blockquote>
<p>关于「合法」：我不是律师或者无线电专家。根据我对相关政策法规的研究，我的整套配置应当是合法的。但如果你按照本文操作后遇到了法律问题，我不负任何责任。</p>
</blockquote>
<p>我当时选择 Magma，是因为我买的 CBRS LTE 基站原本用于 Helium Mobile 网络，而 <a href="https://github.com/helium/HIP/blob/main/0139-phase-out-cbrs.md#what-to-do-with-cbrs-radios" rel="noopener noreferrer" target="_blank">Nova Labs/Helium Mobile 使用的 CBRS 核心网就是 Magma</a>。这保证了 Magma 一定兼容我手上的基站。但是，从在 Homelab 里自建核心网的角度来考虑，Magma 存在这些问题：</p>
<ul>
<li>Magma 的核心网依赖 Docker 或者 Kubernetes 进行部署，难以用常规的方式（例如 systemd 服务）在容器外部署。而我是 NixOS 用户，希望尽量避免臃肿的 Docker 容器，而是用 systemd 服务管理系统上的服务。</li>
<li>Magma 的访问网关（Access Gateway）只能安装在 Ubuntu 20.04 系统上，系统管理方式与我常用的 NixOS 完全不同。这意味着我需要手工管理访问网关机器的配置以及系统升级，无法复用我现有的 NixOS 配置。</li>
<li>Magma 有时会出一些奇怪的问题，例如：
<ul>
<li>Android 手机死活连不上基站但 iPhone 没问题；</li>
<li>手机无法正常获取网络名称，网络名称总是显示为 MCC/MNC <code>315 010</code> 而不是我配置的网络名 <code>Lan Tian Mobile</code>；</li>
<li>访问网关明明连上了核心网并且正常同步配置，但核心网管理界面中显示访问网关已经很久没连上了。</li>
</ul>
</li>
</ul>
<p>因此，在上一篇文章完成，确定自建 LTE 网络可行后，我就开始尝试用另一款开源 LTE 核心网软件 <a href="https://open5gs.org/" rel="noopener noreferrer" target="_blank">Open5GS</a> 替换 Magma。</p>
<p>相比 Magma，Open5GS 有这些优点：</p>
<ul>
<li>Open5GS 不区分核心网和访问网关两套组件，只需要一台机器就可以完整部署。</li>
<li>Nixpkgs 中已经有了 Open5GS 软件包（<code>pkgs.open5gs</code>），我不用自己打包就能直接在 NixOS 上安装使用，不需要 Docker 或者 Ubuntu。</li>
<li>Open5GS 没有 Magma 那些奇怪的问题，一旦搭建完成就可以稳定运行。</li>
</ul>
<p>本文记录我在 NixOS 系统上用 Open5GS 搭建核心网，并且用 FreedomFi/Sercomm 的 SCE4255W 基站连接核心网、发射 LTE 信号的过程。</p>
<h1 id="安装-open5gs">安装 Open5GS</h1>
<blockquote>
<p>配置过程参考了以下资料：</p>
<ul>
<li><a href="https://open5gs.org/open5gs/docs/" rel="noopener noreferrer" target="_blank">Open5GS 的官方文档</a></li>
<li>一套打包成开箱即用 Docker 容器的 Open5GS（以及一些附加组件）配置：<a href="https://github.com/herlesupreeth/docker_open5gs" rel="noopener noreferrer" target="_blank">herlesupreeth/docker_open5gs</a></li>
</ul>
</blockquote>
<h2 id="准备工作">准备工作</h2>
<p>本文假定你已经按照我的<a href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/">上一篇文章</a>准备好了这些硬件或软件配置。如果你没有完成这些配置，可以参考上一篇文章中的对应教程配置软件或者购买硬件：</p>
<ul>
<li>一台 FreedomFi/Sercomm 的 SCE4255W 基站，已经解锁 Web 管理界面</li>
<li>基站已经注册到 CBRS SAS 上</li>
<li>一张已经写好认证信息（KI，OPC 等值）的 SIM 卡，并且你记录了这些认证信息（以便稍后注册到 Open5GS）</li>
</ul>
<p>本文基于 NixOS 进行所有配置，但也提供了一些 Ubuntu 相关的命令，以便其它 Linux 发行版的用户参考。</p>
<h2 id="了解-open5gs-的组件">了解 Open5GS 的组件</h2>
<p>Open5GS 如其名，是一套主要实现 5G 核心网（但也实现了 LTE 核心网）的软件。由于 5G 时代的核心网协议和结构与 4G 时代相比有了较大不同，尤其是独立组网的 5G SA 网络，因此 Open5GS 大致上可以看作是一套 LTE/5G NSA 核心网软件，加上一套 5G SA 核心网软件，两者之间共享一小部分组件。</p>
<p>Open5GS 的 LTE/5G NSA 部分由如下组件组成：</p>
<ul>
<li>MME - Mobility Management Entity</li>
<li>HSS - Home Subscriber Server</li>
<li>PCRF - Policy and Charging Rules Function</li>
<li>SGWC - Serving Gateway Control Plane</li>
<li>SGWU - Serving Gateway User Plane</li>
<li>SMF - Session Management Function
<ul>
<li>SMF 本身是 5G 核心网的组件，但 Open5GS SMF 也实现了 4G 核心网结构中的 Packet Gateway Control Plane</li>
</ul>
</li>
<li>UPF - User Plane Function
<ul>
<li>UPF 本身是 5G 核心网的组件，但 Open5GS UPF 也实现了 4G 核心网结构中的 Packet Gateway User Plane</li>
</ul>
</li>
<li>NRF - NF Repository Function
<ul>
<li>NRF 本身是 5G 核心网的组件，但是 SCP 依赖它</li>
</ul>
</li>
<li>SCP - <del>Secure, Contain, Protect</del> Service Communication Proxy
<ul>
<li>SCP 本身是 5G 核心网的组件，但是 SMF 依赖它</li>
</ul>
</li>
</ul>
<p>而 5G SA 部分由如下组件组成：</p>
<ul>
<li>NRF - NF Repository Function</li>
<li>SCP - Service Communication Proxy</li>
<li>SEPP - Security Edge Protection Proxy</li>
<li>AMF - Access and Mobility Management Function</li>
<li>SMF - Session Management Function</li>
<li>UPF - User Plane Function</li>
<li>AUSF - Authentication Server Function</li>
<li>UDM - Unified Data Management</li>
<li>UDR - Unified Data Repository</li>
<li>PCF - Policy and Charging Function</li>
<li>NSSF - Network Slice Selection Function</li>
<li>BSF - Binding Support Function</li>
</ul>
<p>这些组件之间以如下的结构互相通信：</p>
<p><picture><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.webp" type="image/webp"><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.avif" type="image/avif"><source srcset="/usr/uploads/202507/Open5GS_CUPS-01.jpg.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/Open5GS_CUPS-01.jpg" alt="Open5GS 组件结构图"></picture></p>
<p>（图源：<a href="https://open5gs.org/open5gs/docs/guide/01-quickstart/" rel="noopener noreferrer" target="_blank">Open5GS 官方文档</a>）</p>
<p>4G/5G 核心网的各个组件之间通信走的是标准化的 <a href="https://en.wikipedia.org/wiki/Diameter_(protocol)" rel="noopener noreferrer" target="_blank">Diameter 协议</a>，它基于 TCP 或者 <a href="https://zh.wikipedia.org/wiki/%E6%B5%81%E6%8E%A7%E5%88%B6%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE" rel="noopener noreferrer" target="_blank">SCTP</a> 协议，在 4G/5G 核心网的各个组件之间交换数据。这也意味着来自不同厂商的软硬件，只要支持 Diameter 协议，就都可以加入同一个核心网中，共同为移动用户提供服务。</p>
<p>但本文中我将全程使用 Open5GS 的组件，暂时不将别的组件加入核心网。</p>
<h2 id="安装-open5gs-软件包">安装 Open5GS 软件包</h2>
<p>如果你用的是 Ubuntu，可以参考 <a href="https://open5gs.org/open5gs/docs/guide/01-quickstart/" rel="noopener noreferrer" target="_blank">Open5GS 的官方安装教程</a>：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 安装 MongoDB</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://pgp.mongodb.com/server-8.0.asc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -o</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /usr/share/keyrings/mongodb-server-8.0.gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --dearmor</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/sources.list.d/mongodb-org-8.0.list</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> mongodb-org</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 安装 Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> add-apt-repository</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ppa:open5gs/latest</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs</span></span></code></pre>
<p>这个过程中除了安装了 Open5GS 的二进制文件，还创建了一组 systemd 服务对应 Open5GS 的各个组件，以及将 Open5GS 的默认配置复制到了 <code>/etc</code> 下。</p>
<p>由于 NixOS 中只有 Open5GS 的软件包，没有对应的 NixOS 模块，因此我们需要模仿在 Ubuntu 等其它系统上安装的过程，手动为 Open5GS 创建 systemd 服务：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{ </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, ... }:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 由于我们只搭建 4G 核心网，只开启 4G 核心网需要的服务</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "hss"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "mme"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "nrf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "pcrf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "scp"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "sgwc"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "sgwu"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "smf"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    "upf"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ];</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">in</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 开启 MongoDB，HSS、PCF、PCRF 组件需要用 MongoDB 保存配置</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">mongodb</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    enable</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    bind_ip</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"127.0.0.1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    package</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">mongodb-ce</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 创建 Open5GS 各组件的 systemd 服务</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">listToAttrs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">map</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        description</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Open5GS </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">toUpper</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> Daemon"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">        serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">          # 这里指向的 open5gs 文件夹下的配置文件我们下一步再创建</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d -c </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.yaml"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          ExecReload</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">coreutils</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/kill -HUP $MAINPID"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          LogsDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          Restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"always"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          RestartSec</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          RestartPreventExitStatus</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    }) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">services</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  );</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 创建一个单独的用户和组给 Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    isSystemUser</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">true</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  users</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">groups</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = { };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 创建一个名为 ogstun 的 TUN 接口，用于与 LTE 设备通信</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">network</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">netdevs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    netdevConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Kind</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"tun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"ogstun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">network</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">networks</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # 这里用的 IP 地址和 Open5GS 默认配置中的相同</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "10.45.0.1/16"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "2001:db8:cafe::1/48"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    linkConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      MTUBytes</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1400</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      RequiredForOnline</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">false</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    matchConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">Name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"ogstun"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="创建-open5gs-配置文件">创建 Open5GS 配置文件</h2>
<p>如果你用的是 Ubuntu，上面的安装过程应该已经自动将默认配置文件安装到了 <code>/etc/freeDiameter</code> 和 <code>/etc/open5gs</code> 下。但在 NixOS 中，这个过程不会自动完成，我们需要手动复制配置文件，或者手动指定配置文件的路径。</p>
<p>由于 Nixpkgs 的 Open5GS 软件包已经自带了一组默认配置，我们可以直接从这个包里复制默认配置文件。首先构建软件包：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">nix</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nixpkgs#open5gs</span></span></code></pre>
<p>不出意外，Nix 会从 Binary Cache 里下载预先编译好的 Open5GS，并且把它软链接到 <code>result</code> 目录下。此时我们在 <code>result/etc</code> 文件夹下就可以看到默认的配置文件了：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">ls</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc</span></span></code></pre>
<p>然后我们就可以把它们复制到自己的 NixOS 配置中，以便后续修改：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -r</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc/freeDiameter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/freeDiameter</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -r</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> result/etc/open5gs</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/open5gs</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 从 Nix store 中复制出的文件默认是只读的，给它们加上写权限</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">chmod</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -R</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> +w</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/freeDiameter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /path/to/your/nixos-config/open5gs</span></span></code></pre>
<p>对于 <code>freeDiameter</code> 文件夹中的文件，我们需要把它们放到 <code>/etc/freeDiameter</code> 下：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">etc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"freeDiameter"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">source</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./freeDiameter</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>对于 <code>open5gs</code> 文件夹中的文件，可以在启动 Open5GS 时使用 <code>-c</code> 参数直接指定配置文件的为止：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/open5gs-</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">d -c </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">./open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">svc</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.yaml"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>不把它们放到 <code>/etc</code> 中，是为了保证修改配置文件后，Open5GS 服务会自动重启。</p>
<h2 id="修复-nixos-下-open5gs-配置文件中的路径">修复 NixOS 下 Open5GS 配置文件中的路径</h2>
<p>由于 Nixpkgs 中打包的 Open5GS 默认安装到 <code>/nix/store</code> 中的一个路径下，因此它的配置文件中也默认包含了很多 <code>/nix/store</code> 下的路径。</p>
<p>首先获取 Open5GS 的实际安装路径：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">nix</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nixpkgs#open5gs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --print-out-paths</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --no-link</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 输出类似：</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/</span></span></code></pre>
<p>然后在复制出来的配置文件中搜索这个路径，可以看到有很多处包含了完整的路径：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">grep</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 引用 Open5GS 构建过程中默认生成的 TLS 证书</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter/hss.conf:TLS_Cred = "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/hss.crt", "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/hss.key";</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 引用 freeDiameter 的 Extension</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter/hss.conf:LoadExtension = "/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/lib/freeDiameter/dbg_msg_dumps.fdx" : "0x8888";</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 默认日志路径被放到了 Nix store 中</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># open5gs/hss.yaml:    path: /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/var/log/open5gs/hss.log</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter 的配置文件路径被设置到了 Nix store 中</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># open5gs/hss.yaml:  freeDiameter: /nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/freeDiameter/hss.conf</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># ...</span></span></code></pre>
<p>一旦 Open5GS 软件包或者它的依赖更新，Open5GS 在 Nix store 中的路径就会发生变动，导致以绝对路径指定的文件失效，从而导致 Open5GS 无法启动。因此，我们需要让这些路径和 Open5GS 的路径保持同步，或者指向 Nix store 之外，以防止未来出现问题。</p>
<p>我用的方法是，首先把 <code>pkgs.open5gs</code> 软件包链接一份到 <code>/etc</code> 里：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">etc</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-pkg"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">source</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>然后修改上述路径：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># TLS 证书指向 /etc/open5gs-pkg。虽然这个证书是从 Nixpkgs Binary Cache 下载的，私钥可以视为公开，但我们单机部署，通信不经过外部网络，因此加密无关紧要</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/#/etc/open5gs-pkg/etc/open5gs/tls/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter Extension 指向 /etc/open5gs-pkg</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/lib/freeDiameter/#/etc/open5gs-pkg/lib/freeDiameter/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># /var 中的路径指向实际的 /var</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/var/#/var/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># freeDiameter 配置文件指向 /etc/freeDiameter</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/freeDiameter/#/etc/freeDiameter/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<p>修改完成后，就可以保证 Open5GS 未来升级时不会出现问题，同时我们放在 <code>/etc</code> 的配置文件可以正常生效。</p>
<h2 id="可选重新生成-diameter-的-tls-证书">（可选）重新生成 Diameter 的 TLS 证书</h2>
<p>Nixpkgs 中打包的 Open5GS 自带了一份在构建过程中生成的 TLS 证书。如果你的 Open5GS 是从 Binary Cache 下载的，而不是本地编译的，那么其他人也可以从 Binary Cache 上下载到同一份密钥。</p>
<p>如果你按照本教程的流程单机部署，因为所有的通信都在本地，不会经过外部网络，所以有没有加密、私钥是否泄露对安全性没什么影响。</p>
<p>但如果你准备将一部分组件放到别的机器上，或者你不想使用这份已经泄露的密钥，你也可以用如下的脚本生成一份新的：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs-certs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">with</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; [ </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">openssl</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    script</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">''</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      mkdir -p demoCA</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      if [ ! -f "demoCA/serial" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        echo 01 > demoCA/serial</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      fi</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      touch demoCA/index.txt</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      # CA self certificate</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      if [ ! -f "ca.crt" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        openssl req -new -x509 -days 3650 -newkey rsa:2048 -nodes -keyout ca.key -out ca.crt \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          -subj /CN=ca.epc.mnc010.mcc315.3gppnetwork.org/C=KO/ST=Seoul/O=NeoPlane</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      fi</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      for i in amf ausf bsf hss mme nrf scp sepp1 sepp2 sepp3 nssf pcf pcrf smf udm udr</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      do</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        if [ ! -f "$i.crt" ]; then</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -out $i.key</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl req -new -key $i.key -out $i.csr \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -subj /CN=$i.epc.mnc010.mcc315.3gppnetwork.org/C=KO/ST=Seoul/O=NeoPlane</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">          openssl ca -batch -notext -days 3650 \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -keyfile ca.key -cert ca.crt \</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">              -in $i.csr -out $i.crt -outdir .</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">        fi</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      done</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    ''</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Type</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"oneshot"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      StateDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs-certs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      WorkingDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"/var/lib/open5gs-certs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>当运行 <code>systemctl start open5gs-certs.service</code> 时，这个服务就会自动在 <code>/var/lib/open5gs-certs</code> 中生成缺失的密钥。</p>
<p>然后你就可以修改 Open5GS 的配置文件，将 TLS 密钥路径指向 <code>/var/lib/open5gs-certs</code>：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 如果你在上一步没有替换 TLS 密钥的路径</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/nix/store/vbb0aa2mkjbfay7gdgaw5r23g0ss6kyz-open5gs-v2.7.6/etc/open5gs/tls/#/var/lib/open5gs-certs/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 如果你在上一步已经替换了 TLS 密钥的路径</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#/etc/open5gs-pkg/etc/open5gs/tls/#/var/lib/open5gs-certs/#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> freeDiameter/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<p>你也可以把 <code>open5gs-certs.service</code> 加到 Open5GS 各个 systemd 服务的 <code>After</code> 和 <code>Requires</code> 里，从而保证 Open5GS 启动时密钥已经生成完毕。</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "open5gs-certs.service"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "open5gs-certs.service"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="安装-open5gs-的-web-管理面板">安装 Open5GS 的 Web 管理面板</h2>
<p>上面的步骤配置了 Open5GS 核心网本身，但我们还需要安装管理面板 WebUI，以管理注册到 Open5GS 的 SIM 卡相关信息。</p>
<p>如果你用的是 Ubuntu，可以使用 Open5GS 官方的一键安装脚本：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 下载 Nodesource 的 GPG 密钥</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -y</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ca-certificates</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> curl</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gnupg</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> mkdir</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -p</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/keyrings</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> gpg</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --dearmor</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -o</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/keyrings/nodesource.gpg</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 添加 NodeJS 软件包仓库</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">NODE_MAJOR</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">20</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">$NODE_MAJOR</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">.x nodistro main"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/apt/sources.list.d/nodesource.list</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 安装 NodeJS</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> update</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> apt</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> install</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nodejs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -y</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000"># 安装 Open5GS WebUI</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">curl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -fsSL</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://open5gs.org/open5gs/assets/webui/install</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -E</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> bash</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span></span></code></pre>
<p>如果你用的是 NixOS，可以用以下配置安装：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">  config</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">,</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  ...</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  systemd</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">services</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">open5gs-webui</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    description</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Open5GS WebUI"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    wantedBy</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [ </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"multi-user.target"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    after</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    requires</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = [</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "network.target"</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      "mongodb.service"</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    path</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">with</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; [</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      bash</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      nodejs</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      rsync</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    ];</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    environment</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      HOSTNAME</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"0.0.0.0"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      PORT</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"9999"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    preStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">''</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      export HOME=$(pwd)</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      rsync -r --chmod=D755,F755 </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">open5gs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">src</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/webui/ .</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      npm install</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">      npm run build</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    ''</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    serviceConfig</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      ExecStart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">${</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pkgs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">nodejs</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">/bin/npm run start"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      CacheDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      WorkingDirectory</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"/var/cache/open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      User</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Group</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"open5gs"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      Restart</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"always"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      RestartSec</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"5"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">    };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="启动-open5gs">启动 Open5GS</h2>
<p>将上面的配置部署到你的 NixOS 机器上，不出意外这些服务都应该正常启动。</p>
<p>如果你用的是 Ubuntu，那么在安装 <code>open5gs</code> 软件包时，4G/5G 的所有服务都应该已经自动启动了。你可以禁用掉我们用不到的 5G SA 相关的服务，也可以不管它们，它们对后续配置没有任何影响。</p>
<h2 id="创建管理面板的默认管理员">创建管理面板的默认管理员</h2>
<p>Open5GS 启动时并不会自动创建默认的管理员用户，所以在配置部署完成，MongoDB 已经启动之后，我们需要手动运行下面的命令来创建管理员：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cat</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;&#x3C;</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EOF</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">mongosh</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">db = db.getSiblingDB('open5gs')</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">cursor = db.accounts.find()</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">if ( cursor.count() == 0 ) {</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">    db.accounts.insertOne({ salt: 'f5c15fa72622d62b6b790aa8569b9339729801ab8bda5d13997b5db6bfc1d997', hash: '402223057db5194899d2e082aeb0802f6794622e1cbc47529c419e5a603f2cc592074b4f3323b239ffa594c8b756d5c70a4e1f6ecd3f9f0d2d7328c4cf8b1b766514effff0350a90b89e21eac54cd4497a169c0c7554a0e2cd9b672e5414c323f76b8559bc768cba11cad2ea3ae704fb36abc8abc2619231ff84ded60063c6e1554a9777a4a464ef9cfdfa90ecfdacc9844e0e3b2f91b59d9ff024aec4ea1f51b703a31cda9afb1cc2c719a09cee4f9852ba3cf9f07159b1ccf8133924f74df770b1a391c19e8d67ffdcbbef4084a3277e93f55ac60d80338172b2a7b3f29cfe8a36738681794f7ccbe9bc98f8cdeded02f8a4cd0d4b54e1d6ba3d11792ee0ae8801213691848e9c5338e39485816bb0f734b775ac89f454ef90992003511aa8cceed58a3ac2c3814f14afaaed39cbaf4e2719d7213f81665564eec02f60ede838212555873ef742f6666cc66883dcb8281715d5c762fb236d72b770257e7e8d86c122bb69028a34cf1ed93bb973b440fa89a23604cd3fefe85fbd7f55c9b71acf6ad167228c79513f5cfe899a2e2cc498feb6d2d2f07354a17ba74cecfbda3e87d57b147e17dcc7f4c52b802a8e77f28d255a6712dcdc1519e6ac9ec593270bfcf4c395e2531a271a841b1adefb8516a07136b0de47c7fd534601b16f0f7a98f1dbd31795feb97da59e1d23c08461cf37d6f2877d0f2e437f07e25015960f63', username: 'admin', roles: [ 'admin' ], "__v" : 0})</span></span>
<span class="line"><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">}</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EOF</span></span></code></pre>
<p>（来源：<a href="https://github.com/open5gs/open5gs/blob/main/docs/assets/webui/mongo-init.js" rel="noopener noreferrer" target="_blank">https://github.com/open5gs/open5gs/blob/main/docs/assets/webui/mongo-init.js</a>）</p>
<p>以上命令会创建一个用户名为 <code>admin</code>，密码为 <code>1423</code> 的管理员用户。</p>
<p>用浏览器打开 <code>http://[Open5GS 机器的 IP 地址]:9999</code>，就可以用上述用户名密码登录管理面板。</p>
<h1 id="修改-open5gs-的配置文件">修改 Open5GS 的配置文件</h1>
<p>Open5GS 安装完成后，就可以修改配置文件，使其符合我们的 CBRS LTE 网络的参数。我们只需要做如下修改：</p>
<ul>
<li>将 MCC/MNC 从默认的 999/70 修改成 CBRS 的 315/010</li>
</ul>
<p>直接全局搜索 <code>mcc: 999</code> 和 <code>mnc: 70</code>，然后将它们替换成 <code>mcc: 315</code> 和 <code>mnc: 010</code> 即可：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#mcc: 999#mcc: 315#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sed</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "s#mnc: 70#mnc: 010#g"</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs/</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span></span></code></pre>
<ul>
<li>让 MME 组件在 <code>eth0</code>（或者你的实际网卡名）接口上监听，而不是 <code>127.0.0.2</code>，否则基站无法连上核心网</li>
</ul>
<p>修改 <code>open5gs/mme.yaml</code>，将 <code>s1ap</code> 下原本的配置：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">mme</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  s1ap</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    server</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">address</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">127.0.0.2</span></span></code></pre>
<p>修改成：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">mme</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  s1ap</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">    server</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      - </span><span style="--shiki-dark:#569CD6;--shiki-light:#800000">dev</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">eth0</span><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 或者你的实际网卡名</span></span></code></pre>
<ul>
<li>（可选）自定义 MME 广播的网络名。</li>
</ul>
<p>修改 <code>open5gs/mme.yaml</code>，找到 <code>network_name</code>：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">network_name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  full</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Open5GS</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  short</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Next</span></span></code></pre>
<p>改成你想要的网络名即可，例如：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">network_name</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  full</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">Lan Tian Mobile</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#800000">  short</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#CE9178;--shiki-light:#0000FF">LTMobile</span></span></code></pre>
<p>最后，重启所有 Open5GS 相关的服务：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">systemctl</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> restart</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> open5gs-</span><span style="--shiki-dark:#D7BA7D;--shiki-light:#EE0000">\*</span></span></code></pre>
<h1 id="将-freedomfisercomm-基站连上-open5gs">将 FreedomFi/Sercomm 基站连上 Open5GS</h1>
<p>首先，请确保你可以通过 IP 地址登录 FreedomFi/Sercomm SCE4255 基站的 Web 管理面板。如果无法访问基站的 Web 管理面板，请参考<a href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#%E8%A7%A3%E9%94%81%E5%AE%A4%E5%86%85%E5%9F%BA%E7%AB%99%E7%9A%84%E7%AE%A1%E7%90%86%E7%95%8C%E9%9D%A2">我的上一篇文章</a>开启管理面板。</p>
<h2 id="关闭-tr-069-远程管理">关闭 TR-069 远程管理</h2>
<p>FreedomFi 出售的 Sercomm 基站默认会连接 <code>acs.freedomfi.com</code> 这个 TR-069 服务器，从 TR-069 服务器自动获取配置。虽然这个远程管理服务器随着 Helium Mobile 停用 CBRS 网络而关闭，但我们的基站仍然会不停尝试连接远程管理。在用 Magma 搭建核心网时，由于 Magma 核心网自带 TR-069 服务器的功能，所以我们可以保持远程管理开启，只需要将远程管理劫持到我们的 TR-069 服务器即可。但 Open5GS 没有 TR-069 的功能，所以我们要关闭基站的 TR-069 远程管理，避免不必要的请求，并防止基站的配置被意外覆盖。</p>
<p>在管理界面的顶部点击 <code>TR098</code>，然后点击 <code>MgntServer</code> 标签页，切换到基站的 TR-069 远程管理设置页面：</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-tr069.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-tr069.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-tr069.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-tr069.png" alt="Sercomm 基站的 TR-069 设置页面"></picture></p>
<p>取消勾选 <code>EnableCWMP</code>，然后点击 <code>Save</code> 按钮保存设置。</p>
<p>由于 Sercomm 基站管理面板的 Bug 有点多，所以这里建议重启一次基站以保证设置生效。保存设置时基站可能会自动重启，但如果基站没有重启，可以点击管理界面右上角的电源按钮手动重启一次，或者手动断电重启。重启完后请再次回到此页面并保证 <code>EnableCWMP</code> 是关闭状态。</p>
<p>此时，基站的 TR-069 远程管理功能就关闭了，我们就可以随意修改设置，不怕被远程管理覆盖了。</p>
<h2 id="修改基站的-cbrs-sas-连接配置">修改基站的 CBRS SAS 连接配置</h2>
<p>下一步是让基站连接 CBRS SAS 服务器，获取频段分配，从而避免和其它基站或者运营商的信号发生冲突，以及避免 FCC 上门和你玩彩虹六号。在使用 Magma 核心网时，CBRS SAS 的连接配置由 Magma 的 TR-069 服务器自动下发，但由于 Open5GS 没有 TR-069 的功能，这部分就需要我们手动设置了。</p>
<p>首先确保你的基站已经注册到了 SAS 上，可以参考<a href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#%E5%9F%BA%E7%AB%99%E8%BF%9E%E6%8E%A5-sas">我的上一篇文章中，连接 SAS 的部分</a>。</p>
<p>然后，在基站管理界面的顶部点击 <code>Manage</code>，然后点击 <code>SAS Configuration</code> 标签页：</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas.png" alt="Sercomm 基站的 SAS 设置页面"></picture></p>
<ul>
<li><code>Enable</code> 选项打勾。</li>
<li><code>Method</code> 选项输入 0。</li>
<li><code>Server</code> 选择 <code>Commercial-Google</code>，对应 Google SAS。此时 <code>Server Url</code> 应该会自动填充。</li>
<li><code>UserID</code> 输入你的 Google Cloud Project ID，可以在控制台主页看到：<a href="https://console.cloud.google.com" rel="noopener noreferrer" target="_blank">https://console.cloud.google.com</a></li>
<li><code>Category</code> 选择 <code>A</code>，对应室内基站。</li>
<li><code>ChannelType</code> 选择 <code>GAA</code>，对应 CBRS 三类用户中优先级最低的一类。</li>
<li><code>CertSubject</code> 输入 <code>/C=TW/O=Sercomm/OU=WInnForum CBSD Certificate/CN=P27-SCE4255W:%s</code></li>
</ul>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas-location.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas-location.png" alt="Sercomm 基站的 SAS 位置设置页面"></picture></p>
<ul>
<li><code>Location</code> 选择 <code>indoor</code>，对应室内部署。</li>
<li>如果你的基站所在位置 GPS 信号良好，<code>Location Source</code> 可以选择 <code>GPS</code>。但如果 GPS 信号差，基站重启后需要等待 GPS 定位完成后才会连接 CBRS SAS 并开始发送信号。此时你可以选择 <code>Manual</code> 并手动输入基站的经纬度。</li>
<li><code>Latitude</code> 是纬度，正数为北纬，负数为南纬。注意 Sercomm 基站的经纬度单位是微度（即百万分之一度），所以如果你要设置北纬 40 度，请输入 <code>40000000</code>.</li>
<li><code>Longitude</code> 是经度，正数为东经，负数为西经。注意 Sercomm 基站的经纬度单位是微度（即百万分之一度），所以如果你要设置西经 80 度，请输入 <code>-80000000</code>.
<ul>
<li>经纬度请用你的手机等设备实际定位得到，基站的位置需要比较精确，否则会影响到 CBRS SAS 的频段分配。同时这个经纬度应该和 CBRS SAS 平台上设置的经纬度一致。</li>
</ul>
</li>
<li><code>HeightType</code> 选择 <code>AMSL</code>，即相对海平面的高度。</li>
<li><code>Elevation</code> 输入基站的海拔高度，单位是毫米，所以如果你要设置海平面以上 40 米，请输入 <code>40000</code>。</li>
</ul>
<p>保存设置。暂时不用重启基站，下一步配置完基站到 Open5GS 核心网的连接后再一起重启。</p>
<h2 id="修改基站的核心网连接配置">修改基站的核心网连接配置</h2>
<p>下一步是让基站连接 Open5GS 核心网，从而传输用户信息和数据流量。</p>
<p>在基站管理界面的顶部点击 <code>Manage</code>，然后点击 <code>LTE Basic Setting</code> 标签页：</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-sas-location.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-sas-location.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-sas-location.png" alt="Sercomm 基站的 LTE 设置页面"></picture></p>
<ul>
<li>
<p><code>Cell Configuration</code> 下：</p>
<ul>
<li><code>AdminStats</code> 选项打勾，代表启用信号发射。</li>
<li><code>Carrier Number</code> 选择 1。
<ul>
<li>如果选择 2 并相应调整下面的设置，可以启用载波聚合，让带宽翻倍，但是这种情况下 Sercomm 的 CBRS SAS 实现有点问题，可能会随机导致信号发射中断。</li>
</ul>
</li>
<li><code>Carrier Aggregation</code> 选项不要打勾。
<ul>
<li>如果你想开载波聚合，此处打勾。</li>
</ul>
</li>
<li><code>BandWidth</code> 选择 20，把带宽拉满获得最高速度。</li>
<li><code>CellIDentity</code> 输入 <code>0</code>。如果你有多个基站，可以依次输入 <code>1</code>，<code>2</code> 等等，基站之间不要重复。
<ul>
<li>如果你想开载波聚合，输入 <code>0,1</code>，即逗号分隔的两个不同的 ID。</li>
</ul>
</li>
<li><code>PCI</code> 输入 <code>100</code>。如果你有多个基站，可以依次输入 <code>101</code>，<code>102</code> 等等，基站之间不要重复。
<ul>
<li>如果你想开载波聚合，输入 <code>100,101</code>，即逗号分隔的两个不同的 ID。</li>
</ul>
</li>
<li><code>TxPower</code> 输入 <code>24</code>。</li>
</ul>
</li>
<li>
<p><code>S1 Configuration</code> 下：</p>
<ul>
<li><code>Tunnel Type</code> 选择 IPv4。此时基站到核心网之间的数据是明文传输。
<ul>
<li>由于我们的基站和核心网在同一个局域网下，都由我们物理控制，所以这里的安全风险不大。但如果你的基站需要通过互联网连接到核心网，你应该尝试使用 <code>IPSEC</code> 选项，但相应的你需要额外配置 IPSec 隧道的相关设置。</li>
</ul>
</li>
<li><code>MME IP Address</code> 输入 <code>Open5GS</code> 核心网机器的 IP 地址。
<ul>
<li>如果你的 <code>Open5GS</code> 核心网的不同组件安装在不同机器上，此处输入运行 MME 组件机器的 IP 地址。</li>
</ul>
</li>
<li><code>PLMNID</code> 输入 <code>315010</code>，对应 CBRS 的 MCC/MNC。</li>
<li><code>TAC</code> 输入 <code>1</code>。</li>
</ul>
</li>
<li>
<p>如果你的基站所在位置 GPS 信号良好，<code>Sync Source</code> 可以选择 <code>GPS</code>。但如果 GPS 信号差，基站重启后需要等待 GPS 定位完成后才会开始发送信号。此时你可以选择 <code>FREE_RUNNING</code>。</p>
</li>
</ul>
<p>保存设置，这里建议重启一次基站以保证设置生效。保存设置时基站可能会自动重启，但如果基站没有重启，可以点击管理界面右上角的电源按钮手动重启一次，或者手动断电重启。</p>
<p>重启完成后，稍等片刻，看一下基站的指示灯，最左侧的 LTE 状态指示灯应该是蓝灯常亮，代表此时已经在发射 LTE 信号。到这里，基站的配置就全部完成了。</p>
<p>拿出你的手机，随便选择一张 SIM 卡，关闭「自动选择网络」选项，手机就会自动搜索附近的移动网络。如果你的手机支持 LTE 48 频段，你应该就能看到一个名为 <code>Lan Tian Mobile</code>（或者你自己配置的网络名称）的网络，这就是你的基站发射的信号。</p>
<p>基站管理面板上也应该显示 <code>henb running</code>，代表基站运行正常：</p>
<p><picture><source srcset="/usr/uploads/202507/sercomm-status.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/sercomm-status.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/sercomm-status.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/sercomm-status.png" alt="Sercomm 基站状态页面"></picture></p>
<h1 id="将-sim-卡信息注册到-open5gs-上">将 SIM 卡信息注册到 Open5GS 上</h1>
<p>核心网和基站正常运行后，就可以将 SIM 卡注册到核心网上，让使用这些 SIM 卡的手机等设备连接 LTE 网络了。</p>
<p>准备几张可编程 SIM 卡，按照<a href="https://lantian.pub/article/modify-computer/legal-lte-network-at-home-for-100-bucks.lantian/#%E5%86%99-sim-%E5%8D%A1">上一篇文章中的写 SIM 卡教程</a>给你的 SIM 卡写入认证信息。记录下 SIM 卡的 IMSI/KI/OPC 信息。</p>
<p>登录 Open5GS 的 Web 管理面板，然后点击 <code>Add a subscriber</code>：</p>
<p><picture><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.webp" type="image/webp"><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.avif" type="image/avif"><source srcset="/usr/uploads/202507/open5gs-add-subscriber.png.jxl" type="image/jxl"><img src="https://lantian.pub/usr/uploads/202507/open5gs-add-subscriber.png" alt="Open5GS 添加 SIM 卡界面"></picture></p>
<ul>
<li><code>IMSI</code> 输入 SIM 卡对应的 IMSI 信息。</li>
<li><code>Subscriber Key</code> 输入 SIM 卡的 <code>KI</code>。</li>
<li><code>Operator Key</code> 输入 SIM 卡的 <code>OPC</code>。</li>
</ul>
<p>其它选项全部保持默认，点击 Save 保存。</p>
<p>把 SIM 卡插到你的手机上，稍等片刻，手机就应该可以连上你的移动网络了。</p>
<h1 id="总结">总结</h1>
<p>本文主要记录搭建 Open5GS 时与 Magma 核心网不同的步骤，以及在 NixOS 上搭建时特有的一些问题。相比 Magma，Open5GS 的安装流程更加简单，而且不依赖 Docker 等容器化管理工具。如果你用的是 Ubuntu，上面的大部分流程其实在 <code>apt install</code> 时就已经自动完成。</p>
<p>从 LTE 终端设备（例如手机）的角度来看，使用这两款核心网软件并没有什么区别，两者的延迟、网络带宽都没有大的差别，主要还是受到 LTE 通信本身的限制。（除了我使用 Magma 时遇到的，Android 手机无法正常认证的奇怪 Bug）</p>
<p>我切换到 Open5GS，也是为了开头提到的管理上的便利。你可以根据自己的喜好，选择 Open5GS 或者 Magma 之一。</p>]]></content>
        <published>2025-07-20T12:38:31.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using SideStore without StosVPN across your LAN]]></title>
        <id>https://lantian.pub/en/article/modify-computer/sidestore-without-stosvpn-across-lan.lantian/</id>
        <link href="https://lantian.pub/en/article/modify-computer/sidestore-without-stosvpn-across-lan.lantian/"/>
        <updated>2025-06-27T00:47:31.000Z</updated>
        <content type="html"><![CDATA[<p><strong>2026-05-01 update:</strong> Added Nftables rule that apply to the entire network, provided by <a href="https://github.com/KusakabeShi" rel="noopener noreferrer" target="_blank">@KusakabeShi</a>.</p>
<h2 id="foreword">Foreword</h2>
<p><a href="https://github.com/SideStore/SideStore" rel="noopener noreferrer" target="_blank">SideStore</a> is a commonly used iOS app sideloading tool that allows you to install third-party apps bypassing the App Store. It works by using your Apple ID to obtain a free Apple developer certificate, which is then used to sign the app you want to install, allowing it to run normally on your iOS device.</p>
<p>However, to maintain control over the iOS ecosystem, Apple prevents third-party app stores from using developer certificates to bypass restrictions on a large scale, setting a 7-day expiration period for developer certificates. Users need to regularly obtain new developer certificates and re-sign their apps to continue using the third-party apps they have installed.</p>
<p>Traditional sideloading tools, such as AltStore, rely on software like iTunes on a computer for the re-signing process. But unlike other sideloading tools, SideStore only requires computer assistance for the initial installation. After installation, SideStore can simulate a computer with iTunes installed, allowing the iOS system to communicate with it through a virtual network, thus achieving the effect of re-signing apps and even installing new third-party apps without a computer.</p>
<p>SideStore's virtual network can generally be implemented in the following two ways:</p>
<ul>
<li>WireGuard: SideStore can create a WireGuard server on the device itself. Users can install a WireGuard client and connect to this server, allowing the iOS system to communicate with the simulated computer over the network.
<ul>
<li>The disadvantage of this method is that due to iOS system limitations, when the iPhone/iPad is using cellular data, the WireGuard client cannot connect to the WireGuard server created locally by SideStore. Therefore, SideStore only works properly when the device is connected to Wi-Fi.</li>
<li>Also, since the iOS system only supports connecting to one VPN at a time, if the user needs to use another VPN software, they have to manually switch between VPNs, which is quite troublesome.</li>
</ul>
</li>
<li><a href="https://github.com/SideStore/StosVPN" rel="noopener noreferrer" target="_blank">StosVPN</a>: A dedicated VPN client developed by the SideStore team that works exclusively for SideStore.
<ul>
<li>Compared to WireGuard, StosVPN is not affected by iOS restrictions and can work normally when the device is using cellular data. However, after trying it out, I found that StosVPN often disconnects automatically and cannot stay in the background for a long time. If the iOS device is not used for a while and StosVPN disconnects, and SideStore and other third-party apps fail to renew in time, you will have to find a computer to sign these apps again.</li>
<li>Also, since StosVPN is also a VPN, it is also subject to the iOS's limitation of only supporting one VPN connection at a time.</li>
</ul>
</li>
</ul>
<p>So I wanted to try to analyze the working principles of SideStore/StosVPN to see if I could integrate them into my home network or ZeroTier SDN network, allowing SideStore to refresh normally without extra VPN configuration.</p>
<h2 id="how-stosvpn-works">How StosVPN Works</h2>
<p>According to <a href="https://github.com/SideStore/StosVPN/blob/main/TunnelProv/PacketTunnelProvider.swift" rel="noopener noreferrer" target="_blank">StosVPN's packet processing logic</a>, StosVPN roughly does the following:</p>
<ul>
<li>Assigns IP address <code>10.7.0.0</code> to the iOS device, and configure iOS to send packets for <code>10.7.0.0/24</code> to StosVPN.</li>
<li>Defines an IP address <code>10.7.0.1</code>, where StosVPN will simulate a computer with iTunes installed.</li>
<li>For each packet:
<ul>
<li>If the packet is sent from <code>10.7.0.0</code> to <code>10.7.0.1</code>, swap the source and destination IP addresses, to send the packet back to the iOS device.</li>
</ul>
</li>
</ul>
<p>This logic is quite simple. SideStore essentially opens some ports locally on the iOS device, simulating a computer with iTunes installed. Suppose iOS creates a connection like this when trying to connect to the simulated computer:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.0:12345</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.1:54321</span></span></code></pre>
<p>Then WireGuard or StosVPN will swap the source and destination IP addresses (but not the port numbers), rewrite the packet as follows, and send it back to the iOS device:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.1:12345</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.0:54321</span></span></code></pre>
<p>From the iOS device's perspective, this is a new TCP connection from <code>10.7.0.1</code>, unrelated to the previous connection sent to the computer. Since the port iOS is trying to connect to (<code>54321</code> in this case) should be an iTunes port, and SideStore simulates iTunes locally, SideStore should be listening on port <code>54321</code> at this time and receiving the data.</p>
<p>After SideStore's simulated iTunes logic processes the data and generates a reply:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.0:54321</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.1:12345</span></span></code></pre>
<p>WireGuard or StosVPN will again swap the source and destination IP addresses:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.1:54321</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.0:12345</span></span></code></pre>
<p>This reply packet matches the initial connection sent to the simulated computer. iOS therefore believes it has received a reply from iTunes on the computer, and thus continues updating the developer certificate.</p>
<h2 id="simulating-stosvpns-working-logic-with-nftables">Simulating StosVPN's Working Logic with Nftables</h2>
<p>Now understanding how StosVPN works, we just need to mimic its logic in our own network.</p>
<p>If you only have a few iOS devices, and they are all assigned static IP addresses, and you have a router running OpenWrt or another Linux system, you can simply use the following Nftables rules:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> inet</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  chain</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> RAW_PREROUTING</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    type</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> filter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> hook</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> prerouting</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> priority</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> raw</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">policy</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # Replace 192.168.0.xxx here with your iOS device's IP address</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.123</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.123</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.234</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.234</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # Add more rules as needed</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>The purpose of the above rules is that if a packet is received from your iOS device (<code>192.168.0.123</code> or <code>192.168.0.234</code>) destined for <code>10.7.0.1</code> (the virtual computer), it changes the packet's source IP to <code>10.7.0.1</code> (the virtual computer) and the destination IP to your iOS device (<code>192.168.0.123</code> or <code>192.168.0.234</code>), and then sends it out. The <code>notrack</code> here disables connection tracking, which prevents Linux from matching these packets to previously received packets and connection tracking entries, which could make the rules ineffective.</p>
<p><del>Since Nftables does not support using packet source/destination IP addresses as variables, it's not possible to achieve the purpose of "swapping source and destination addresses" with a single set of rules. Therefore, we need to add a rule for each iOS device. If you have a small number of iOS devices, you can write a separate rule for each device's IP address. However, if you have many devices, or if they don't have static IP addresses, you will need to write a rule for every IP address in your home network segment, which can be very troublesome. Also, if your router does not support Nftables or similar firewall functions and cannot rewrite packets in a similar way, you cannot achieve this functionality.</del></p>
<p><strong>2026-05-01 update:</strong> Thanks to <a href="https://github.com/KusakabeShi" rel="noopener noreferrer" target="_blank">@KusakabeShi</a> who provided the following rules, these Nftables rules can swap the source and destination addresses in one go. It will work for your entire network, and you don't need to create rules for each IP one by one:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  chain</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> NAT_PREROUTING</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    type</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nat</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> hook</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> prerouting</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> priority</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -350</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">policy</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="sidestore-vpn-tool">SideStore VPN Tool</h2>
<p>If you cannot use the above method, I have also written a small program that implements the above logic: <a href="https://github.com/xddxdd/sidestore-vpn" rel="noopener noreferrer" target="_blank">SideStore VPN Tool</a>. It can create a TUN interface on a Linux device, listen for packets destined for <code>10.7.0.1</code>, and process these packets with the same logic as StosVPN.</p>
<p>To use this tool in your network, you need a device running Linux (such as a Raspberry Pi or a virtual machine), connect it to the same LAN as your iOS devices, and set a static IP address. Since the packets rewritten by the tool can be seen as a new connection from this Linux device to the iOS device, there should be no firewall or NAT between the iOS device and this Linux device, otherwise this new connection will be blocked, preventing SideStore's simulated computer from receiving requests normally.</p>
<p>Then, perform the following steps:</p>
<ol>
<li>Enable IP Forwarding on the Linux device:</li>
</ol>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "net.ipv4.ip_forward=1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -a</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/sysctl.conf</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sysctl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -p</span></span></code></pre>
<ol start="2">
<li>
<p>Install Rust and Cargo on the device.</p>
</li>
<li>
<p>Run the following commands to install and start the SideStore VPN Tool:</p>
</li>
</ol>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">git</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> clone</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://github.com/xddxdd/sidestore-vpn.git</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cd</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore-vpn</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cargo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --release</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> target/release/sidestore-vpn</span></span></code></pre>
<p>The SideStore VPN Tool will create a TUN device called <code>sidestore</code> and set up system routes to send all traffic destined for <code>10.7.0.1</code> to the tool for processing.</p>
<ol start="4">
<li>Add a static route on your main router:</li>
</ol>
<pre><code>Route: 10.7.0.1/32
Subnet Mask (if needed): 255.255.255.255
Gateway: The IP address of the Linux device mentioned earlier.
</code></pre>
<p>To minimize IP conflicts, this static route only affects a single IP address, <code>10.7.0.1</code>. However, if your router does not support creating /32 routes, you can adjust the subnet mask to expand the scope of this routing rule, as long as it does not conflict with other devices:</p>
<pre><code>Route: 10.7.0.0/24
Subnet Mask (if needed): 255.255.255.0
Gateway: The IP address of the Linux device mentioned earlier.
</code></pre>
<ol start="5">
<li>
<p>Ping <code>10.7.0.1</code> from any device on the LAN. It should now be reachable.</p>
</li>
<li>
<p>Disconnect WireGuard or StosVPN on your iOS device, and then try refreshing apps with SideStore. SideStore should now be able to refresh certificates normally even without a VPN.</p>
</li>
</ol>]]></content>
        <published>2025-06-27T00:47:31.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[在内网中免 StosVPN 使用 SideStore]]></title>
        <id>https://lantian.pub/article/modify-computer/sidestore-without-stosvpn-across-lan.lantian/</id>
        <link href="https://lantian.pub/article/modify-computer/sidestore-without-stosvpn-across-lan.lantian/"/>
        <updated>2025-06-27T00:47:31.000Z</updated>
        <content type="html"><![CDATA[<p><strong>2026 年 5 月 1 日更新：</strong> 增加了<a href="https://github.com/KusakabeShi" rel="noopener noreferrer" target="_blank">@日下部 詩</a>提供的在整个网络生效的 Nftables 规则。</p>
<h2 id="前言">前言</h2>
<p><a href="https://github.com/SideStore/SideStore" rel="noopener noreferrer" target="_blank">SideStore</a> 是一款常用的 iOS 应用侧载工具，可以绕过 App Store 安装第三方应用。它的工作原理是用你的 Apple ID 获取免费的苹果开发者证书，给你要安装的应用签名，从而让应用可以在 iOS 设备上正常运行。</p>
<p>然而，苹果为了维护其对 iOS 生态系统的控制，阻止第三方应用商店使用开发者证书大规模地绕过限制，对开发者证书设置了 7 天的过期时间。用户需要定期获取新的开发者证书，重新给应用签名，才能一直使用自己安装的第三方应用。</p>
<p>传统的侧载工具，例如 AltStore，都依赖电脑上的 iTunes 等软件进行重新签名的操作。但 SideStore 与其它侧载工具不同，它只有首次安装时需要电脑辅助。安装完成后，SideStore 可以自己模拟一台安装了 iTunes 的电脑，让 iOS 系统通过虚拟网络与其通信，从而实现无需电脑就能给应用重新签名，甚至安装新的第三方应用的效果。</p>
<p>SideStore 的虚拟网络一般可以用下面两种方式实现：</p>
<ul>
<li>WireGuard：SideStore 可以在本机上创建一个 WireGuard 服务器。用户可以自行安装 WireGuard 客户端，并连接到这个服务器上，从而让 iOS 系统可以通过网络和模拟出的电脑通信。
<ul>
<li>这种方法的缺点是，受到 iOS 系统限制，当 iPhone/iPad 通过移动网络上网时，WireGuard 客户端是连不上 SideStore 本地创建的 WireGuard 服务器的。因此，SideStore 只有在设备连接到 Wi-Fi 时才能正常工作。</li>
<li>同时，由于 iOS 系统只支持同时连接一个 VPN，如果用户需要使用别的 VPN 软件，就只能手动切换 VPN，操作比较麻烦。</li>
</ul>
</li>
<li><a href="https://github.com/SideStore/StosVPN" rel="noopener noreferrer" target="_blank">StosVPN</a>：是 SideStore 团队开发的专用 VPN 客户端，只能用于 SideStore。
<ul>
<li>相比于 WireGuard，StosVPN 不会受到 iOS 的限制，可以在设备通过移动网络上网时正常工作。但是我试用后发现，StosVPN 无法长时间保持在后台运行，经常会自动断开。如果有一段时间没有使用 iOS 设备，同时 StosVPN 断开了连接，SideStore 以及其它第三方应用没能及时续期，就只能重新连接电脑，给这些应用签名了。</li>
<li>同时，由于 StosVPN 也是 VPN，它同样受到 iOS 系统只支持同时连接一个 VPN 的限制。</li>
</ul>
</li>
</ul>
<p>于是我就想尝试分析 SideStore/StosVPN 的工作原理，看看能不能把它们集成到我的家庭网络或者 ZeroTier SDN 网络里，让 SideStore 无需额外的 VPN 配置就能正常刷新。</p>
<h2 id="stosvpn-的工作原理">StosVPN 的工作原理</h2>
<p>根据 <a href="https://github.com/SideStore/StosVPN/blob/main/TunnelProv/PacketTunnelProvider.swift" rel="noopener noreferrer" target="_blank">StosVPN 的关键数据包处理逻辑</a>，StosVPN 大致做了以下几件事：</p>
<ul>
<li>给 iOS 设备分配 IP <code>10.7.0.0</code>，让 iOS 把 <code>10.7.0.0/24</code> 这个网段的数据包发送到 StosVPN。</li>
<li>定义了一个 IP <code>10.7.0.1</code>，StosVPN 将在这个 IP 上模拟装了 iTunes 的电脑。</li>
<li>对于每个数据包：
<ul>
<li>如果数据包是从 <code>10.7.0.0</code> 发给 <code>10.7.0.1</code> 的，就交换数据包的来源和目标 IP，从而把数据包发回给 iOS 设备。</li>
</ul>
</li>
</ul>
<p>这个逻辑看起来很简单，实际上也一点都不复杂。实际上，SideStore 就是在 iOS 设备本地打开了一系列端口，模拟安装了 iTunes 的电脑。假设 iOS 在尝试连接模拟出的电脑时创建了这样一条连接：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.0:12345</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.1:54321</span></span></code></pre>
<p>那么 WireGuard 或者 StosVPN 就会交换来源和目标 IP（但不交换端口号），将数据包改写成以下的样子并发回 iOS 设备：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.1:12345</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.0:54321</span></span></code></pre>
<p>从 iOS 设备看来，这是一条从 <code>10.7.0.1</code> 发来的新 TCP 连接，与上一条发往电脑的连接没什么关系。由于 iOS 尝试连接到的端口（此处以 <code>54321</code> 示例）应当是 iTunes 的端口，而 SideStore 又在本地模拟了 iTunes，所以 SideStore 此时也应在监听 <code>54321</code> 端口，并收到了数据。</p>
<p>SideStore 模拟 iTunes 的逻辑处理完数据，并生成一个回复：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.0:54321</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.1:12345</span></span></code></pre>
<p>WireGuard 或者 StosVPN 又会交换来源和目标 IP：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">TCP</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> 10.7.0.1:54321</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -> </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">10.7.0.0:12345</span></span></code></pre>
<p>这一个回复数据包就对上了最开始发往电脑的连接。iOS 因此认为自己收到了电脑上 iTunes 的回复，从而继续更新开发者证书。</p>
<h2 id="用-nftables-模拟-stosvpn-的工作逻辑">用 Nftables 模拟 StosVPN 的工作逻辑</h2>
<p>了解了 StosVPN 的工作原理，我们只需要在自己的网络里模仿它的工作逻辑就可以了。</p>
<p>如果你只有少量的 iOS 设备，并且给它们都分配了固定 IP，而且有一台运行 OpenWrt 或其它 Linux 系统的路由器，你直接用以下 Nftables 规则就可以了：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> inet</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  chain</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> RAW_PREROUTING</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    type</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> filter</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> hook</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> prerouting</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> priority</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> raw</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">policy</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # 此处 192.168.0.xxx 改成你的 iOS 设备的 IP</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.123</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.123</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.234</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 192.168.0.234</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">    # 可以按需添加更多的规则</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>上述规则的用途是，如果收到了来自你的 iOS 设备（<code>192.168.0.123</code> 或 <code>192.168.0.234</code>）发往 <code>10.7.0.1</code>（虚拟电脑）的数据包，就把数据包的源 IP 改成 <code>10.7.0.1</code>（虚拟电脑），目标 IP 改成你的 iOS 设备（<code>192.168.0.123</code> 或 <code>192.168.0.234</code>），然后发送出去。此处的 <code>notrack</code> 是关闭连接跟踪，防止 Linux 用这些数据包去匹配之前收到的数据包和连接跟踪条目，导致规则不生效。</p>
<p><del>由于 Nftables 不支持将数据包的来源/目标 IP 等信息用作变量，无法用一组规则实现「交换来源和目标地址」的目的，所以我们需要给每台 iOS 设备都添加一条规则。如果你的 iOS 设备比较少，可以给每个设备的 IP 都单独写一条规则。但如果你的设备很多，或者没有固定 IP，你就需要给家庭网段内的每一个 IP 都写一条规则，非常麻烦。同时，如果你的路由器不支持 Nftables 或类似的防火墙功能，无法用类似的方式改写数据包，也无法实现这样的功能。</del></p>
<p><strong>2026 年 5 月 1 日更新：</strong> 感谢<a href="https://github.com/KusakabeShi" rel="noopener noreferrer" target="_blank">@日下部 詩</a>提供的防火墙规则，以下 Nftables 规则可以直接交换来源和目标地址，对整个网络生效，无需给每个 IP 单独设置规则：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">table</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">  chain</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> NAT_PREROUTING</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> {</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    type</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> nat</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> hook</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> prerouting</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> priority</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -350</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">policy</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> accept</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">    ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> daddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> ip</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> saddr</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> set</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10.7.0.1</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> notrack</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  }</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="sidestore-vpn-工具">SideStore VPN 工具</h2>
<p>如果你无法使用上面的方法，我也写了一个实现上述逻辑的小工具：<a href="https://github.com/xddxdd/sidestore-vpn" rel="noopener noreferrer" target="_blank">SideStore VPN 工具</a>。它可以在 Linux 设备上创建一个 TUN 接口，监听发往 <code>10.7.0.1</code> 的数据包，并用和 StosVPN 相同的逻辑处理这些数据包。</p>
<p>要在你的网络内使用这个工具，你需要准备一台运行 Linux 的设备（例如树莓派或者虚拟机），将它连接到 iOS 设备所在的同一个内网中，并设置一个固定 IP。由于工具改写后的数据包可以看作是从这个 Linux 设备向 iOS 设备的一条新连接，所以 iOS 设备和这个 Linux 设备之间不能有防火墙或者 NAT，否则这条新连接会被拦截，导致 SideStore 的模拟电脑无法正常收到请求。</p>
<p>然后，执行以下操作：</p>
<ol>
<li>在 Linux 设备上开启 IP 转发（IP Forwarding）：</li>
</ol>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">echo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "net.ipv4.ip_forward=1"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> | </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> tee</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -a</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /etc/sysctl.conf</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sysctl</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> -p</span></span></code></pre>
<ol start="2">
<li>
<p>在设备上安装 Rust 和 Cargo。</p>
</li>
<li>
<p>运行以下命令，安装并启动 SideStore VPN 工具：</p>
</li>
</ol>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">git</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> clone</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> https://github.com/xddxdd/sidestore-vpn.git</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cd</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> sidestore-vpn</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">cargo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> build</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> --release</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">sudo</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> target/release/sidestore-vpn</span></span></code></pre>
<p>SideStore VPN 工具会创建一个名为 <code>sidestore</code> 的 TUN 设备，并设置系统路由将发往 <code>10.7.0.1</code> 的流量全部交给工具处理。</p>
<ol start="4">
<li>在你的主路由器上添加一条静态路由：</li>
</ol>
<pre><code>路由: 10.7.0.1/32
子网掩码 (如果需要): 255.255.255.255
网关: 前文中 Linux 设备的 IP 地址。
</code></pre>
<p>为了最大限度地避免 IP 冲突，这条静态路由只影响 <code>10.7.0.1</code> 一个 IP 地址。但如果你的路由器不支持创建 /32 路由，你可以调整子网掩码，扩大这条路由规则的影响范围，只要不与其他设备冲突即可：</p>
<pre><code>路由: 10.7.0.0/24
子网掩码 (如果需要): 255.255.255.0
网关: 前文中 Linux 设备的 IP 地址。
</code></pre>
<ol start="5">
<li>
<p>用内网中的任何一个设备 Ping <code>10.7.0.1</code>，此时应该可以 Ping 通。</p>
</li>
<li>
<p>在你的 iOS 设备上断开 WireGuard 或者 StosVPN，然后用 SideStore 尝试刷新应用。现在即使不开 VPN，SideStore 应该也可以正常刷新证书了。</p>
</li>
</ol>]]></content>
        <published>2025-06-27T00:47:31.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[Nix Logarithmic Math Library from Ground Zero]]></title>
        <id>https://lantian.pub/en/article/modify-computer/nix-logarithmetic-math-library-from-zero.lantian/</id>
        <link href="https://lantian.pub/en/article/modify-computer/nix-logarithmetic-math-library-from-zero.lantian/"/>
        <link rel="enclosure" href="https://lantian.pub/usr/uploads/202505/logarithm.png" type="image/png"/>
        <updated>2025-05-19T23:02:28.000Z</updated>
        <content type="html"><![CDATA[<p>(Cover image from:
<a href="https://en.wikipedia.org/wiki/Logarithm" rel="noopener noreferrer" target="_blank">Wikipedia - Logarithm</a>)</p>
<h2 id="origin">Origin</h2>
<p>Due to a somewhat absurd reason (calculating the physical distance between VPS
to estimate network latency), I
<a href="https://lantian.pub/en/article/modify-computer/nix-trigonometric-math-library-from-zero.lantian/">implemented a somewhat absurd trigonometric function library using Nix</a>.
After I
<a href="https://github.com/xddxdd/nix-math" rel="noopener noreferrer" target="_blank">published the trigonometric function library on GitHub</a>,
I found that someone actually used it! It seems my needs weren't too absurd
after all.</p>
<p>In the repository's Issues,
<a href="https://github.com/xddxdd/nix-math/issues/1" rel="noopener noreferrer" target="_blank">a user suggested that I add some exponential/logarithmic function support to this math library</a>,
such as <code>exp</code>, <code>ln</code>, <code>pow</code>, and <code>log</code>.</p>
<p>Since implementing these basic functions from scratch is also quite interesting,
I took some time to research it. Among these four functions, <code>exp</code> and <code>ln</code> are
somewhat difficult. <code>pow</code> and <code>log</code> can both be derived from the other two
functions:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mi>n</mi></msub><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mfrac><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow><mrow><mi>ln</mi><mo>⁡</mo><mi>n</mi></mrow></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>p</mi><mi>o</mi><mi>w</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>n</mi><mo stretchy="false">)</mo><mo>=</mo><msup><mi>x</mi><mi>n</mi></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi>exp</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>n</mi><mo>∗</mo><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\log_n x &#x26;= \frac{\ln x}{\ln n} \\
pow(x, n) = x^n &#x26;= \exp (n * \ln x)
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.8574em;vertical-align:-1.6787em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.1787em;"><span style="top:-4.1787em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.0573em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.3527em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">n</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7144em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.6787em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.1787em;"><span style="top:-4.1787em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.3527em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop">exp</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.6787em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<h2 id="logarithmic-function-ln">Logarithmic Function ln</h2>
<p>As we learned in math classes, when <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span>, the natural logarithm
function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow><annotation encoding="application/x-tex">\ln x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span></span> can be obtained using the following Taylor series:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>1</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mo stretchy="false">(</mo><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></msup><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mi>n</mi></msup></mrow><mi>n</mi></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mfrac><mrow><mi>x</mi><mo>−</mo><mn>1</mn></mrow><mn>1</mn></mfrac><mo>−</mo><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow><mn>2</mn></mfrac><mo>+</mo><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mn>3</mn></msup></mrow><mn>3</mn></mfrac><mo>−</mo><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\ln x &#x26;= \sum_{n=1}^\infty (-1)^{n+1} \frac{(x-1)^n}{n} \\
&#x26;= \frac{x-1}{1} - \frac{(x-1)^2}{2} + \frac{(x-1)^3}{3} - ...
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.6956em;vertical-align:-2.5978em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6514em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">1</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2671em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord">−</span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">...</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>When I implemented the trigonometric function last time, I wrote code to
calculate the result of the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>sin</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\sin</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6679em;"></span><span class="mop">sin</span></span></span></span> function based on the Taylor series.
Therefore, we only need to copy the code and change the formula for calculating
a term in the series.</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Precision limit, stop calculation when the Taylor expansion term is less than this value</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  epsilon</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Absolute value function abs and alias fabs</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    then</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  fabs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Helper function to calculate the product of all terms in a list</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  multiply</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">foldl'</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">mul</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Integer power calculation function, which is the `pow` function from the previous article, renamed to avoid conflict with the floating-point power function</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  _pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      multiply</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">replicate</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Natural logarithm function, currently only handles 0 &#x3C; x &#x3C;= 2</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Calculate the i-th term in the series, where i starts from 1</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      step</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Same as the `sin` function, calculate the Taylor series until the next term is less than epsilon (1e-10)</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # tmp is used for accumulation, i is the index of the Taylor expansion term</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">step</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        in</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Stop calculation if the absolute value of the current term is less than epsilon, otherwise continue to the next step</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fabs</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> tmp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      helper</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>(Formula from:
<a href="https://en.wikipedia.org/wiki/Logarithm#Taylor_series" rel="noopener noreferrer" target="_blank">Wikipedia</a>)</p>
<p>Although this Taylor series can handle the range <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span>, after testing,
when <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> is close to the ends of the range, the number of terms to be calculated
becomes too large, causing Nix to report a stack overflow error:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">error:</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> stack</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> overflow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">max-call-depth</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> exceeded</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">at</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /nix/store/qhnbm9x3zs2y55nyx1gxqf801gmjdjfc-source/default.nix:163:61:</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   162</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|     </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">let</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   163</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|       </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">step</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> =</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> i:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (_pow_int (0 </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">-</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (1.0 </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> x</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1.0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) i) / i;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      |                                                             </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">^</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   164</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|       </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">helper</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> =</span></span></code></pre>
<p>After testing, the number of terms is acceptable when <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.1</mn><mo>≤</mo><mi>x</mi><mo>≤</mo><mn>1.9</mn></mrow><annotation encoding="application/x-tex">0.1 \le x \le 1.9</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7804em;vertical-align:-0.136em;"></span><span class="mord">0.1</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.9</span></span></span></span>, so I
only use the Taylor series for calculation within this range.</p>
<p>For inputs outside this range, it needs to be converted to this range before
calculation:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>≤</mo><mn>0</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mtext>Invalid Value</mtext></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>&#x3C;</mo><mn>1</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo>−</mo><mi>ln</mi><mo>⁡</mo><mfrac><mn>1</mn><mi>x</mi></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>></mo><mn>1.9</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mn>2</mn><mo>∗</mo><mi>ln</mi><mo>⁡</mo><msqrt><mi>x</mi></msqrt></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
x \le 0,&#x26; \ln x = \text{Invalid Value} \\
x &#x3C; 1,&#x26; \ln x = -\ln \frac{1}{x} \\
x > 1.9,&#x26; \ln (x) = 2 * \ln \sqrt x \\
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.3166em;vertical-align:-2.4083em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.9083em;"><span style="top:-5.3897em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">0</span><span class="mpunct">,</span></span></span><span style="top:-3.4083em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mpunct">,</span></span></span><span style="top:-1.5731em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1.9</span><span class="mpunct">,</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.4083em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.9083em;"><span style="top:-5.3897em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord text"><span class="mord">Invalid Value</span></span></span></span><span style="top:-3.4083em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-1.5731em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8492em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal" style="padding-left:0.833em;">x</span></span><span style="top:-2.8092em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1908em;"><span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.4083em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Since the calculation method <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo>−</mo><mi>ln</mi><mo>⁡</mo><mfrac><mn>1</mn><mi>x</mi></mfrac></mrow><annotation encoding="application/x-tex">\ln x = -\ln \frac{1}{x}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> is applicable to the
entire <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>&#x3C;</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x &#x3C; 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5782em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span> range, to maintain consistency in the calculation method, I
used this method for all inputs in this range.</p>
<p>Next, we only need to implement the logic to use different algorithms based on
the input range:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      step</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">step</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fabs</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> tmp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;= </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      throw</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "ln(x&#x3C;=0) returns invalid value"</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      -</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> > </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">9</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">sqrt</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      helper</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>With the natural logarithm function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\ln</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span></span></span></span>, we can implement the generic
logarithm function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mi>n</mi></msub></mrow><annotation encoding="application/x-tex">\log_n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9386em;vertical-align:-0.2441em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.0573em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span></span></span></span> with any base:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">base</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> base</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">log</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">log</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="natural-exponential-function-exp">Natural Exponential Function exp</h2>
<p>With the logarithm function in place, we still need another piece of the puzzle:
the natural exponential function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><msup><mi>e</mi><mi>x</mi></msup></mrow><annotation encoding="application/x-tex">\exp x = e^x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6644em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span></span></span></span></span></span></span>. The Taylor expansion of the
natural exponential function is:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mfrac><msup><mi>x</mi><mi>n</mi></msup><mi>n</mi></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mn>1</mn><mo>+</mo><mi>x</mi><mo>+</mo><mfrac><msup><mi>x</mi><mn>2</mn></msup><mrow><mn>2</mn><mo stretchy="false">!</mo></mrow></mfrac><mo>+</mo><mfrac><msup><mi>x</mi><mn>3</mn></msup><mrow><mn>3</mn><mo stretchy="false">!</mo></mrow></mfrac><mo>+</mo><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\exp x &#x26;= \sum_{n=0}^\infty \frac{x^n}{n} \\
&#x26;= 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + ...
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.6956em;vertical-align:-2.5978em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6514em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">0</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2671em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3414em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mclose">!</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span><span class="mclose">!</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">...</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Obviously, this Taylor expansion never converges, so we cannot calculate the
result term by term and then sum them up. Therefore, we can use the same method
as calculating <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>arctan</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\arctan</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mop">arctan</span></span></span></span> in the previous article, using polynomial regression to
fit the curve of the natural exponential function.</p>
<p>So which segment should we fit? When <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>≤</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">x \le 0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span>, we can directly calculate the
reciprocal of the absolute value exponent <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><msup><mi>e</mi><mrow><mo>−</mo><mi>x</mi></mrow></msup></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{e^{-x}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7027em;"><span style="top:-2.786em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">x</span></span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>. And since we
already have the function <code>_pow_int</code> for calculating integer powers, when
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>≥</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">x \ge 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>, we can split <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> into integer and decimal parts and calculate them
separately:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>≤</mo><mn>0</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mfrac><mn>1</mn><mrow><mi>exp</mi><mo>⁡</mo><mo>−</mo><mi>x</mi></mrow></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>></mo><mn>1</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo stretchy="false">(</mo><msup><mi>e</mi><mrow><mo fence="true">⌊</mo><mi>x</mi><mo fence="true">⌋</mo></mrow></msup><mo stretchy="false">)</mo><mo stretchy="false">(</mo><msup><mi>e</mi><mrow><mi>x</mi><mo>−</mo><mrow><mo fence="true">⌊</mo><mi>x</mi><mo fence="true">⌋</mo></mrow></mrow></msup><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
x \le 0,&#x26; \exp x = \frac{1}{\exp -x} \\
x > 1,&#x26; \exp x = (e^{\left \lfloor{x}\right \rfloor}) (e^{x - \left \lfloor{x}\right \rfloor}) \\
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.0999em;vertical-align:-1.7999em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.2999em;"><span style="top:-4.2999em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">0</span><span class="mpunct">,</span></span></span><span style="top:-2.1815em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mpunct">,</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.7999em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.2999em;"><span style="top:-4.2999em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">−</span><span class="mord mathnormal">x</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.1815em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="minner mtight"><span class="mopen mtight delimcenter" style="top:0em;"><span class="mtight">⌊</span></span><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span><span class="mclose mtight delimcenter" style="top:0em;"><span class="mtight">⌋</span></span></span></span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="mbin mtight">−</span><span class="minner mtight"><span class="mopen mtight delimcenter" style="top:0em;"><span class="mtight">⌊</span></span><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span><span class="mclose mtight delimcenter" style="top:0em;"><span class="mtight">⌋</span></span></span></span></span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.7999em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>Therefore, we only need to fit the natural exponential function on <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">[0, 1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span>.</p>
<p>Since we don't know how many terms to use in polynomial regression to get the
best result, I wrote a simple script using Python and Numpy to try from 1 term
to 100 terms and then select the fitting result with the smallest error:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> json</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> typing </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> Callable, Iterable, List, Optional, Tuple</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> numpy </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">as</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> np</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> numpy.polynomial.polynomial </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> Polynomial</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EPSILON = </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1e-10</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">class</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Approximate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> __init__</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fn</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Callable[[Iterable[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]], Iterable[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]], </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">linspace</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">max_poly_degrees</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Optional[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">] = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">None</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">):</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.fn = fn</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Range for polynomial regression, in np.linspace format</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace = linspace</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input = np.linspace(*linspace)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Calculate standard results using the standard function fn</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected = fn(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Maximum number of terms to search</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.max_poly_degrees = max_poly_degrees</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> _fit</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">deg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, Polynomial]:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Perform regression using Numpy's Polynomial regression class</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        fit: Polynomial = Polynomial.fit(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input, </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected, deg, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">domain</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]), </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]))</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Calculate results using the regressed polynomial function</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        result = fit(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Calculate error</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error_percent = np.fabs((result - </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected) / </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected)</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        max_error_percent = np.max(error_percent[error_percent &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1e308</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">] * </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> max_error_percent, fit</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> run</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, Polynomial]:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Search for the fitting result with the minimum error from 1 to max_poly_degrees terms</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error, poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">._fit(</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        for</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> deg </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">in</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> range</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.max_poly_degrees+</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">):</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">            _error, _poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">._fit(deg)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">            if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> _error &#x3C; error:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">                error = _error</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">                poly = _poly</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> error, poly</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> explain</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Polynomial:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # Output results, where the JSON of Coefficients output can be directly copied into Nix code</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error, poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.run()</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Degree: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">poly.degree()</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Error %: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">error</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Coefficients: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">json.dumps(json.dumps(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">list</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(poly.coef)))</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> poly</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Approximate(np.exp, (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">10000</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">), </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">max_poly_degrees</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">).explain()</span></span></code></pre>
<p>By comparing with the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>arctan</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\arctan</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mop">arctan</span></span></span></span> function, which is also based on polynomial
regression, we can implement the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>exp</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\exp</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mop">exp</span></span></span></span> function:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # Integer function</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">floor</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Extract the integer part of the input</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Extract the decimal part of the input</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      x_decimal</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Polynomial coefficients calculated using Python and Numpy</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      decimal_poly</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fromJSON</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "[0.9999999999999997, 0.9999999999999494, 0.5000000000013429, 0.16666666664916754, 0.04166666680065545, 0.008333332669176907, 0.001388891142716621, 0.00019840730702746657, 2.481076351588151e-05, 2.744709498016379e-06, 2.846575263734758e-07, 2.0215584670370862e-08, 3.542885385105854e-09]"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Calculate the reciprocal of the absolute value exponent</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Calculate the exponents of the integer and decimal parts separately, then multiply</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> e</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">polynomial</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x_decimal</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> decimal_poly</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="floating-point-power-function-pow">Floating-Point Power Function pow</h2>
<p>With the above functions, we can finally calculate floating-point powers using
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>x</mi><mi>n</mi></msup><mo>=</mo><mi>exp</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>n</mi><mo>∗</mo><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">x^n = \exp (n * \ln x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6644em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">exp</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span>. The only thing to note is various special cases:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  pow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Check if the exponent is an integer</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      is_int_times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> is_int_times</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # If it's an integer, use the existing integer power calculation function, which is faster and can handle the case when x &#x3C; 0</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      _pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Base is 0, any power is 0</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      0</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Base is negative, cannot calculate floating-point power because we don't support imaginary numbers</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      throw</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "Calculating power of negative base and decimal exponential is not supported"</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # Calculate the result using the above formula</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="summary">Summary</h2>
<p>All these logarithmetic functions (and some extra math functions) can be
obtained from my GitHub: <a href="https://github.com/xddxdd/nix-math" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nix-math</a></p>
<p>If you're using Nix Flake, you can use the function as follows:</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    nix-math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">url</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"github:xddxdd/nix-math"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  outputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">nix-math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  in</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">sin</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">deg2rad</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 45</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>]]></content>
        <published>2025-05-19T23:02:28.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
    <entry>
        <title type="html"><![CDATA[从零开始实现 Nix 对数函数库]]></title>
        <id>https://lantian.pub/article/modify-computer/nix-logarithmetic-math-library-from-zero.lantian/</id>
        <link href="https://lantian.pub/article/modify-computer/nix-logarithmetic-math-library-from-zero.lantian/"/>
        <link rel="enclosure" href="https://lantian.pub/usr/uploads/202505/logarithm.png" type="image/png"/>
        <updated>2025-05-19T23:02:28.000Z</updated>
        <content type="html"><![CDATA[<p>（题图来自：<a href="https://zh.wikipedia.org/zh-cn/%E5%AF%B9%E6%95%B0" rel="noopener noreferrer" target="_blank">维基百科 - 对数</a>）</p>
<h2 id="起因">起因</h2>
<p>由于一个有点离谱的原因（计算 VPS 间的物理距离来估算网络延迟），我<a href="https://lantian.pub/article/modify-computer/nix-trigonometric-math-library-from-zero.lantian/">用 Nix 实现了一个有点离谱的三角函数库</a>。我把三角函数库<a href="https://github.com/xddxdd/nix-math" rel="noopener noreferrer" target="_blank">发布到 GitHub 上</a>后，发现居然有人用！看来我的需求也不算太离谱。</p>
<p>在仓库的 Issues 里，<a href="https://github.com/xddxdd/nix-math/issues/1" rel="noopener noreferrer" target="_blank">有用户建议我给这个数学库添加一些指数/对数函数支持</a>，例如 <code>exp</code>，<code>ln</code>，<code>pow</code> 和 <code>log</code>。</p>
<p>因为从零开始实现这些基础函数也挺有趣的，所以我就抽空研究了一下。这四个函数中，有些难度的是 <code>exp</code> 和 <code>ln</code>。<code>pow</code> 和 <code>log</code> 都可以用另外两个函数转化出来：</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mi>n</mi></msub><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mfrac><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow><mrow><mi>ln</mi><mo>⁡</mo><mi>n</mi></mrow></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>p</mi><mi>o</mi><mi>w</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>n</mi><mo stretchy="false">)</mo><mo>=</mo><msup><mi>x</mi><mi>n</mi></msup></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi>exp</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>n</mi><mo>∗</mo><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\log_n x &#x26;= \frac{\ln x}{\ln n} \\
pow(x, n) = x^n &#x26;= \exp (n * \ln x)
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:3.8574em;vertical-align:-1.6787em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.1787em;"><span style="top:-4.1787em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.0573em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.3527em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord mathnormal">p</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.02691em;">w</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">n</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7144em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.6787em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.1787em;"><span style="top:-4.1787em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3714em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.3527em;"><span class="pstrut" style="height:3.3714em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop">exp</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.6787em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<h2 id="对数函数-ln">对数函数 ln</h2>
<p>学习过小学二年级的我们都知道，当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span> 时，以自然底数 <code>e</code> 为底的对数函数
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow><annotation encoding="application/x-tex">\ln x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span></span> 可以用如下的泰勒级数求得：</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>1</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mo stretchy="false">(</mo><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></msup><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mi>n</mi></msup></mrow><mi>n</mi></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mfrac><mrow><mi>x</mi><mo>−</mo><mn>1</mn></mrow><mn>1</mn></mfrac><mo>−</mo><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow><mn>2</mn></mfrac><mo>+</mo><mfrac><mrow><mo stretchy="false">(</mo><mi>x</mi><mo>−</mo><mn>1</mn><msup><mo stretchy="false">)</mo><mn>3</mn></msup></mrow><mn>3</mn></mfrac><mo>−</mo><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\ln x &#x26;= \sum_{n=1}^\infty (-1)^{n+1} \frac{(x-1)^n}{n} \\
&#x26;= \frac{x-1}{1} - \frac{(x-1)^2}{2} + \frac{(x-1)^3}{3} - ...
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.6956em;vertical-align:-2.5978em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6514em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">1</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2671em;"><span></span></span></span></span></span><span class="mopen">(</span><span class="mord">−</span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">+</span><span class="mord mtight">1</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.427em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">1</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">...</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>上次实现三角函数时，我就写过了基于泰勒级数求 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>sin</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\sin</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6679em;"></span><span class="mop">sin</span></span></span></span>
函数结果的代码。因此我们只需要将代码抄过来，改掉计算数列中某一项的公式即可。</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 精度限制，泰勒展开项小于该值时停止计算</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  epsilon</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">pow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 绝对值函数 abs 以及别名 fabs</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    then</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  fabs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 辅助函数，对数列中的所有项求乘积</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  multiply</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">foldl'</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">mul</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 整数次幂的计算函数，就是前一篇文章中的 `pow` 函数，为了防止和浮点次幂函数冲突改了名字</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  _pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      multiply</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">replicate</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 自然对数函数，目前只处理了 0 &#x3C; x &#x3C;= 2</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 计算数列中的第 i 项，其中 i 从 1 开始</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      step</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 与 `sin` 函数相同，计算泰勒级数直到下一项小于 epsilon（1e-10）</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # tmp 用于累加，i 是泰勒展开项的编号计数</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">step</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        in</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 如果当前项的绝对值小于 epsilon 就停止计算，否则继续算下一步</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fabs</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> tmp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      helper</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>（公式来自：<a href="https://en.wikipedia.org/wiki/Logarithm#Taylor_series" rel="noopener noreferrer" target="_blank">维基百科</a>）</p>
<p>虽然这个泰勒级数可以处理 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>≤</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x \le 2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">2</span></span></span></span> 的范围，但经过测试，当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span>
接近范围两端时，需要计算的项数会变得过多，导致 Nix 报栈溢出错误：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">error:</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> stack</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> overflow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">; </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">max-call-depth</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> exceeded</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">at</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> /nix/store/qhnbm9x3zs2y55nyx1gxqf801gmjdjfc-source/default.nix:163:61:</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   162</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|     </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">let</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   163</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|       </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">step</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> =</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> i:</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (_pow_int (0 </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">-</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">i</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (1.0 </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">*</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> x</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> -</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1.0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) i) / i;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      |                                                             </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">^</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">   164</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">|       </span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">helper</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> =</span></span></code></pre>
<p>经过测试，当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.1</mn><mo>≤</mo><mi>x</mi><mo>≤</mo><mn>1.9</mn></mrow><annotation encoding="application/x-tex">0.1 \le x \le 1.9</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7804em;vertical-align:-0.136em;"></span><span class="mord">0.1</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1.9</span></span></span></span>
时计算项数可以接受，因此我只在这个区间内使用泰勒级数进行计算。</p>
<p>对于超过这个范围的输入，就需要转化到这个区间内再进行计算：</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>≤</mo><mn>0</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mtext>无效值</mtext></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>&#x3C;</mo><mn>1</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo>−</mo><mi>ln</mi><mo>⁡</mo><mfrac><mn>1</mn><mi>x</mi></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>></mo><mn>1.9</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>ln</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mn>2</mn><mo>∗</mo><mi>ln</mi><mo>⁡</mo><msqrt><mi>x</mi></msqrt></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
x \le 0,&#x26; \ln x = \text{无效值} \\
x &#x3C; 1,&#x26; \ln x = -\ln \frac{1}{x} \\
x > 1.9,&#x26; \ln (x) = 2 * \ln \sqrt x \\
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.3166em;vertical-align:-2.4083em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.9083em;"><span style="top:-5.3897em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">0</span><span class="mpunct">,</span></span></span><span style="top:-3.4083em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mpunct">,</span></span></span><span style="top:-1.5731em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1.9</span><span class="mpunct">,</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.4083em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.9083em;"><span style="top:-5.3897em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord text"><span class="mord cjk_fallback">无效值</span></span></span></span><span style="top:-3.4083em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">x</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-1.5731em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8492em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord mathnormal" style="padding-left:0.833em;">x</span></span><span style="top:-2.8092em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702
c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14
c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54
c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10
s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429
c69,-144,104.5,-217.7,106.5,-221
l0 -0
c5.3,-9.3,12,-14,20,-14
H400000v40H845.2724
s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7
c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z
M834 80h400000v40h-400000z"></path></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.1908em;"><span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.4083em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>由于 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo>−</mo><mi>ln</mi><mo>⁡</mo><mfrac><mn>1</mn><mi>x</mi></mfrac></mrow><annotation encoding="application/x-tex">\ln x = -\ln \frac{1}{x}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord">−</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span> 时的计算方法适用于整个 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn><mo>&#x3C;</mo><mi>x</mi><mo>&#x3C;</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">0 &#x3C; x &#x3C; 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6835em;vertical-align:-0.0391em;"></span><span class="mord">0</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.5782em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&#x3C;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span>
区间，因此为了保持计算方法一致，我就对这个区间的输入全部使用这个方法了。</p>
<p>接下来只需要实现根据输入范围使用不同算法的逻辑就可以了：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      step</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">        tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">          value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">step</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fabs</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> tmp</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> else</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> helper</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">tmp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">i</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> + </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C;= </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      throw</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "ln(x&#x3C;=0) returns invalid value"</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      -</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> > </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">9</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">sqrt</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      helper</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 0</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<p>有了自然对数函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ln</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\ln</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mop">ln</span></span></span></span> 后，我们自然就可以实现以任意数为底的对数函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi>log</mi><mo>⁡</mo></mrow><mi>n</mi></msub></mrow><annotation encoding="application/x-tex">\log_n</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.9386em;vertical-align:-0.2441em;"></span><span class="mop"><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.0573em;"><span style="top:-2.4559em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2441em;"><span></span></span></span></span></span></span></span></span></span>：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">base</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> base</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">log</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  log10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">log</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 10</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="自然指数函数-exp">自然指数函数 exp</h2>
<p>有了对数函数，我们还需要另一块拼图：自然指数函数
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><msup><mi>e</mi><mi>x</mi></msup></mrow><annotation encoding="application/x-tex">\exp x = e^x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6644em;"></span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">x</span></span></span></span></span></span></span></span></span></span></span>。自然指数函数的泰勒展开式是：</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><munderover><mo>∑</mo><mrow><mi>n</mi><mo>=</mo><mn>0</mn></mrow><mi mathvariant="normal">∞</mi></munderover><mfrac><msup><mi>x</mi><mi>n</mi></msup><mrow><mi>n</mi><mo stretchy="false">!</mo></mrow></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mn>1</mn><mo>+</mo><mi>x</mi><mo>+</mo><mfrac><msup><mi>x</mi><mn>2</mn></msup><mrow><mn>2</mn><mo stretchy="false">!</mo></mrow></mfrac><mo>+</mo><mfrac><msup><mi>x</mi><mn>3</mn></msup><mrow><mn>3</mn><mo stretchy="false">!</mo></mrow></mfrac><mo>+</mo><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
\exp x &#x26;= \sum_{n=0}^\infty \frac{x^n}{n!} \\
&#x26;= 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + ...
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:5.6956em;vertical-align:-2.5978em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:3.0978em;"><span style="top:-5.0978em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mop op-limits"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.6514em;"><span style="top:-1.8829em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mrel mtight">=</span><span class="mord mtight">0</span></span></span></span><span style="top:-3.05em;"><span class="pstrut" style="height:3.05em;"></span><span><span class="mop op-symbol large-op">∑</span></span></span><span style="top:-4.3em;margin-left:0em;"><span class="pstrut" style="height:3.05em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">∞</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.2671em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3414em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="mclose">!</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.0396em;"><span class="pstrut" style="height:3.6514em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mclose">!</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.4911em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">3</span><span class="mclose">!</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">3</span></span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.686em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord">...</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:2.5978em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>显然这个泰勒展开式永不收敛，因此我们不能一项项地计算结果然后求和。所以我们可以使用和上一篇文章中计算
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>arctan</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\arctan</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mop">arctan</span></span></span></span> 时相同的方法，用多项式回归来拟合自然指数函数的曲线。</p>
<p>那么我们要拟合哪一段呢？当 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>≤</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">x \le 0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> 时，我们可以直接计算绝对值指数的倒数
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mn>1</mn><msup><mi>e</mi><mrow><mo>−</mo><mi>x</mi></mrow></msup></mfrac></mrow><annotation encoding="application/x-tex">\frac{1}{e^{-x}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.1901em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8451em;"><span style="top:-2.655em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mathnormal mtight">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7027em;"><span style="top:-2.786em;margin-right:0.0714em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">x</span></span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>。而因为我们已经有了计算整数次幂的函数 <code>_pow_int</code>，因此当
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>≥</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">x \ge 1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7719em;vertical-align:-0.136em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≥</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span> 时，我们可以将 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> 分拆为整数和小数两个部分，分别计算：</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>≤</mo><mn>0</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mfrac><mn>1</mn><mrow><mi>exp</mi><mo>⁡</mo><mo>−</mo><mi>x</mi></mrow></mfrac></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mi>x</mi><mo>></mo><mn>1</mn><mo separator="true">,</mo></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mi>exp</mi><mo>⁡</mo><mi>x</mi><mo>=</mo><mo stretchy="false">(</mo><msup><mi>e</mi><mrow><mo fence="true">⌊</mo><mi>x</mi><mo fence="true">⌋</mo></mrow></msup><mo stretchy="false">)</mo><mo stretchy="false">(</mo><msup><mi>e</mi><mrow><mi>x</mi><mo>−</mo><mrow><mo fence="true">⌊</mo><mi>x</mi><mo fence="true">⌋</mo></mrow></mrow></msup><mo stretchy="false">)</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{aligned}
x \le 0,&#x26; \exp x = \frac{1}{\exp -x} \\
x > 1,&#x26; \exp x = (e^{\left \lfloor{x}\right \rfloor}) (e^{x - \left \lfloor{x}\right \rfloor}) \\
\end{aligned}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:4.0999em;vertical-align:-1.7999em;"></span><span class="mord"><span class="mtable"><span class="col-align-r"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.2999em;"><span style="top:-4.2999em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≤</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">0</span><span class="mpunct">,</span></span></span><span style="top:-2.1815em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">1</span><span class="mpunct">,</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.7999em;"><span></span></span></span></span></span><span class="col-align-l"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:2.2999em;"><span style="top:-4.2999em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">−</span><span class="mord mathnormal">x</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-2.1815em;"><span class="pstrut" style="height:3.3214em;"></span><span class="mord"><span class="mord"></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mop">exp</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="minner mtight"><span class="mopen mtight delimcenter" style="top:0em;"><span class="mtight">⌊</span></span><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span><span class="mclose mtight delimcenter" style="top:0em;"><span class="mtight">⌋</span></span></span></span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">x</span><span class="mbin mtight">−</span><span class="minner mtight"><span class="mopen mtight delimcenter" style="top:0em;"><span class="mtight">⌊</span></span><span class="mord mtight"><span class="mord mathnormal mtight">x</span></span><span class="mclose mtight delimcenter" style="top:0em;"><span class="mtight">⌋</span></span></span></span></span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:1.7999em;"><span></span></span></span></span></span></span></span></span></span></span></span>
<p>因此，我们只需要在 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">[0, 1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">[</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">1</span><span class="mclose">)</span></span></span></span> 上拟合自然指数函数就可以了。</p>
<p>由于我们不知道多项式回归使用几项时获得最佳结果，我用 Python 和 Numpy 写了一个简单的脚本，从 1 项到 100 项都试一次，然后选取误差最小的拟合结果：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> json</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> typing </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> Callable, Iterable, List, Optional, Tuple</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> numpy </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">as</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> np</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">from</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> numpy.polynomial.polynomial </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">import</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> Polynomial</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">EPSILON = </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1e-10</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">class</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99"> Approximate</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> __init__</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fn</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Callable[[Iterable[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]], Iterable[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]], </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">linspace</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">max_poly_degrees</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: Optional[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">] = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">None</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">):</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.fn = fn</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 多项式回归的范围，使用 np.linspace 的格式</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace = linspace</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input = np.linspace(*linspace)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 使用标准函数 fn 计算标准结果</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected = fn(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 最大搜索几项</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">        self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.max_poly_degrees = max_poly_degrees</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> _fit</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">deg</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, Polynomial]:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 用 Numpy 的 Polynomial 多项式回归类进行回归</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        fit: Polynomial = Polynomial.fit(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input, </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected, deg, </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">domain</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]), </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">window</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">], </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.linspace[</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">]))</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 使用回归出来的多项式函数计算结果</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        result = fit(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.input)</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 计算误差</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error_percent = np.fabs((result - </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected) / </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.expected)</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        max_error_percent = np.max(error_percent[error_percent &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1e308</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">] * </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> max_error_percent, fit</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> run</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Tuple[</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">float</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, Polynomial]:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 从 1 到 max_poly_degrees 项，搜索误差最小的拟合结果</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error, poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">._fit(</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        for</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> deg </span><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">in</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> range</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">2</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.max_poly_degrees+</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">):</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">            _error, _poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">._fit(deg)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">            if</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> _error &#x3C; error:</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">                error = _error</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">                poly = _poly</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> error, poly</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    def</span><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26"> explain</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) -> Polynomial:</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">        # 输出结果，其中 Coefficients 输出的回归结果 JSON 可以直接复制进 Nix 代码</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">        error, poly = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">self</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.run()</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Degree: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">poly.degree()</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Error %: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">error</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">        print</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">f</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"Coefficients: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">{</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">json.dumps(json.dumps(</span><span style="--shiki-dark:#4EC9B0;--shiki-light:#267F99">list</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">(poly.coef)))</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">}</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#C586C0;--shiki-light:#AF00DB">        return</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> poly</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">Approximate(np.exp, (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">, </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">10000</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">), </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">max_poly_degrees</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">=</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">100</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">).explain()</span></span></code></pre>
<p>对照同样基于多项式回归的 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>arctan</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\arctan</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6151em;"></span><span class="mop">arctan</span></span></span></span> 函数，就可以实现 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>exp</mi><mo>⁡</mo></mrow><annotation encoding="application/x-tex">\exp</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mop">exp</span></span></span></span> 函数：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">  # 取整函数</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> -</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">floor</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 提取输入的整数部分</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 提取输入的小数部分</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      x_decimal</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 使用 Python 和 Numpy 计算出的多项式系数</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      decimal_poly</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">builtins</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">fromJSON</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "[0.9999999999999997, 0.9999999999999494, 0.5000000000013429, 0.16666666664916754, 0.04166666680065545, 0.008333332669176907, 0.001388891142716621, 0.00019840730702746657, 2.481076351588151e-05, 2.744709498016379e-06, 2.846575263734758e-07, 2.0215584670370862e-08, 3.542885385105854e-09]"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 计算绝对值指数的倒数</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      1</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> / (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">))</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 分开计算整数部分和小数部分的指数，然后相乘</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">      (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">_pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> e</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x_int</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) * (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">polynomial</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x_decimal</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> decimal_poly</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="浮点次幂指数函数-pow">浮点次幂指数函数 pow</h2>
<p>有了以上函数，我们就可以用 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>x</mi><mi>n</mi></msup><mo>=</mo><mi>exp</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>n</mi><mo>∗</mo><mi>ln</mi><mo>⁡</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">x^n = \exp (n * \ln x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6644em;"></span><span class="mord"><span class="mord mathnormal">x</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.6644em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">exp</span><span class="mopen">(</span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mop">ln</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span>
计算浮点次幂了。唯一要注意的就是各种特殊情况：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  pow</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> =</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">    x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">:</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    let</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 判断指数是否为整数</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">      is_int_times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">abs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> - </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">) &#x3C; </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">epsilon</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    in</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> is_int_times</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 是整数时使用已有的整数幂运算函数，速度更快，并且可以处理 x &#x3C; 0 时的情况</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      _pow_int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">int</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">)</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> == </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 底数为 0，任意次幂都是 0</span></span>
<span class="line"><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">      0</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> if</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> &#x3C; </span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658">0</span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF"> then</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 底数为负，无法计算浮点次幂，因为我们不支持虚数功能</span></span>
<span class="line"><span style="--shiki-dark:#DCDCAA;--shiki-light:#795E26">      throw</span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515"> "Calculating power of negative base and decimal exponential is not supported"</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">    else</span></span>
<span class="line"><span style="--shiki-dark:#6A9955;--shiki-light:#008000">      # 使用上述公式计算结果</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">      exp</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> (</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">times</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> * </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080"> x</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">);</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>
<h2 id="总结">总结</h2>
<p>以上对数和指数函数（和一些额外的数学函数）可以在我的 GitHub 获取：<a href="https://github.com/xddxdd/nix-math" rel="noopener noreferrer" target="_blank">https://github.com/xddxdd/nix-math</a></p>
<p>如果你使用 Nix Flake，可以用以下方式使用这些函数：</p>
<pre class="shiki shiki-themes dark-plus light-plus" style="--shiki-dark:#D4D4D4;--shiki-light:#000000;--shiki-dark-bg:#1E1E1E;--shiki-light-bg:#FFFFFF" tabindex="0"><code><span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = {</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    nix-math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">url</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#CE9178;--shiki-light:#A31515">"github:xddxdd/nix-math"</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">  outputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">: </span><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">let</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">inputs</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">nix-math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">lib</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#569CD6;--shiki-light:#0000FF">  in</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">{</span></span>
<span class="line"><span style="--shiki-dark:#9CDCFE;--shiki-light:#E50000">    value</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000"> = </span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">math</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">.</span><span style="--shiki-dark:#9CDCFE;--shiki-light:#001080">ln</span><span style="--shiki-dark:#B5CEA8;--shiki-light:#098658"> 123</span><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">;</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">  };</span></span>
<span class="line"><span style="--shiki-dark:#D4D4D4;--shiki-light:#000000">}</span></span></code></pre>]]></content>
        <published>2025-05-19T23:02:28.000Z</published>
        <rights>Copyright 2012-2026 Lan Tian @ Blog</rights>
    </entry>
</feed>