现充|junyu33

Learn intranet penetration the hard way (CanMV-K230 version)

Please note that this article is not a tutorial for readers who are new to intranet penetration, but rather a follow-up to the article "Building a Fast and Secure VNC Service from Scratch."

Problem Introduction

If you clicked into this article, you should already be able to perform intranet penetration using a public network server. However, the Azure server I'm using for free is not located domestically, and even though it can connect to my workstation computer, the speed somewhat affects the user experience.

In the previous article, "Building a Fast and Secure VNC Service from Scratch," I mentioned that my dormitory has a dynamic public IP after broadband dial-up, which can largely solve the issue of slow connection speeds when using a cloud server to access my workstation. However, I recently learned from a graduate student senior that the network cables in the graduate dormitory do not support public IPs. This means that my current solution for remotely connecting to my workstation computer will become completely ineffective after I graduate from my undergraduate studies—how can I tolerate that? At the time, I hadn't come up with a good solution.

This unfortunate reality took a slight turn when, due to recent research needs, I purchased a development board supporting the RVV 1.0 CPU. I realized I could transfer the role of my current computer as an SSH jump server to the newly purchased development board. After graduation, I could hand the development board to a junior student, who would continue to connect it to the broadband, allowing me to maintain the ideal of "working from home."

Goal Breakdown

Now, we need such a development board to fulfill the following four functions:

  1. Automatically boot the system on startup.
  2. Automatically perform PPPoE dial-up on startup.
  3. Automatically run the previously written DDNS script on startup.
  4. Allow me to conduct development related to RVV 1.0 on the development board.

To meet the fourth requirement, after several hours of STFW (Searching The Friendly Web), I ruled out most off-the-shelf development boards on the market. I found that only Canaan's CanMV-K230 meets my needs (though later someone told me that "Jindie Shikong" also has a model supporting RVV 1.0, I excluded it due to its large size and high price). After reviewing Canaan's user documentation, I further outlined the specific implementation steps:

  1. Purchase the CanMV-K230 development board.
  2. Purchase a suitable SD card and insert it into the development board.
  3. Flash the official system image.
  4. Boot the system, insert the Ethernet cable into the development board, and test whether dial-up works normally.
  5. Copy the DDNS script from the host to the development board and test whether the DDNS service works normally.
  6. SSH into my domain from another device to see if I can connect to both the development board and my workstation computer.

It seems straightforward, but in reality, each step is fraught with pitfalls.

Pitfall Experience

Purchasing the CanMV-K230 Development Board

The first step, of course, was to buy the development board. I searched for "CanMV-K230" on Taobao, and a bunch of options popped up. I clicked on the one with the highest sales volume, which was from the official 01studio flagship store. To my surprise, the memory was listed as 1GB instead of the official 512MB? Amazing! With technology advancing so quickly, I had to grab it right away. So, from opening Taobao to placing the order, it took me less than two minutes—I just made sure the model was indeed K230 and the CPU version was C908 (supporting RVV 1.0).

Three days later, I received the package. The seller had also included a 32GB SD card. Following Canaan's documentation, I started flashing the firmware. I first chose the image labeled with "01studio" and encountered no issues during the flashing process. Then, I connected the power, opened minicom, and—huh? Why is there a Python shell? I tried typing exit, but the command wasn't supported. I pressed Ctrl+D, but the screen just flashed and rebooted back into Python. Did I just spend 284 yuan for a Python shell?

I asked a friend and learned that what I was seeing was actually a MicroPython shell. He tried jailbreaking it but didn't succeed. I then tried other firmware images from Canaan's official website and found that none of them worked except the one with the "01studio" label. To make matters worse, the development board doesn't come with an Ethernet port, so I had to separately purchase a USB-to-Ethernet adapter. At that point, I was already considering returning it.

Over the next few days, I attempted to manually compile the K230-SDK and K230-linux-SDK provided by Canaan for the CanMV-K230 and generate my own images. Without exception, either the board failed to boot, or it booted straight into that annoying MicroPython interface. I also contacted Taobao customer service, who passed the buck to the 01studio communication group. 01studio, in turn, directed me to Canaan, and Canaan finally suggested I try connecting to the Linux debug serial port using a USB programmer. But even that didn't work.

I figured that since 01studio is essentially a downstream partner of Canaan and their target customers are those focused on AI training (CanMV K230 AI Development Board)—not me—and given that this board felt a bit "knockoff" compared to Canaan's official offerings, further debugging might lead to more issues without official support. So, the day after failing with serial debugging—the fifth day after receiving the board (since returns within seven days are hassle-free, with only shipping fees required)—I officially submitted my return request.

It feels like everything has to be branded with "AI" to sell these days. AI has really become the ultimate buzzword.

Purchasing a Suitable SD Card and Inserting It into the Development Board

On the day of the refund, I bought another official Canaan development board for 283 yuan (original price 323, without an SD card), and also purchased a 64GB SD card for 17 yuan (original price 25).

Experienced buyers might notice that the SD card price seems off—and indeed it was. At the time, I believed in the principle "you get what you pay for," so I chose the more expensive development board but didn't think too much about the SD card. Since the product had a high positive review rate, I figured if it turned out to be fake, I could just return it and quickly buy a more expensive one nearby—no big loss.

What was strange was that the SD card's logistics were updated in real-time, while the development board's tracking only showed: "Shipped," "Picked Up," and "Out for Delivery." After three full days with no updates, the SD card had already reached the local distribution center. I began to suspect that I had bought another fake development board, so on the morning of the third day, I called the delivery person. They assured me it had also reached the distribution center, which eased my worries a bit.

By the evening of the third day, my SD card had arrived, but there was still no movement on the development board. Since it was outside business hours, I didn't want to bother the delivery person again, so I waited until the next morning to ask once more. The delivery person said it had arrived and gave me the pickup code. I was puzzled—why hadn't Taobao notified me that the item had arrived? But after unboxing it, the development board looked exactly like the one on Canaan's official website, so I couldn't stay upset.

Next, I used the SD card that arrived the previous day to install the system, using the Debian image from Canaan's official website. After flashing it with dd, I used GParted to expand the Linux partition from 1GB to 58GB. Then, I booted Linux from the development board and encountered the following error:

junyu33@zjy-canmv:~$ sudo apt install net-tools                                                                                                               
[sudo] password for junyu33:              
E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem. 
junyu33@zjy-canmv:~$ sudo dpkg --configure -a
dpkg: error: parsing file '/var/lib/dpkg/updates/0000' near line 0:
 MSDOS end of file (^Z) in field name ''

I had never seen this error before, so I started to suspect that my SD card might be faulty. I pasted the error into ChatGPT, which suggested deleting /var/lib/dpkg/updates/0000. What followed was this:

junyu33@zjy-canmv:~$ sudo rm /var/lib/dpkg/updates/*                                                                                                          
[  151.617623] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6563
[  151.631373] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6563
rm: cannot remove '/[  153.220073] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6719
var/lib/dpkg/updates/0007': Stru[  153.234325] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6719
cture needs cleaning
rm: cannot remove '/[  153.248629] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 8089
var/lib/dpkg/updates/0008': Structure needs cleaning
[  153.321093] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 8089
rm: cannot remove '/[  153.334419] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 8144
var/lib/dpkg/updates/0010': Stru[  153.348760] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 8144
cture needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0011': Structure needs cleaning
[  153.457330] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6557
[  153.470351] EXT4-fs error (device mmcblk1p3): ext4_lookup:1708: inode #13720: comm rm: deleted inode referenced: 6557
rm: cannot remove '/var/lib/dpkg/updates/0013': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0014': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0015': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0016': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0017': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0018': Structure needs cleaning
rm: cannot remove '/var/lib/dpkg/updates/0019': Structure needs cleaning

At that moment, I thought, "Well, that's it—time to prepare for a return." But I still tried repairing it with fsck, and it seemed okay temporarily. However, after installing a bunch of software and rebooting, the "dpkg was interrupted" error appeared again. I realized I needed to check if the SD card was counterfeit (the last time I bought a fake product was in middle school—a "128GB" USB drive that was actually only 8GB). I searched online for tools and found this link.

I followed Method 2 from the link and indeed found the number "08" on the back. The brand was "Jinsudun" (which looks a lot like Kingston but isn't). There was no doubt—it was fake. I immediately started testing with expansion detection software.

F3 read 8.0
Copyright (C) 2010 Digirati Internet LTDA.
This is free software; see the source for copying conditions.

                  SECTORS      ok/corrupted/changed/overwritten
Validating file 1.h2w ... 2097152/        0/      0/      0
Validating file 2.h2w ... 2097152/        0/      0/      0
Validating file 3.h2w ... 2050304/    46848/      0/      0
Validating file 4.h2w ...       0/  2097152/      0/      0
Validating file 5.h2w ...       0/  2097152/      0/      0
Validating file 6.h2w ...       0/  2097152/      0/      0
Validating file 7.h2w ...       0/  2097152/      0/      0
Validating file 8.h2w ...       0/  2097152/      0/      0
Validating file 9.h2w ...       0/  2097152/      0/      0
Validating file 10.h2w ...       0/  2097152/      0/      0
Validating file 11.h2w ...       0/  2097152/      0/      0
Validating file 12.h2w ...       0/  2097152/      0/      0
Validating file 13.h2w ...       0/  2097152/      0/      0
Validating file 14.h2w ...       0/  2097152/      0/      0
Validating file 15.h2w ...       0/  2097152/      0/      0
Validating file 16.h2w ...       0/  2097152/      0/      0
Validating file 17.h2w ...       0/  2097152/      0/      0
Validating file 18.h2w ...       0/  2097152/      0/      0
Validating file 19.h2w ...       0/  2097152/      0/      0
Validating file 20.h2w ...       0/  2097152/      0/      0
Validating file 21.h2w ...       0/  2097152/      0/      0
Validating file 22.h2w ...       0/  2097152/      0/      0
Validating file 23.h2w ...       0/  2097152/      0/      0
Validating file 24.h2w ...       0/  2097152/      0/      0
Validating file 25.h2w ...       0/  2097152/      0/      0
Validating file 26.h2w ...       0/  2097152/      0/      0
Validating file 27.h2w ...       0/  2097152/      0/      0
Validating file 28.h2w ...       0/  2097152/      0/      0
Validating file 29.h2w ...       0/  2097152/      0/      0
Validating file 30.h2w ...       0/  2097152/      0/      0
Validating file 31.h2w ...       0/  2097152/      0/      0
Validating file 32.h2w ...       0/  2097152/      0/      0
Validating file 33.h2w ...       0/  2097152/      0/      0
Validating file 34.h2w ...       0/  2097152/      0/      0
Validating file 35.h2w ...       0/  2097152/      0/      0
Validating file 36.h2w ...       0/  2097152/      0/      0
Validating file 37.h2w ...       0/  2097152/      0/      0
Validating file 38.h2w ...       0/  2097152/      0/      0
Validating file 39.h2w ...       0/  2097152/      0/      0
Validating file 40.h2w ...       0/  2097152/      0/      0
Validating file 41.h2w ...       0/  2097152/      0/      0
Validating file 42.h2w ...       0/  2097152/      0/      0
Validating file 43.h2w ...       0/  2097152/      0/      0
Validating file 44.h2w ...       0/  2097152/      0/      0
Validating file 45.h2w ...       0/  2097152/      0/      0
Validating file 46.h2w ...       0/  2097152/      0/      0
Validating file 47.h2w ...       0/  2097152/      0/      0
Validating file 48.h2w ...       0/  2097152/      0/      0
Validating file 49.h2w ...       0/  2097152/      0/      0
Validating file 50.h2w ...       0/  2097152/      0/      0
Validating file 51.h2w ...       0/  2097152/      0/      0
Validating file 52.h2w ...       0/  2097152/      0/      0
Validating file 53.h2w ...       0/  2097152/      0/      0
Validating file 54.h2w ...       0/  2097152/      0/      0
Validating file 55.h2w ... 1947392/   149760/      0/      0
Validating file 56.h2w ... 2097152/        0/      0/      0
Validating file 57.h2w ... 2097152/        0/      0/      0
Validating file 58.h2w ... 2097152/        0/      0/      0
Validating file 59.h2w ... 1212160/        0/      0/      0

  Data OK: 7.48 GB (15695616 sectors)
Data LOST: 51.09 GB (107151360 sectors)
	       Corrupted: 51.09 GB (107151360 sectors)
	Slightly changed: 0.00 Byte (0 sectors)
	     Overwritten: 0.00 Byte (0 sectors)
Average reading speed: 16.12 MB/s

The results confirmed it was an 8GB counterfeit SD card, with only the first 3GB and last 5GB being usable. I immediately submitted a full refund request (without returning the product), and within a minute, my 17 yuan was refunded.

Additionally, I took a look at the Taobao reviews and found only a few dozen negative comments (which is very few compared to tens of thousands of positive reviews). The negative reviews all complained about the SD card's quality and cursed the unscrupulous seller. Now I finally understand why my sister says, "When shopping on Taobao, only look at the negative reviews."

On the day of writing, I checked the product's reviews again and found that even those few dozen negative comments were gone. I have nothing more to say.

Of course, even though I wasted half a day dealing with a fake SD card, I still had to complete my task. I figured these things should be easy to find anywhere, so the next morning I checked Meituan (a delivery app) and chose a well-known brand (SanDisk). The delivery arrived in less than half an hour, but when I saw it—wait, why is this SD card so big? I searched online and realized that what I thought was a "small SD card" was actually a TF card (or microSD card). Unfortunately, I had bought the wrong thing again.

Since the store was only a few hundred meters from my dorm, I decided it would be easier to walk there and return it. At the store, I explained that I had bought the wrong product, and the seller was willing to accept the return. I asked if they had TF cards, and she said she wasn't sure but would look. Eventually, she found one—a ThinkPlus brand. It might not be as well-known as major brands, but it was definitely much better than "Jinsudun." The seller said they only had 32GB and 128GB options. Considering I would need to download a bunch of toolchains and compile a lot of software, I decided to play it safe and buy the 128GB card.

The price at the physical store was 90 yuan, but I could get it for just over 50 yuan on Meituan. I have no idea where Meituan gets the money to cover that 40-yuan difference.

After getting the 128GB TF card, I tested the first 5GB with f3, and no issues were found. Then I flashed the system image. As of the writing date, no problems have occurred, and the possibility of it being counterfeit has basically been ruled out.

Flashing the Officially Provided System Image

As mentioned earlier, there are two different types of official images: one is an RTOS with micropython running on the large core and Linux on the small core, while the other is an image designed for Debian- and Ubuntu-based distributions. Naturally, the latter better suits my needs, so I flashed the Debian image onto my TF card. Finally, I saw the word "Debian" in the minicom output.

Is that the end of it? Far from it!

Then, I discovered a critical flaw—this Debian system has no WiFi interface! However, according to Canaan's documentation, this development board definitely has WiFi hardware. I immediately reflashed the former image and accessed the debugging serial port of the small core. Here's what I found:

  1. The small core runs a Buildroot-based Linux.
  2. The small core has a WiFi interface and can connect to WiFi normally.
  3. The small core only has 128MB of memory.

So, we're faced with a dilemma:

So, which image should we choose? The answer is—only children make choices.

Building a Custom System Image

Based on our previous observations, we can rule out issues related to hardware and Linux driver compatibility. This implies that the problem can definitely be resolved through our own efforts. In other words, we can address this issue by appropriately "assembling" the MicroPython image and the Debian image.

As for the specific solution, the approaches I have attempted include the following:

  1. Directly hacking the two images in their current state
  2. Attempting to modify the SDK source code used for image compilation to obtain an image that meets our requirements
  3. Attempting to modify the SDK source code used for image compilation and then hacking the resulting image

In-place Hack

We can observe the structure of these two images:

Micropython image:

> mmls CanMV-K230_sdcard_v1.8_nncase_v2.9.0.img 
GUID Partition Table (EFI)
Offset Sector: 0
Units are in 512-byte sectors

      Slot      Start        End          Length       Description
000:  Meta      0000000000   0000000000   0000000001   Safety Table
001:  -------   0000000000   0000020479   0000020480   Unallocated
002:  Meta      0000000001   0000000001   0000000001   GPT Header
003:  Meta      0000000002   0000000033   0000000032   Partition Table
004:  000       0000020480   0000061439   0000040960   rtt
005:  001       0000061440   0000163839   0000102400   linux
006:  -------   0000163840   0000262143   0000098304   Unallocated
007:  002       0000262144   0000425983   0000163840   rootfs
008:  003       0000425984   0000950271   0000524288   fat32appfs
009:  -------   0000950272   0000950304   0000000033   Unallocated

Debian image:

> mmls CanMV-K230_debian_sdcard_sdk_1.3.img 
GUID Partition Table (EFI)
Offset Sector: 0
Units are in 512-byte sectors

      Slot      Start        End          Length       Description
000:  Meta      0000000000   0000000000   0000000001   Safety Table
001:  -------   0000000000   0000020479   0000020480   Unallocated
002:  Meta      0000000001   0000000001   0000000001   GPT Header
003:  Meta      0000000002   0000000033   0000000032   Partition Table
004:  000       0000020480   0000061439   0000040960   rtt
005:  001       0000061440   0000163839   0000102400   linux
006:  -------   0000163840   0000262143   0000098304   Unallocated
007:  002       0000262144   0003515591   0003253448   rootfs
008:  -------   0003515592   0003515624   0000000033   Unallocated

So we can see that only the rootfs partition size differs, and the Debian image lacks the fat32appfs partition. From previous research, we know this partition is used for AI training applications, which is irrelevant to me and can be discarded, so it can essentially be considered part of rootfs. Therefore, the structure of both images is identical and can be replaced on a partition-by-partition basis.

Based on prior judgment, WiFi works normally in the Micropython image. Since WiFi, as part of the driver, is either loaded by the Linux kernel or via DKMS in /lib/modules, to be safe:

Then I burned it to the TF card and booted the development board. dmesg reported missing WiFi firmware, specifically /etc/firmware/config.txt, /etc/firmware/fw_bcm43438a1.bin, and /etc/firmware/nvram.txt. I found the corresponding paths in the Micropython image, copied them over, and rebooted again. This time, it reported errors:

[  OK  ] Started NetworkManager.service - Network Manager.
[   32.925997] [dhd] failed to power up DHD generic adapter, 0 retry left
[  OK  ] Started NetworkManag[   32.955283] [dhd] wifi_platform_set_power = 0, delay: 0 msec
er-dispatcher.�[   32.961896] [dhd] ======== PULL WL_REG_ON(1) LOW! ========
� Manager Sc[   32.968726] [dhd] wifi_platform_bus_enumerate device present 0
ript Dispatcher [   32.975930] [dhd] ======== Card detection to remove SDIO card! ========
Service.
[   32.983935] [dhd] failed to power up DHD generic adapter, max retry reached**
[   32.992006] [dhd] unregister wifi platform drivers
[   32.996807] [dhd] wifi_platform_bus_enumerate device present 0
[   33.002648] [dhd] ======== Card detection to remove SDIO card! ========
[   33.009272] [dhd] dhd_wlan_deinit_gpio: gpio_free(WL_REG_ON 1)
[   33.015121] [dhd] _dhd_module_init: Failed to load the driver, try cnt 0
[   33.021848] [dhd] _dhd_module_init: Failed to load driver max retry reached**
[   33.028996] [dhd] STATIC-MSG) dhd_static_buf_exit : Enter
[   33.034468] [dhd] _dhd_module_init: Exit err=-19
[***   ] Job ifupdown-pre.service/start running (23s / 3min 3s)
[   39.325657] [dhd] _dhd_module_init: in Dongle Host Driver, version 101.10.361.33 (wlan=r892223-20230607-2)
[   39.325657] drivers/net/wireless/bcmdhd compiled on Aug 26 2024 at 15:28:32
[   39.325657] 
[   39.343823] [dhd] STATIC-MSG) dhd_static_buf_init : 101.10.361.31 (wlan=r892223-20230427-1)
[   39.352249] [dhd] STATIC-MSG) dhd_init_wlan_mem : prealloc ok for index 0: 1100800(1075K)
[   39.360468] [dhd] ======== Get GPIO from DTS(android,bcmdhd_wlan) ========
[   39.367383] of_get_named_gpiod_flags: parsed 'gpio_wl_reg_on' property of node '/soc/sdhci0@91580000/bcmdhd_wlan[0]' - status (0)
[   39.379060] [dhd] dhd_wlan_init_gpio: WL_REG_ON=1
[   39.383772] [dhd] dhd_wifi_platform_load: Enter
[   39.388309] [dhd] Power-up adapter 'DHD generic adapter'
[   39.395030] dummy_sdmmc: probe of mmc0:0001:1 failed with error -110
[   39.401552] dummy_sdmmc: probe of mmc0:0001:2 failed with error -110
[   39.408008] [dhd] wifi_platform_set_power = 1, delay: 200 msec
[   39.413855] [dhd] ======== PULL WL_REG_ON(1) HIGH! ========
[   39.625906] [dhd] wifi_platform_bus_enumerate device present 1
[     *] Job ifupdown-pre.service/start running (25s / 3min 3s)
[   41.661915] [dhd] failed to power up DHD generic adapter, 0 retry left
[   41.685982] [dhd] wifi_platform_set_power = 0, delay: 0 msec
[   41.691662] [dhd] ======== PULL WL_REG_ON(1) LOW! ========
[   41.697179] [dhd] wifi_platform_bus_enumerate device present 0
[   41.703022] [dhd] ======== Card detection to remove SDIO card! ========
[   41.709653] [dhd] failed to power up DHD generic adapter, max retry reached**
[   41.716795] [dhd] unregister wifi platform drivers
[   41.721592] [dhd] wifi_platform_bus_enumerate device present 0
[   41.727435] [dhd] ======== Card detection to remove SDIO card! ========
[   41.734059] [dhd] dhd_wlan_deinit_gpio: gpio_free(WL_REG_ON 1)
[   41.739903] [dhd] _dhd_module_init: Failed to load the driver, try cnt 0
[   41.746641] [dhd] Error creating socket.
[   41.750584] [dhd] _dhd_module_init: Failed to load driver max retry reached**
[   41.757732] [dhd] STATIC-MSG) dhd_static_buf_exit : Enter
[  OK  ] Finished ifupdown-pre.service - He…o synchronize boot up for ifupdown.

I searched online but couldn't find a suitable solution. Additionally, a new problem emerged: my Debian's memory was also reduced to 128M!

So I extracted the DTB file from the linux partition, decompiled it into a DTS file, attempted to modify the memory size configured for Linux, recompiled it, embedded it back into the linux partition, rewrote the TF card, and rebooted again—but the sha256sum didn't match!

To proceed further, I would need to read the source code for image generation to understand the sha256 generation logic. Since I'd have to read the source code anyway, it might be better to just recompile from the SDK! Thus, we arrive at the next solution:

Compiling the SDK

As mentioned earlier, the relevant SDKs for my needs are K230-SDK and K230-linux-SDK. I attempted to compile them again, and the results were slightly different from the official images: one generated a usable Debian image with 512MB of memory but without WiFi support; the other generated a Buildroot-based image with WiFi support and 512MB of memory.

Clearly, there are two paths to choose from:

The first path appears to require less debugging, and indeed it does. This process can be broken down into the following sub-steps:

  1. Rebuild a Debian-based rootfs.ext4.
  2. Locate the code that uses rootfs.ext4 to generate the Buildroot image.
  3. Manually execute this code with the correct parameters and environment variables to generate an image that uses the Debian rootfs instead of the Linux-SDK's Buildroot.

Building debian.ext4

The K230-SDK documentation provides the method. Here is the build code:

sudo rm -rf debian13
sudo apt-get update
sudo apt install qemu-user-static binfmt-support debootstrap debian-ports-archive-keyring systemd-container rsync wget
sudo debootstrap --arch=riscv64  unstable debian13 https://mirrors.aliyun.com/debian/
sudo chroot debian13/
echo "root:root" | chpasswd

cat >>/etc/network/interfaces <<EOF
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
apt-get install -y  net-tools ntpdate
ntpdate ntp.ntsc.ac.cn
exit
sudo  mkfs.ext4 -d debian13  -r 1 -N 0 -m 1 -L "rootfs" -O ^64bit debian13.ext4 1G

Note: Do not forget to copy the /etc/firmware and /lib/modules folders from the previously generated K230-linux-SDK image into debian13.ext4.

Locating the Image Generation Code

Search for the string sysimage-sdcard.img in the K230-linux-SDK project:

> egrep -i --recursive "sysimage-sdcard.img" .
./.github/workflows/build.yml:          rm -rf output/${CONF}/images/sysimage-sdcard.img
./.gitlab-ci.yml:  - rm -rf output/${CONF}/images/sysimage-sdcard.img
./.gitlab-ci.yml:            sudo cp -rf --sparse=always -L ${src_file} ${DST_DIR}/${SUB_DIR}/sysimage-sdcard.img.gz;
./.gitlab-ci.yml:            sysimage_md5=$(md5sum ${DST_DIR}/${SUB_DIR}/sysimage-sdcard.img.gz | awk '{print $1}');
./.gitlab-ci.yml:            echo "sysimage_path=${DST_DIR}/${SUB_DIR}/sysimage-sdcard.img.gz" >> $CI_PROJECT_DIR/build.env;
./.gitlab-ci.yml:    - echo "sysimage-sdcard.img.gz md5 ${sysimage_md5}"
./README.md:output/k230d_canmv_defconfig/images/sysimage-sdcard.img.gz
./buildroot-overlay/board/canaan/k230-soc/genimage.cfg:image sysimage-sdcard.img {
./buildroot-overlay/board/canaan/k230-soc/post-image.sh:        local image_name="$2"; #"sysimage-sdcard.img"
./buildroot-overlay/board/canaan/k230-soc/post-image.sh:gen_image ${GENIMAGE_CFG_SD}   sysimage-sdcard.img
./buildroot-overlay/boot/uboot/u-boot-2022.10-overlay/include/configs/k230_evb.h:       "upsdimg=usb start; dhcp;  tftp 0x9000000 10.10.1.94:wjx/sysimage-sdcard.img.gz;gzwrite mmc 1 0x$fileaddr  0x$filesize; \0" \
......

It appears that ./buildroot-overlay/board/canaan/k230-soc/post-image.sh meets our requirements. Let's examine the file:

> tail -n 10 ./buildroot-overlay/board/canaan/k230-soc/post-image.sh
        ${UBOOT_BUILD_DIR}/tools/mkimage -A riscv -O linux -T kernel -C none -a 0 -e 0 -n linux -d ${BINARIES_DIR}/fw_jump.bin  boot/fw_jump_add_uboot_head.bin
        rm -rf boot.ext4 ;fakeroot mkfs.ext4 -d boot  -r 1 -N 0 -m 1 -L "boot" -O ^64bit boot.ext4 45M
}

gen_uboot_bin
gen_env_bin
#gen_linux_bin;
gen_boot_ext4

gen_image ${GENIMAGE_CFG_SD}   sysimage-sdcard.img

As the name suggests, this should be the final code for generating the image.

Manually Executing the Code

When reading the following code:

The code is too lengthy to include here; you can view it at the repository link.

You might feel overwhelmed. The logic is relatively complex. There's no need to scrutinize it carefully (since we only want to replace the original rootfs.ext4 with our debian.ext4), but running it directly will cause errors due to dependencies on certain parameters and environment variables. Therefore, manual instrumentation is required. The modifications are as follows:

#!/bin/bash
set -e

# Print the executed command and all parameters
echo "###########################################################################################"
echo "Command: $0"
echo "All Parameters: $@"

# Print the PATH environment variable
printenv
echo "###########################################################################################"

DTB="k230-canmv.dtb"
LINUX_DIR=${BUILD_DIR}/linux-6.6.22
rootfs_ext4_file=""

[ $# -ge 2 ] && DTB="$2.dtb"
[ $# -ge 3 ] && LINUX_DIR="$3"
[ $# -ge 4 ] && rootfs_ext4_file="$4"


echo "###########################################################################################"
echo "${DTB}, ${rootfs_ext4_file}"
echo "###########################################################################################"

#BINARIES_DIR=/home/wangjianxin/k230_linux_sdk/output/k230_canmv_defconfig/images
UBOOT_BUILD_DIR=${BUILD_DIR}/uboot-2022.10
K230_SDK_ROOT=$(dirname $(dirname ${BASE_DIR}))
GENIMAGE_CFG_SD=$(dirname $(realpath "$0"))/genimage.cfg
env_dir=$(dirname $(realpath "$0"))

Then, rerun the top-level Makefile. We will get output similar to this:

###########################################################################################
Command: board/canaan/k230-soc/post-image.sh
All Parameters: /home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/images k230-canmv /home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/build/linux-3c3f4061b727e6d0e2293357a7475b578d688d92
SHELL=/bin/bash
LSCOLORS=Gxfxcxdxbxegedabagacad
BR2_CONFIG=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/.config
SESSION_MANAGER=local/zjy:@/tmp/.ICE-unix/2277,unix/zjy:/tmp/.ICE-unix/2277
WINDOWID=121634819
QT_ACCESSIBILITY=1
COLORTERM=truecolor
XDG_CONFIG_DIRS=/etc/xdg/xdg-xfce:/etc/xdg
PERL=/usr/bin/perl
LESS=-R
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
XDG_MENU_PREFIX=xfce-
TERM_PROGRAM_VERSION=3.4
GTK_IM_MODULE=fcitx
CONDA_EXE=/home/junyu33/anaconda3/bin/conda
_CE_M=
TMUX=/tmp/tmux-1000/default,50921,1
LANGUAGE=en_US:en
......
HG=hg
CONFIG_SHELL=/bin/bash
LC_NUMERIC=en_US.UTF-8
HOSTCXX_NOCCACHE=/usr/lib/ccache/g++
OLDPWD=/home/junyu33/Desktop/github/k230_linux_sdk/buildroot-overlay
TERM_PROGRAM=tmux
_=/usr/bin/printenv
###########################################################################################

We can see that there are three input parameters: the images path, the DTB file name, and the Linux kernel source path. A quick look at the source code reveals that the fourth parameter is the rootfs.ext4 file path.

For the environment variables, first capture all environment variables and save them as A. Then run printenv without the script and save the result as B. Sort both A and B, then diff them to clearly see which new environment variables the script introduces.

Finally, we can write a command similar to this (run as root):

ACLOCAL_PATH=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/host/riscv64-buildroot-linux-gnu/sysroot/usr/share/aclocal:/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/host/share/aclocal BINARIES_DIR=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/images BASE_DIR=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig BR2_CONFIG=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/.config BR2_DL_DIR=/home/junyu33/Desktop/github/k230_linux_sdk/output/buildroot-2024.02.1/../../dl BR2_VERSION=2024.02.1 BR2_VERSION_FULL=-g8d50485-dirty BR_COMPILER_PARANOID_UNSAFE_PATH=enabled BUILD_DIR=/home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/build BUILDROOT_CONFIG=/tmp/deprecated/The-BUILDROOT_CONFIG-environment-variable-was-renamed-to-BR2_CONFIG BZR=bzr ./buildroot-overlay/board/canaan/k230-soc/post-image.sh /home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/images k230-canmv /home/junyu33/Desktop/github/k230_linux_sdk/output/k230_canmv_defconfig/build/linux-3c3f4061b727e6d0e2293357a7475b578d688d92 /home/junyu33/Desktop/tmp/debian13.ext4

Write the generated image to a TF card, boot the development board, and this time the board runs Debian, supports WiFi, and even has 512MB of memory—seemingly perfect!

However, the story is not over yet.

Testing Dial-up

Then I attempted to use nmcli for dial-up, but nmcli reported an error again:

root@zjy:/etc/NetworkManager/system-connections# netplan apply

** (generate:2357): WARNING **: 03:00:44.109: Permissions for /etc/netplan/01-netcfg.yaml are too open. Netplan configuration should NOT be accessible by others.
/etc/netplan/01-netcfg.yaml:8:3: Error in network definition: unknown key 'pppoe'
  pppoe:

After searching online, I still couldn't find any similar issues. So, I started debugging from lower-level tools, such as testing whether ppp could work properly:

junyu33@zjy:~$ sudo pon
Couldn't open the /dev/ppp device: No such device or address
/usr/sbin/pppd: Please load the ppp_generic kernel module.

It seems I have to deal with the Linux kernel again. Here, there are two possible approaches:

Compiling Modules In-Place

I brushed up on my Linux kernel compilation knowledge, and kernel.org states:

The command to build an external module is:

$ make -C <path_to_kernel_dir> M=$PWD

The kbuild system knows that an external module is being built due to the "M=<dir>" option given in the command.

So I followed the same approach. The PPP module I need is located in drivers/net/ppp, so I used the following compilation command on the development board:

make -C /home/junyu33/linux-3c3f4061b727e6d0e2293357a7475b578d688d92/ M=$PWD

However, it threw errors again:

/home/junyu33/linux-3c3f4061b727e6d0e2293357a7475b578d688d92/Makefile:1325: warning: overriding recipe for target 'vdso_install'
arch/riscv/Makefile:144: warning: ignoring old recipe for target 'vdso_install'
  CALL    scripts/checksyscalls.sh
  LDS     arch/riscv/kernel/vdso/vdso.lds
  VDSO32AS arch/riscv/kernel/vdso/rt_sigreturn-32.o
  VDSO32AS arch/riscv/kernel/vdso/getcpu-32.o
  VDSO32AS arch/riscv/kernel/vdso/flush_icache-32.o
  VDSO32AS arch/riscv/kernel/vdso/sys_hwprobe-32.o
  VDSO32AS arch/riscv/kernel/vdso/note-32.o
  VDSO32CC arch/riscv/kernel/vdso/hwprobe-32.o
  VDSO32LD  arch/riscv/kernel/vdso/vdso32.so.dbg
  OBJCOPY arch/riscv/kernel/vdso/vdso32.so
  VDSO32SYM include/generated/vdso32-offsets.h
  LDS     arch/riscv/kernel/vdso/vdso.lds
  VDSO64AS arch/riscv/kernel/vdso/rt_sigreturn-64.o
  VDSO64AS arch/riscv/kernel/vdso/getcpu-64.o
  VDSO64AS arch/riscv/kernel/vdso/flush_icache-64.o
  VDSO64AS arch/riscv/kernel/vdso/sys_hwprobe-64.o
  VDSO64AS arch/riscv/kernel/vdso/note-64.o
  VDSO64CC arch/riscv/kernel/vdso/vgettimeofday-64.o
  VDSO64CC arch/riscv/kernel/vdso/hwprobe-64.o
  VDSO64LD  arch/riscv/kernel/vdso/vdso64.so.dbg
  OBJCOPY arch/riscv/kernel/vdso/vdso64.so
  VDSO64SYM include/generated/vdso64-offsets.h
  MODPOST Module.symvers
ERROR: modpost: "slhc_free" [drivers/net/ppp/ppp_generic.ko] undefined!
ERROR: modpost: "slhc_uncompress" [drivers/net/ppp/ppp_generic.ko] undefined!
ERROR: modpost: "slhc_toss" [drivers/net/ppp/ppp_generic.ko] undefined!
ERROR: modpost: "slhc_remember" [drivers/net/ppp/ppp_generic.ko] undefined!
ERROR: modpost: "slhc_compress" [drivers/net/ppp/ppp_generic.ko] undefined!
ERROR: modpost: "slhc_init" [drivers/net/ppp/ppp_generic.ko] undefined!
make[2]: *** [scripts/Makefile.modpost:145: Module.symvers] Error 1
make[1]: *** [/home/junyu33/linux-3c3f4061b727e6d0e2293357a7475b578d688d92/Makefile:1903: single_modules] Error 2
make: *** [Makefile:234: __sub-make] Error 2

I tried compiling both in-place and cross-compiling on the host, compiling this specific path and the entire kernel, but encountered this error (or even more errors). Through an LLM, I learned that this happens because PPP's dependency, SLHC, is neither compiled into the kernel nor built as a module, causing the PPP compilation to fail.

I didn't bother looking for where SLHC is located because I was worried that compiling SLHC might reveal further dependencies on other modules. So, I decided to take the second approach.

Compiling the Kernel

Finally, I've reached this damn step, and I'm facing a series of problems again:

  1. Which parameters should I modify?
  2. What compilation commands should I use, and what are the compilation outputs?
  3. Where should I place the outputs in the K230-linux-SDK?

Modifying Parameters

Since we want to enable the ppp_generic feature, according to the Makefile in drivers/net/ppp:

# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the Linux PPP network device drivers.
#

obj-$(CONFIG_PPP) += ppp_generic.o
obj-$(CONFIG_PPP_ASYNC) += ppp_async.o
obj-$(CONFIG_PPP_BSDCOMP) += bsd_comp.o
obj-$(CONFIG_PPP_DEFLATE) += ppp_deflate.o
obj-$(CONFIG_PPP_MPPE) += ppp_mppe.o
obj-$(CONFIG_PPP_SYNC_TTY) += ppp_synctty.o
obj-$(CONFIG_PPPOE) += pppox.o pppoe.o
obj-$(CONFIG_PPPOL2TP) += pppox.o
obj-$(CONFIG_PPTP) += pppox.o pptp.o

Of course, we need to enable CONFIG_PPP=y.

Actually, to be safe, I enabled all the features mentioned here.

Compiling and Generating the Image

Next, we need to understand where the Linux kernel reads these configurations from. According to related documentation, the Linux kernel reads the development board configurations from arch/riscv/config. For the CanMV-K230 development board, the file read is arch/riscv/config/k230_canmv_defconfig.

Additionally, I thought of a trick to bypass the second and third problems. While browsing through the files in the K230-linux-SDK, I accidentally discovered two patches in a certain Linux directory. One of them had the following content:

> cat 0003-driver-net-r8152-use-chip-ID-as-MAC-addr.patch 
From 588af5417c9b1fcc8e9d6d48b996656096d4c322 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=BB=84=E5=AD=90=E6=87=BF?=
 <huangziyi@canaan-creative.com>
Date: Fri, 13 Sep 2024 14:57:10 +0800
Subject: [PATCH] driver: net: r8152 use chip ID as MAC addr
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: 黄子懿 <huangziyi@canaan-creative.com>
---
 drivers/net/usb/r8152.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/net/usb/r8152.c b/drivers/net/usb/r8152.c
index 127b34dcc5b3..65bcef61b1ec 100644
--- a/drivers/net/usb/r8152.c
+++ b/drivers/net/usb/r8152.c
@@ -1808,8 +1808,17 @@ static int determine_ethernet_addr(struct r8152 *tp, struct sockaddr *sa)
        } else if (!is_valid_ether_addr(sa->sa_data)) {
                netif_err(tp, probe, dev, "Invalid ether addr %pM\n",
                          sa->sa_data);
-               eth_hw_addr_random(dev);
-               ether_addr_copy(sa->sa_data, dev->dev_addr);
+               void __iomem *trng_addr = ioremap(0x91213300, 0x100);
+               unsigned int trng_data = readl(trng_addr);
+               iounmap(trng_addr);
+               char mac_addr_hex[6] = {
+                       0x00, 0xe0, 0x4c,
+                       trng_data & 0xff,
+                       (trng_data>>8) & 0xff,
+                       (trng_data>>16) & 0xff
+               };
+               // eth_hw_addr_random(dev);
+               ether_addr_copy(sa->sa_data, mac_addr_hex);
                netif_info(tp, probe, dev, "Random ether addr %pM\n",
                           sa->sa_data);
                return 0;
-- 
2.46.0

I asked an LLM about it and learned that this is in git patch format, which can be generated using git format-patch -1 <commit-hash>. So, I made a separate copy of the Linux source, created a git repository, modified arch/riscv/config/k230_canmv_defconfig to include the PPP-related configurations, and then ran git format-patch -1. The result was as follows:

> cat 0002-add-ppp-for-canmv-k230.patch                  
From 5613ac073bf8d9fcc140542458cb3d05c43228a0 Mon Sep 17 00:00:00 2001
From: junyu33 <2658799217@qq.com>
Date: Wed, 6 Nov 2024 19:07:02 +0800
Subject: [PATCH] add ppp for canmv k230

---
 arch/riscv/configs/k230_defconfig | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/riscv/configs/k230_defconfig b/arch/riscv/configs/k230_defconfig
index 6e579f78c..f4c993f12 100644
--- a/arch/riscv/configs/k230_defconfig
+++ b/arch/riscv/configs/k230_defconfig
@@ -303,3 +303,14 @@ CONFIG_CFG80211_WEXT=y
 CONFIG_MAC80211=y
 CONFIG_MEDIA_USB_SUPPORT=y
 CONFIG_USB_VIDEO_CLASS=y
+
+CONFIG_PPP=y
+CONFIG_PPP_BSDCOMP=y
+CONFIG_PPP_DEFLATE=y
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=y
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOE=y
+CONFIG_PPPOE_HASH_BITS=4
+CONFIG_PPP_ASYNC=y
+CONFIG_PPP_SYNC_TTY=y
-- 
2.43.0

It looked quite similar, so I named it 0002-add-ppp-for-canmv-k230.patch and saved it in the same directory as the other patches. Then, I deleted the Linux source downloaded by the K230-linux-SDK, ran the top-level Makefile of the project again, and generated the image.

Hacking the Image Again

Of course, after putting so much effort into the Debian rootfs, I really didn't want to "restore the system to factory settings." So, once again, I extracted the Linux partition of the Buildroot image and overwrote the corresponding partition of the Debian image, then attempted to boot.

The result of using zcat is as follows:

junyu33@zjy:~$ zcat /proc/config.gz | grep CONFIG_PPP
CONFIG_PPP=y
CONFIG_PPP_BSDCOMP=y
CONFIG_PPP_DEFLATE=y
CONFIG_PPP_FILTER=y
CONFIG_PPP_MPPE=y
CONFIG_PPP_MULTILINK=y
CONFIG_PPPOE=y
# CONFIG_PPPOE_HASH_BITS_1 is not set
# CONFIG_PPPOE_HASH_BITS_2 is not set
CONFIG_PPPOE_HASH_BITS_4=y
# CONFIG_PPPOE_HASH_BITS_8 is not set
CONFIG_PPPOE_HASH_BITS=4
CONFIG_PPP_ASYNC=y
CONFIG_PPP_SYNC_TTY=y

This confirms that our previous modifications to the Linux partition were effective.

pppd also runs normally:

junyu33@zjy:~$ sudo pppd --version
[sudo] password for junyu33: 
pppd version 2.5.0

No issues at all—seems like this kind of hack is really quite useful.

Client-Side Debugging

Through the debugging at the above levels, we have resolved the issue of PPPoE unavailability at the driver level. Now, the only remaining problem lies in the user configuration, which is much simpler, and the subsequent steps are straightforward without many alternative paths to choose from.

Since NetworkManager is already installed, we can directly run the corresponding commands:

sudo nmcli connection add type pppoe ifname enx00e04ca7c468 con-name mypppoe username "<username>" password "<password>"
sudo nmcli connection up mypppoe 

Hmm, the connection still failed. Then, I checked ifconfig and noticed that enx00e04ca7c468 did not have an IPv4 address. So, I added a DHCP command before the connection command:

sudo dhclient enx00e04ca7c468
sudo nmcli connection add type pppoe ifname enx00e04ca7c468 con-name mypppoe username "<username>" password "<password>"
sudo nmcli connection up mypppoe 

This time, the connection succeeded without errors. I then attempted to test the ping using this PPP interface, and the results were as follows:

junyu33@zjy:~$ sudo ping -I ppp0 8.8.8.8
PING 8.8.8.8 (8.8.8.8) from 220.167.40.175 ppp0: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=56 time=33.2 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=56 time=33.4 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=56 time=33.4 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=56 time=33.0 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=56 time=33.4 ms

The dial-up issue has finally been completely resolved.

Testing DDNS

I previously uploaded the script I used for DDNS to GitHub. I cloned it onto the development board, wrote the configuration file, and ran the script—only to be told that curl wasn't installed. I thought, isn't curl a pretty basic tool? How could it not be included?

Then there was the issue of Python package dependencies. Since Python 3.12 restricts the use of pip for installing packages in the system environment, and I didn't feel like installing Anaconda or Virtualenv on this development board—especially since the project only depends on requests—I solved it with one line:

sudo apt install python3-requests

After starting the script, there were no errors. Within a few minutes, I was able to SSH into the development board using my domain name from the host machine.

Testing SSH and Making It Persistent

Now we come to the most important and critical step: since the school dormitory experiences power outages at night, I need to ensure that after a power loss, the DDNS service automatically resumes when power is restored at 6 a.m. the next day. Specifically, we need to achieve the following:

  1. Enable Debian to start on boot.
  2. Automatically initiate dial-up on startup.
  3. Automatically start DDNS on boot.
  4. Allow the development board to operate independently of a computer.

Boot Debian on Startup

This is not difficult. Just enter U-Boot during startup and change the wait time from 3 to 0.

setenv bootdelay 0
saveenv

The consequence of doing this is that I can no longer enter U-Boot, but it doesn't really matter. If I ever need to change it back, I can just reflash the Linux partition.

Auto Dial on Boot

Create a systemd service:

junyu33@zjy:~$ sudo cat /etc/systemd/system/pppoe-connection.service 
[sudo] password for junyu33: 
[Unit]
Description=Start PPPoE Connection mypppoe
After=network.target

[Service]
Type=oneshot
ExecStartPre=dhclient enx00e04ca7c468
ExecStart=/usr/bin/nmcli connection up mypppoe
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

After restarting, it was found that nmcli did not have permission to dial, so the user was changed to run as root.

junyu33@zjy:~$ sudo cat /etc/systemd/system/pppoe-connection.service 
[sudo] password for junyu33: 
[Unit]
Description=Start PPPoE Connection mypppoe
After=network.target

[Service]
User=root
Type=oneshot
ExecStartPre=dhclient enx00e04ca7c468
ExecStart=/usr/bin/nmcli connection up mypppoe
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

After restarting again, the dial-up functioned normally.

Auto DDNS on Boot

Since the IP may change, ensure it updates once at boot and every half hour:

junyu33@zjy:~$ sudo cat /etc/systemd/system/ddns-update.timer 
[Unit]
Description=Run DDNS update script every 30 minutes
 
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true
 
[Install]
WantedBy=timers.target

Then write the specific service content, noting the following:

junyu33@zjy:~$ sudo cat /etc/systemd/system/ddns-update.service 
[Unit]
Description=Run DDNS update script

[Service]
Type=oneshot
WorkingDirectory=/home/junyu33/netlify-dynamic-dns-py
ExecStartPre=/usr/bin/env http_proxy= https_proxy=
ExecStart=/bin/bash /home/junyu33/netlify-dynamic-dns-py/ddns.sh

Running the Development Board Independently from the Computer

This was the only part of the process that felt like stumbling in the dark. Since I was short on power adapters, I ended up using one that hadn't been used in a long time. As it turned out, that particular adapter was probably faulty—even though the power indicator light was on, Debian simply wouldn't boot. As a result, I spent quite a while waiting on my host computer, unable to SSH in.

I then reconnected the development board to my computer and tried SSH again—this time it worked. That ruled out any issues with the development board's system.

Next, I switched to the power adapter I normally use for my headphones, and it worked. So it was clear the issue was with the old adapter I had used earlier.

After that, I plugged the power and Ethernet cables into my roommate's unused USB charging hub and broadband port (he's away for an internship in Beijing), and it worked again. It was almost lights-out time by then, so I decided to call it a night and see whether I could SSH into both my development board and my workstation computer the next day.

The next morning, as soon as I woke up, I tried connecting from my own computer—everything worked perfectly. And so:

Congratulations—I've finally achieved the dream of “working from home” once again!

Lessons Learned

  1. You get what you pay for—high quality rarely comes cheap.
  2. When shopping online, read the negative reviews. Don't just buy something because it's popular.
  3. Always keep a clear understanding of what you're doing, or it's easy to get lost in the woods.
  4. Break out of the “student mindset”—don't learn something just for the sake of learning it. That approach is highly inefficient.