buninu-linux 0.13.56 → 0.14.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +46 -46
- package/README.md +44 -14
- package/fetch-alpine.sh +1 -1
- package/initramfs/bin/dhclient +8 -0
- package/initramfs/bin/ping +8 -0
- package/initramfs/init.js +8 -7
- package/initramfs/lib/bunterm/renderer.js +1 -1
- package/initramfs/lib/bunterm/terminal.js +1 -1
- package/initramfs/lib/dhclient.js +135 -0
- package/initramfs/lib/dhcp-start.js +85 -0
- package/initramfs/lib/dhcp-transport.js +44 -0
- package/initramfs/lib/dhcp.js +124 -0
- package/initramfs/lib/dlopen.js +58 -11
- package/initramfs/lib/ping.js +110 -0
- package/initramfs/usr/share/doc/buninu-linux/bunterm.md +1 -1
- package/initramfs/usr/share/doc/buninu-linux/dhclient.md +82 -0
- package/initramfs/usr/share/doc/buninu-linux/ping.md +91 -0
- package/package.json +2 -2
- package/scripts/arch.js +5 -5
- package/scripts/arch.sh +5 -5
- package/scripts/modules.js +1 -0
- package/scripts/npm-packages.md +8 -8
- package/test/dhcp-start.test.js +67 -0
- package/test/dhcp.test.js +99 -0
- package/test/fetch-npm.test.js +1 -1
- package/test/libc.test.js +41 -0
- package/test/ping.test.js +80 -0
package/NOTICE.md
CHANGED
|
@@ -139,83 +139,83 @@ the built image redistributes them.
|
|
|
139
139
|
- License: GPL-2.0-only WITH Linux-syscall-note
|
|
140
140
|
- See: `LICENSES/GPL-2.0.txt` and `LICENSES/Linux-syscall-note.txt`
|
|
141
141
|
|
|
142
|
-
Linux 6.18.
|
|
143
|
-
The default build uses package `linux-virt`, version 6.18.
|
|
144
|
-
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.
|
|
142
|
+
Linux 6.18.57 as packaged by Alpine Linux 3.24 from the `linux-lts` aport.
|
|
143
|
+
The default build uses package `linux-virt`, version 6.18.57-r0.
|
|
144
|
+
`--linux-lts` and `--real` use package `linux-lts`, version 6.18.57-r0.
|
|
145
145
|
Each comes from the x86_64 or aarch64 repository to match `--arch`. All
|
|
146
146
|
listed modules are the selected package's `.ko.gz` files decompressed without
|
|
147
147
|
modification.
|
|
148
148
|
|
|
149
|
-
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.
|
|
149
|
+
- Upstream source: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.18.57.tar.xz
|
|
150
150
|
- Alpine build recipe, configuration and patches (Corresponding Source),
|
|
151
|
-
pinned to the commit shipping 6.18.
|
|
152
|
-
https://gitlab.alpinelinux.org/alpine/aports/-/blob/
|
|
151
|
+
pinned to the commit shipping 6.18.57-r0 (the same for both architectures):
|
|
152
|
+
https://gitlab.alpinelinux.org/alpine/aports/-/blob/0c40eb4fea009af7de457fa20e186f43a88e0954/main/linux-lts/APKBUILD
|
|
153
153
|
- Packages:
|
|
154
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.
|
|
155
|
-
SHA-256 `
|
|
156
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.
|
|
157
|
-
SHA-256 `
|
|
158
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.
|
|
159
|
-
SHA-256 `
|
|
160
|
-
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.
|
|
161
|
-
SHA-256 `
|
|
154
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-virt-6.18.57-r0.apk
|
|
155
|
+
SHA-256 `414aa2572ef88164b1af9905fa6dfbe863bf50f6130d895d57282dea3e15f7b9`
|
|
156
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/x86_64/linux-lts-6.18.57-r0.apk
|
|
157
|
+
SHA-256 `7614556471859956a436b95813daf409b444aa849ec899178b404e828c5a9d98`
|
|
158
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-virt-6.18.57-r0.apk
|
|
159
|
+
SHA-256 `b3820fee22e0366a15f7c139e5bc00348e333f3e7f77802271df7ded795aee4f`
|
|
160
|
+
https://dl-cdn.alpinelinux.org/alpine/v3.24/main/aarch64/linux-lts-6.18.57-r0.apk
|
|
161
|
+
SHA-256 `67b8f61c5cfb6ac9b5a65cb1610b67d467e6c4ffc17e7d29dbea4bd8c447ff32`
|
|
162
162
|
|
|
163
163
|
SHA-256 of files in the default x86_64 `linux-virt` image:
|
|
164
164
|
|
|
165
165
|
```text
|
|
166
|
-
kernel/x86_64/vmlinuz-virt
|
|
167
|
-
failover.ko
|
|
168
|
-
net_failover.ko
|
|
169
|
-
virtio_net.ko
|
|
166
|
+
kernel/x86_64/vmlinuz-virt 3725536e1c015345d781430b728849c2f090cf2040af00d7fe825920a106a08e
|
|
167
|
+
failover.ko f373917790e5e17b8c0c278efdd6fef7545ee8362510efaa80efb821e553f0b3
|
|
168
|
+
net_failover.ko 78fce63b3625b11bc65d4f0eb8f844796cad472ef14302016458ac60f898ac95
|
|
169
|
+
virtio_net.ko 18addca2d17e0b01b9adc64111b0409c08da6a196215e68bfda067309ff323a8
|
|
170
170
|
```
|
|
171
171
|
|
|
172
172
|
SHA-256 of files in an x86_64 `--linux-lts` image:
|
|
173
173
|
|
|
174
174
|
```text
|
|
175
|
-
kernel/x86_64/vmlinuz-lts
|
|
176
|
-
failover.ko
|
|
177
|
-
net_failover.ko
|
|
178
|
-
virtio_net.ko
|
|
179
|
-
virtio_ring.ko
|
|
180
|
-
virtio.ko
|
|
181
|
-
virtio_pci_legacy_dev.ko
|
|
182
|
-
virtio_pci_modern_dev.ko
|
|
183
|
-
virtio_pci.ko
|
|
175
|
+
kernel/x86_64/vmlinuz-lts 674c615f1f0887621e6609becd11a7db40cc71b7616644fcc260b4b226a52103
|
|
176
|
+
failover.ko 00d409746a8033e5acf0d4d0b8db4589d48168f2965e4de0c05374bc4e3a4025
|
|
177
|
+
net_failover.ko afbe1a6cd279381ade52943158c62931d79f38dcd293e31ef522878ff82c17ed
|
|
178
|
+
virtio_net.ko 84b658a64416094f3bc793ac43ccd987e087898d0c0510ddf5f9e53e0f9b36ff
|
|
179
|
+
virtio_ring.ko db497b3c23133f48b5a48800695b9f7068244dfa9d52cd1d10b5d4618e68fb60
|
|
180
|
+
virtio.ko c867c11e61db4c9c4a7af54f29ba9d127759706854ba84bf42ca6f2ae8b4f71a
|
|
181
|
+
virtio_pci_legacy_dev.ko 8825df8124c86800b3bf3d3403f6aba47d527d02565f91baddebe40ea0f66f88
|
|
182
|
+
virtio_pci_modern_dev.ko 3ba582ad90247b04fe2afe289d2b51a39e3efc1cc40e58f55e7701062141e061
|
|
183
|
+
virtio_pci.ko 5f6baef35e1536d735628e14fa3172c27228cd26f7c80b7465d2afbed8ee3876
|
|
184
184
|
```
|
|
185
185
|
|
|
186
186
|
`--real` additionally includes the following modules from that same
|
|
187
187
|
x86_64 `linux-lts` package for physical xHCI controllers and USB HID keyboards:
|
|
188
188
|
|
|
189
189
|
```text
|
|
190
|
-
usb-common.ko
|
|
191
|
-
usbcore.ko
|
|
192
|
-
xhci-hcd.ko
|
|
193
|
-
xhci-pci.ko
|
|
194
|
-
xhci-pci-renesas.ko
|
|
195
|
-
hid.ko
|
|
196
|
-
hid-generic.ko
|
|
197
|
-
usbhid.ko
|
|
190
|
+
usb-common.ko eb9d7b52b5c8f74b062ea3efbd8112c1460e3d34c0338e538602c648b958a8ef
|
|
191
|
+
usbcore.ko 30286b4e42c985e56903f41fd35233b3b35700b96ab99b088b1d91735c8e6e94
|
|
192
|
+
xhci-hcd.ko f0988c511d5692265f8c5221d77c468c9650151f3d69b5b68988eba9d1fab7f1
|
|
193
|
+
xhci-pci.ko a9145f467b6bf9b099e52eb5ac41b0c1164d9400e5b2f5f9d595ff58b4d776fb
|
|
194
|
+
xhci-pci-renesas.ko 2d88a4e72512944b62a4394172702db8dc211ccc7b99a93778f2d42ed8a23caf
|
|
195
|
+
hid.ko 7994f6b42f1df68a62312cf4de50a22b1a9756c3da91966a634630a62b18ae10
|
|
196
|
+
hid-generic.ko ea2eb7491e30bef769d2c419a2ccc9d55db030edddfe531c0d4e4723e170a4c3
|
|
197
|
+
usbhid.ko 899f357db5ba74182cf9161a577b080f8a6a9b0b015f6a322a5c31bf8754188b
|
|
198
198
|
```
|
|
199
199
|
|
|
200
200
|
SHA-256 of files in the default aarch64 `linux-virt` image:
|
|
201
201
|
|
|
202
202
|
```text
|
|
203
|
-
kernel/aarch64/vmlinuz-virt
|
|
204
|
-
failover.ko
|
|
205
|
-
net_failover.ko
|
|
206
|
-
virtio_net.ko
|
|
203
|
+
kernel/aarch64/vmlinuz-virt 60603568b5589c730e9183e5d6d4f0b8575ddac31e9187222decc92416329b26
|
|
204
|
+
failover.ko 792943de0975a4111d2040ab35cc214e5b1b4ec8c12dbc9725fd0602a56f1576
|
|
205
|
+
net_failover.ko ba3ab71ef38aa3a957c5b0fe7ddd908e31635e408f5937bccb50f36bb78761ba
|
|
206
|
+
virtio_net.ko 716e32b4425aa8dd4ebfeb6808e58c125e6b125c71b00ba8288851363f72fa60
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
SHA-256 of files in an aarch64 `--linux-lts` image:
|
|
210
210
|
|
|
211
211
|
```text
|
|
212
|
-
kernel/aarch64/vmlinuz-lts
|
|
213
|
-
failover.ko
|
|
214
|
-
net_failover.ko
|
|
215
|
-
virtio_net.ko
|
|
216
|
-
virtio_pci_legacy_dev.ko
|
|
217
|
-
virtio_pci_modern_dev.ko
|
|
218
|
-
virtio_pci.ko
|
|
212
|
+
kernel/aarch64/vmlinuz-lts 19fc9f974bb3cb666b8a216f18ca9f9df8e1658da4e9e55ed464c3fcaf87a303
|
|
213
|
+
failover.ko d243de2d9c2b2c34094c125719fa784b2e476986bfcd5766b96e7f7fbcb658ac
|
|
214
|
+
net_failover.ko 675cdf971cbb2ab15f8db6295afadcfc852172f38d289e9935ed28386e3e38f6
|
|
215
|
+
virtio_net.ko 34ea985ebf0bef82731f398aa3aca9030d6d7882d4bcb1542fd17d8637cad70e
|
|
216
|
+
virtio_pci_legacy_dev.ko 17f1487bf62ac3b2961f5f959fb28a2e1135db565e8a77296372c5101e00aae1
|
|
217
|
+
virtio_pci_modern_dev.ko e7a76945ae0b9c75af78a77950a4f22c95758fe507db2b77fbc168a4d6f842d8
|
|
218
|
+
virtio_pci.ko 76efc4e0428eb0413727247f41972d72c3f2ed1373f7e94f88052357a097b6a8
|
|
219
219
|
```
|
|
220
220
|
|
|
221
221
|
### linuxx64.efi.stub and linuxaa64.efi.stub
|
package/README.md
CHANGED
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
* Bun reaches its interactive REPL
|
|
36
36
|
* `start` starts the Buninu userspace shell
|
|
37
37
|
* `cfg.disk` + shell `mount` mounts local disks
|
|
38
|
-
* `cfg.net` loads common wired NIC and Android USB-tethering drivers
|
|
38
|
+
* `cfg.net` loads common wired NIC and Android USB-tethering drivers and automatically attempts DHCP configuration
|
|
39
39
|
* `cfg.sound` loads every packaged ALSA sound module
|
|
40
40
|
* Android phone USB tethering over RNDIS has been tested successfully, allowing Buninu Linux to access the Internet through an Android phone
|
|
41
|
-
*
|
|
41
|
+
* After a DHCP lease expires or when you need fresh network settings, run `dhclient usb0` (using your interface name). Manual configuration with `ip` remains available.
|
|
42
42
|
* The bundled `jmi` editor, jsmdcui App runtime, and local JavaScript execution also work.
|
|
43
43
|
* Multi-tasking with kernel VT or jsmdcui
|
|
44
44
|
+ See [Multitasking modes](#multitasking-modes)
|
|
@@ -227,8 +227,7 @@ It supports Chinese text, emoji and the browser's terminal graphics. Use
|
|
|
227
227
|
`Ctrl+Alt+F1` to return to the original console and `Ctrl+Alt+F2` to return
|
|
228
228
|
to this terminal.
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
described under [Basic configuration](#basic-configuration). Run `poweroff` for a synced
|
|
230
|
+
Run `poweroff` for a synced
|
|
232
231
|
shutdown. After editing the initramfs, `bun ./index.js -b --real --export` rebuilds the
|
|
233
232
|
image; `-h` lists all flags.
|
|
234
233
|
|
|
@@ -384,9 +383,16 @@ cfg.power
|
|
|
384
383
|
cfg.sound
|
|
385
384
|
```
|
|
386
385
|
|
|
387
|
-
`cfg.net` loads the packaged wired-network and Android USB-tethering drivers
|
|
388
|
-
interfaces and MAC addresses
|
|
389
|
-
|
|
386
|
+
`cfg.net` loads the packaged wired-network and Android USB-tethering drivers,
|
|
387
|
+
prints detected interfaces and MAC addresses, and automatically attempts DHCP
|
|
388
|
+
configuration. If it succeeds, no additional DHCP command is needed.
|
|
389
|
+
|
|
390
|
+
After a lease expires or when you need to obtain network settings again,
|
|
391
|
+
enter the Buninu shell with `start()` and run `dhclient usb0` (replace `usb0`
|
|
392
|
+
with your interface name). It configures the interface once and exits;
|
|
393
|
+
there is no automatic renewal.
|
|
394
|
+
|
|
395
|
+
For a manually configured static address, use:
|
|
390
396
|
|
|
391
397
|
```sh
|
|
392
398
|
ip link set eth0 up
|
|
@@ -695,6 +701,8 @@ If there is no sound, check step 4's terminal for errors, and make sure
|
|
|
695
701
|
| `mount` | `mount(2)` with type detection, `-o` parsing, `LABEL=`/`UUID=`, bind/move/remount; loads required filesystem and disk modules | `mount --help` → `/usr/share/doc/buninu-linux/mount.md` |
|
|
696
702
|
| `umount` | `umount2(2)` with `-l`, `-f`, `-R` | `umount --help` |
|
|
697
703
|
| `ip` | iproute2 grammar over `SIOC*` ioctls and `/proc/net`: `link`, `addr`, `route`, `neigh` | `ip --help` |
|
|
704
|
+
| `dhclient` | Reacquire DHCPv4 settings after expiry or when needed (`dhclient usb0`), then exit; also supports release | `dhclient --help` |
|
|
705
|
+
| `ping` | IPv4 ICMP / IPv6 ICMPv6 echo via Bun FFI; count, interval, timeout, deadline, payload size, TTL and source selection | `ping --help` |
|
|
698
706
|
| `ps` | composable process fields from `/proc`; also exports its snapshot and single-row formatter for other commands | `ps --help` |
|
|
699
707
|
| `top` | periodically refreshes the process snapshot and rows supplied by `ps` | `top --help` |
|
|
700
708
|
| `poweroff` | sync pending writes and power off through the Linux reboot system call | `poweroff --help` |
|
|
@@ -717,6 +725,14 @@ above, implemented as Bun scripts or thin launchers for bundled packages.
|
|
|
717
725
|
For Debian/Alpine rootfs and Chromium examples, see
|
|
718
726
|
[Optional chroot and native binaries](#optional-chroot-and-native-binaries).
|
|
719
727
|
|
|
728
|
+
`ping -c 4 127.0.0.1` checks IPv4 connectivity; `ping -6 -c 4 ::1` checks
|
|
729
|
+
IPv6. `ping -c 10 -i 0.2 -W 1 HOST` adjusts count, interval and reply timeout.
|
|
730
|
+
The command uses ICMP sockets directly, with a raw-socket fallback when needed.
|
|
731
|
+
It supports Linux glibc/musl and native Android bionic through the shared libc
|
|
732
|
+
loader; `BUNINU_LIBC` remains an explicit override. Run
|
|
733
|
+
`bun test test/ping.test.js test/libc.test.js` to compare against the installed
|
|
734
|
+
system ping. See `ping --help` for permission requirements and supported flags.
|
|
735
|
+
|
|
720
736
|
### Buninu Browser (early-stage, experimental)
|
|
721
737
|
|
|
722
738
|
`buninu-browser` is still in an early stage. Websites may render incorrectly
|
|
@@ -1179,7 +1195,7 @@ initramfs/ init.js, commands, userspace: the same for every archi
|
|
|
1179
1195
|
native/<arch>/ musl and the GCC runtime (committed); bun and modules (fetched)
|
|
1180
1196
|
```
|
|
1181
1197
|
|
|
1182
|
-
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.
|
|
1198
|
+
The `--real` inputs are Alpine v3.24 `linux-lts-6.18.57-r0`, musl `1.2.6-r2`,
|
|
1183
1199
|
`systemd-efistub-260.2-r0`, `libstdc++`/`libgcc` `15.2.0-r5`, and Bun 1.4.2
|
|
1184
1200
|
`linux-x64-musl-baseline`. The aarch64 guest uses the same versions from
|
|
1185
1201
|
Alpine's aarch64 repository and Bun's `linux-aarch64-musl`. There is no BusyBox
|
|
@@ -1194,7 +1210,7 @@ extracts from it. Bumping a version changes both the file name and the hash, so
|
|
|
1194
1210
|
it always refetches.
|
|
1195
1211
|
|
|
1196
1212
|
The five application packages are pinned separately in
|
|
1197
|
-
`scripts/npm-packages.md`: `buninu-browser@0.2.
|
|
1213
|
+
`scripts/npm-packages.md`: `buninu-browser@0.2.4`, `buninu-land@0.1.4`,
|
|
1198
1214
|
`@drxiaozhi/casty@1.3.7`, `@drxiaozhi/jspulse@0.2.1`, and
|
|
1199
1215
|
`@drxiaozhi/jspinyin@0.1.1`. These were npm's `latest` versions when checked
|
|
1200
1216
|
on 2026-10-11, with buninu-browser updated on 2026-10-12, and
|
|
@@ -1343,7 +1359,7 @@ The physical boot chain is firmware → the UKI stub → its embedded
|
|
|
1343
1359
|
and, with the currently pinned kernel, embeds this complete command line:
|
|
1344
1360
|
|
|
1345
1361
|
```text
|
|
1346
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1362
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
1347
1363
|
```
|
|
1348
1364
|
|
|
1349
1365
|
Serial kernel logging is retained, while the final console makes
|
|
@@ -1367,7 +1383,7 @@ the same way). Build without `--real`; this selects Alpine `linux-virt`, omits t
|
|
|
1367
1383
|
module set, and puts `ttyS0` last so serial stdio owns `/dev/console`:
|
|
1368
1384
|
|
|
1369
1385
|
```text
|
|
1370
|
-
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1386
|
+
console=tty0 console=ttyS0,115200 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-virt rdinit=/bin/bun -- -e import('/init.js')
|
|
1371
1387
|
```
|
|
1372
1388
|
|
|
1373
1389
|
Build and boot it with:
|
|
@@ -1484,7 +1500,7 @@ The default `--real` image embeds this kernel command line (shown for the
|
|
|
1484
1500
|
currently pinned LTS release):
|
|
1485
1501
|
|
|
1486
1502
|
```text
|
|
1487
|
-
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.
|
|
1503
|
+
console=ttyS0,115200 console=tty0 REAL_MACHINE=1 panic=0 PATH=/bin KERNEL_RELEASE=6.18.57-0-lts rdinit=/bin/bun -- -e import('/init.js')
|
|
1488
1504
|
```
|
|
1489
1505
|
|
|
1490
1506
|
The physical display (`tty0`) is the final console and therefore owns PID 1's
|
|
@@ -1563,8 +1579,22 @@ custom `libinit.so` is needed.
|
|
|
1563
1579
|
On a `--real` boot, init starts `/etc/resolv.conf` with `1.1.1.1` and
|
|
1564
1580
|
`8.8.8.8`; these are initial public resolvers until the user replaces them
|
|
1565
1581
|
with DNS appropriate for the local network. Hardware networking remains down
|
|
1566
|
-
until `cfg.net` loads the packaged drivers and
|
|
1567
|
-
route.
|
|
1582
|
+
until `cfg.net` loads the packaged drivers and attempts DHCP configuration.
|
|
1583
|
+
Manual address and route configuration with `ip` remains available.
|
|
1584
|
+
|
|
1585
|
+
`cfg.net` starts DHCP in the background for every detected non-loopback interface.
|
|
1586
|
+
It returns immediately; failures and acquisition timeouts are logged without
|
|
1587
|
+
interrupting init. Each client exits after configuring its lease; no renewal process stays running.
|
|
1588
|
+
Repeated `cfg.net` calls do not launch duplicate active clients. Route metrics
|
|
1589
|
+
start at 100; only the first interface updates DNS, leaving the init fallback
|
|
1590
|
+
resolver available if it cannot acquire a lease.
|
|
1591
|
+
|
|
1592
|
+
After a lease expires or when network settings need to be obtained again,
|
|
1593
|
+
run `dhclient usb0` (or `dhclient eth0`, depending on the interface name).
|
|
1594
|
+
This is not an extra step after successful automatic configuration by `cfg.net`.
|
|
1595
|
+
`dhclient` exits after configuration; `-timeout 20` limits acquisition to 20
|
|
1596
|
+
seconds. Leases do not renew automatically. For manual static configuration,
|
|
1597
|
+
see [Basic configuration](#basic-configuration).
|
|
1568
1598
|
|
|
1569
1599
|
On a `--real` image the REPL also offers the getter-based `cfg` namespace:
|
|
1570
1600
|
`cfg.net`, `cfg.disk`, `cfg.power`, `cfg.sound` and `cfg.all` run as soon as the property is read, without
|
package/fetch-alpine.sh
CHANGED
|
@@ -63,7 +63,7 @@ select_optional_module() {
|
|
|
63
63
|
# Network: virtio-net for QEMU (init.js loads these at boot). virtio_pci is
|
|
64
64
|
# the PCI transport for every virtio device, disk included; linux-virt builds
|
|
65
65
|
# it in, linux-lts has it as a module that nothing in modules.dep pulls in.
|
|
66
|
-
for module in failover net_failover virtio_net; do select_module "$module"; done
|
|
66
|
+
for module in failover net_failover virtio_net af_packet; do select_module "$module"; done
|
|
67
67
|
select_optional_module virtio_pci
|
|
68
68
|
# Storage: what /bin/mount loads on demand. ext4 brings jbd2, mbcache and
|
|
69
69
|
# crc16; FAT, exFAT and NTFS ask the kernel for the NLS tables at mount
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
import {parseDhclient,runDhclient} from '../lib/dhclient.js';
|
|
3
|
+
import {showDocument} from '../lib/document.js';
|
|
4
|
+
try {
|
|
5
|
+
const options=parseDhclient(Bun.argv.slice(2));
|
|
6
|
+
if(options.help)await showDocument('dhclient');
|
|
7
|
+
else process.exitCode=await runDhclient(options);
|
|
8
|
+
}catch(error){console.error(`dhclient: ${error.message}`);process.exitCode=1;}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/bun
|
|
2
|
+
import {parsePing,runPing} from '../lib/ping.js';
|
|
3
|
+
import {showDocument} from '../lib/document.js';
|
|
4
|
+
try {
|
|
5
|
+
const options=parsePing(Bun.argv.slice(2));
|
|
6
|
+
if(options.help)await showDocument('ping');
|
|
7
|
+
else process.exitCode=await runPing(options);
|
|
8
|
+
} catch(error) {console.error(`ping: ${error.message}`);process.exitCode=2;}
|
package/initramfs/init.js
CHANGED
|
@@ -495,7 +495,7 @@ globalThis.start = launchBuninu;
|
|
|
495
495
|
// cfg.net loads every packaged network module, including PHY and bus support
|
|
496
496
|
// that a kernel userspace modprobe helper would normally load on demand. It
|
|
497
497
|
// then reports the device matches and lists the resulting interfaces. The
|
|
498
|
-
//
|
|
498
|
+
// DHCP runs independently afterward; errors and timeouts never block init.
|
|
499
499
|
const configureEthernet = () => {
|
|
500
500
|
const { autoload, modprobeAll } = require("/lib/modprobe.js");
|
|
501
501
|
const { readdirSync, readFileSync } = require("node:fs");
|
|
@@ -529,12 +529,13 @@ const configureEthernet = () => {
|
|
|
529
529
|
const carrier = read(`/sys/class/net/${name}/carrier`);
|
|
530
530
|
console.log(`eth: ${name} ${read(`/sys/class/net/${name}/address`)} ${read(`/sys/class/net/${name}/operstate`)}${carrier === "1" ? " carrier" : ""}`);
|
|
531
531
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
532
|
+
try {
|
|
533
|
+
const { startDhcp } = require("/lib/dhcp-start.js");
|
|
534
|
+
startDhcp(interfaces);
|
|
535
|
+
} catch (error) {
|
|
536
|
+
console.log(`[dhcp setup] unavailable: ${error?.message ?? error}; init continues`);
|
|
537
|
+
}
|
|
538
|
+
console.log("[dhcp] If automatic DHCP fails, run start() to enter the shell, then try dhclient usb0 (replace usb0 with your interface) or configure the network manually with ip commands. Use ip link to list network interfaces.");
|
|
538
539
|
return interfaces;
|
|
539
540
|
};
|
|
540
541
|
|
|
@@ -58,7 +58,7 @@ const isSingleCodePoint = (text) => {
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export class Renderer {
|
|
61
|
-
constructor({ CanvasKit, screen, fonts, fontSize =
|
|
61
|
+
constructor({ CanvasKit, screen, fonts, fontSize = 13, lineHeight = 1, theme = defaultTheme }) {
|
|
62
62
|
this.CanvasKit = CanvasKit;
|
|
63
63
|
this.screen = screen;
|
|
64
64
|
this.canvas = screen.canvas;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {readFileSync,writeFileSync,mkdirSync,renameSync,unlinkSync,openSync,closeSync} from 'node:fs';
|
|
2
|
+
import {dirname,resolve} from 'node:path';
|
|
3
|
+
import {DhcpClient} from './dhcp.js';
|
|
4
|
+
import {interfaceInfo,openTransport} from './dhcp-transport.js';
|
|
5
|
+
export function parseDhclient(args) {
|
|
6
|
+
const o={timeout:60,dns:true,resolv:'/etc/resolv.conf',once:true,release:false,verbose:false};
|
|
7
|
+
for(let i=0;i<args.length;i++){
|
|
8
|
+
const a=args[i];
|
|
9
|
+
if(['--help','-h','-help'].includes(a))return {...o,help:true};
|
|
10
|
+
if(a==='-4'||a==='-d')continue;
|
|
11
|
+
if(a==='-1')o.once=true;
|
|
12
|
+
else if(a==='-r')o.release=true;
|
|
13
|
+
else if(a==='-v')o.verbose=true;
|
|
14
|
+
else if(a==='-q')o.quiet=true;
|
|
15
|
+
else if(a==='--no-dns')o.dns=false;
|
|
16
|
+
else if(['-timeout','-lf','-H','--resolv-conf','--metric'].includes(a)){
|
|
17
|
+
const value=args[++i];if(!value||value.startsWith('-'))throw new Error(`${a} requires a value`);
|
|
18
|
+
if(a==='-timeout'){o.timeout=Number(value);if(!Number.isFinite(o.timeout)||o.timeout<=0)throw new Error('timeout must be positive');}
|
|
19
|
+
if(a==='--metric'){o.metric=Number(value);if(!Number.isInteger(o.metric)||o.metric<0||o.metric>32766)throw new Error('metric must be between 0 and 32766');}
|
|
20
|
+
if(a==='-lf')o.leaseFile=resolve(value);
|
|
21
|
+
if(a==='-H'){if(!/^[a-zA-Z0-9][a-zA-Z0-9.-]{0,252}$/.test(value))throw new Error('invalid hostname');o.hostname=value;}
|
|
22
|
+
if(a==='--resolv-conf')o.resolv=resolve(value);
|
|
23
|
+
}else if(a.startsWith('-'))throw new Error(`unsupported option: ${a} (DHCPv4 only; see --help)`);
|
|
24
|
+
else if(o.interface)throw new Error('specify one interface per process');
|
|
25
|
+
else o.interface=a;
|
|
26
|
+
}
|
|
27
|
+
if(!o.interface||!/^[a-zA-Z0-9_.:-]{1,15}$/.test(o.interface))throw new Error('specify an Ethernet interface, e.g. dhclient usb0. Use ip link to list network interfaces; use dhclient -h or dhclient --help for help.');
|
|
28
|
+
o.leaseFile??=`/var/lib/dhclient/${o.interface}.json`;
|
|
29
|
+
if(args.includes('-1')&&o.release)throw new Error('-1 and -r cannot be combined');
|
|
30
|
+
return o;
|
|
31
|
+
}
|
|
32
|
+
export async function runIp(args) {
|
|
33
|
+
const p=Bun.spawn([process.execPath,`${import.meta.dir}/../bin/ip`,...args],{stdout:'pipe',stderr:'pipe'});
|
|
34
|
+
const [status,out,err]=await Promise.all([p.exited,new Response(p.stdout).text(),new Response(p.stderr).text()]);
|
|
35
|
+
if(status)throw new Error(`ip ${args.join(' ')}: ${err.trim()||out.trim()}`);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
const readOptional=path=>{try{return readFileSync(path,'utf8');}catch(e){if(e.code==='ENOENT')return null;throw e;}};
|
|
39
|
+
export function saveLease(path,lease) {
|
|
40
|
+
mkdirSync(dirname(path),{recursive:true});const tmp=`${path}.${process.pid}.tmp`;
|
|
41
|
+
writeFileSync(tmp,JSON.stringify(lease,null,2)+'\n',{mode:0o600});renameSync(tmp,path);
|
|
42
|
+
}
|
|
43
|
+
export class Configuration {
|
|
44
|
+
constructor(options,ip=runIp){this.options=options;this.ip=ip;}
|
|
45
|
+
metric(lease){return lease.metric===undefined?[]:['metric',String(lease.metric)];}
|
|
46
|
+
async apply(lease,previous) {
|
|
47
|
+
const o=this.options;
|
|
48
|
+
lease.metric=o.metric;
|
|
49
|
+
if(previous&&previous.routers[0]!==lease.routers[0])await this.removeRoute(previous);
|
|
50
|
+
await this.ip(['addr','replace',`${lease.address}/${lease.prefix}`,'brd','+','dev',o.interface]);
|
|
51
|
+
if(lease.routers[0])await this.ip(['route','replace','default','via',lease.routers[0],'dev',o.interface,...this.metric(lease)]);
|
|
52
|
+
if(previous?.resolver)lease.resolver=previous.resolver;
|
|
53
|
+
if(o.dns&&lease.dns.length){
|
|
54
|
+
const content=`# dhclient ${o.interface}\n`+lease.dns.map(ip=>`nameserver ${ip}\n`).join('');
|
|
55
|
+
const old=lease.resolver;
|
|
56
|
+
// Keep the original resolver across repeated invocations, unless another writer took over.
|
|
57
|
+
const current=readOptional(o.resolv);
|
|
58
|
+
lease.resolver={path:o.resolv,before:old?.path===o.resolv&¤t===old.written?old.before:current,written:content};
|
|
59
|
+
writeFileSync(o.resolv,content);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async removeRoute(lease) {
|
|
63
|
+
if(!lease.routers[0])return;
|
|
64
|
+
try{await this.ip(['route','del','default','via',lease.routers[0],'dev',this.options.interface,...this.metric(lease)]);}
|
|
65
|
+
catch(e){if(!/No such process|No such device|Cannot find device/.test(e.message))throw e;}
|
|
66
|
+
}
|
|
67
|
+
async clear(lease) {
|
|
68
|
+
const errors=[];
|
|
69
|
+
try{await this.removeRoute(lease);}catch(e){errors.push(e);}
|
|
70
|
+
// ip addr del checks the current address before deleting it.
|
|
71
|
+
try{await this.ip(['addr','del',`${lease.address}/${lease.prefix}`,'dev',this.options.interface]);}
|
|
72
|
+
catch(e){if(!/Cannot assign requested address|Cannot find device/.test(e.message))errors.push(e);}
|
|
73
|
+
try{
|
|
74
|
+
const r=lease.resolver;
|
|
75
|
+
if(r&&readOptional(r.path)===r.written){if(r.before===null)unlinkSync(r.path);else writeFileSync(r.path,r.before);}
|
|
76
|
+
}catch(e){errors.push(e);}
|
|
77
|
+
if(errors.length)throw new AggregateError(errors,errors.map(e=>e.message).join('; '));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// A command invocation acquires once and exits. There is no renewal daemon.
|
|
81
|
+
export async function manageLease(client,config,{previous,signal,log=console.log,save=()=>{},forget=()=>{}}={}) {
|
|
82
|
+
let lease,kept=false;
|
|
83
|
+
try {
|
|
84
|
+
lease=await client.acquire();
|
|
85
|
+
if(!lease){if(signal?.aborted)return 0;throw new Error('no DHCP lease received before timeout');}
|
|
86
|
+
if(signal?.aborted)return 0;
|
|
87
|
+
await config.apply(lease,previous);await save(lease);
|
|
88
|
+
log(`bound to ${lease.address}/${lease.prefix}, lease ${lease.seconds}s; configured and exiting (no automatic renewal)`);
|
|
89
|
+
kept=true;return 0;
|
|
90
|
+
}finally{
|
|
91
|
+
if(lease&&!kept){
|
|
92
|
+
try{client.release(lease);}catch(e){log(`release: ${e.message}`);}
|
|
93
|
+
await config.clear(lease);await forget();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export async function runDhclient(o) {
|
|
98
|
+
if(!['linux','android'].includes(process.platform))throw new Error('dhclient requires Linux/Android networking');
|
|
99
|
+
// Serialize this interface even when callers choose different lease files.
|
|
100
|
+
const lock=`/run/dhclient-${o.interface}.lock`;mkdirSync(dirname(lock),{recursive:true});
|
|
101
|
+
let handle;
|
|
102
|
+
try{handle=openSync(lock,'wx',0o600);}catch(e){if(e.code==='EEXIST')throw new Error(`${lock} exists; stop the other client, or remove this lock if its process died`);throw e;}
|
|
103
|
+
writeFileSync(handle,`${process.pid}\n`);
|
|
104
|
+
let transport;
|
|
105
|
+
const controller=new AbortController(),stop=()=>controller.abort();
|
|
106
|
+
process.on('SIGINT',stop);process.on('SIGTERM',stop);
|
|
107
|
+
const log=message=>{if(!o.quiet)console.log(`dhclient: ${message}`);};
|
|
108
|
+
const forget=()=>{try{unlinkSync(o.leaseFile);}catch(e){if(e.code!=='ENOENT')throw e;}};
|
|
109
|
+
try{
|
|
110
|
+
const info=interfaceInfo(o.interface);
|
|
111
|
+
await runIp(['link','set','dev',o.interface,'up']);
|
|
112
|
+
try { transport=openTransport(o.interface,info); }
|
|
113
|
+
catch(error) {
|
|
114
|
+
if(error.errno!==97)throw error; // EAFNOSUPPORT: Alpine ships AF_PACKET as a module.
|
|
115
|
+
const {modprobe}=await import('./modprobe.js');
|
|
116
|
+
modprobe('af_packet');transport=openTransport(o.interface,info);
|
|
117
|
+
}
|
|
118
|
+
const client=new DhcpClient({transport,...info,...o,signal:controller.signal,log:o.verbose?log:()=>{}});
|
|
119
|
+
const config=new Configuration(o);
|
|
120
|
+
if(o.release){
|
|
121
|
+
const text=readOptional(o.leaseFile);if(!text)throw new Error(`no saved lease: ${o.leaseFile}`);
|
|
122
|
+
const lease=JSON.parse(text);if(lease.interface!==o.interface||lease.mac!==info.mac.toString('hex'))throw new Error('saved lease belongs to another interface');
|
|
123
|
+
try{client.release(lease);}finally{await config.clear(lease);forget();}log(`released ${lease.address}`);return 0;
|
|
124
|
+
}
|
|
125
|
+
let previous;
|
|
126
|
+
const saved=readOptional(o.leaseFile);
|
|
127
|
+
if(saved){
|
|
128
|
+
const candidate=JSON.parse(saved);
|
|
129
|
+
if(candidate.interface===o.interface&&candidate.mac===info.mac.toString('hex'))previous=candidate;
|
|
130
|
+
}
|
|
131
|
+
return await manageLease(client,config,{...o,previous,signal:controller.signal,log,save:lease=>saveLease(o.leaseFile,{...lease,interface:o.interface,mac:info.mac.toString('hex')}),forget});
|
|
132
|
+
}finally{
|
|
133
|
+
transport?.close();process.off('SIGINT',stop);process.off('SIGTERM',stop);closeSync(handle);unlinkSync(lock);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// Best-effort one-shot DHCP for cfg.net. Each interface owns its result and
|
|
2
|
+
// bounded output buffer; a failed link never changes another link's status.
|
|
3
|
+
export function createDhcpLauncher({spawn=(argv,options)=>Bun.spawn(argv,options),log=console.log,
|
|
4
|
+
acquisitionSeconds=20,watchdogMs=25000,killGraceMs=2000,outputGraceMs=250}={}) {
|
|
5
|
+
const active=new Map();
|
|
6
|
+
const messageOf=error=>String(error?.message??error);
|
|
7
|
+
const clean=text=>String(text).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,'').replace(/[\x00-\x1f\x7f]/g,' ').trim();
|
|
8
|
+
const report=(name,message)=>{try{log(`[dhcp ${clean(name)}] ${clean(message)}`);}catch{}};
|
|
9
|
+
return function startDhcp(interfaces) {
|
|
10
|
+
const started=[];
|
|
11
|
+
try {
|
|
12
|
+
for(const [index,name] of interfaces.entries()) {
|
|
13
|
+
if(name==='lo'||active.has(name))continue;
|
|
14
|
+
let child,state;
|
|
15
|
+
try {
|
|
16
|
+
if(!/^[a-zA-Z0-9_.:-]{1,15}$/.test(name))throw new Error('invalid interface name');
|
|
17
|
+
const args=[process.execPath,`${import.meta.dir}/../bin/dhclient`,'-1','-timeout',String(acquisitionSeconds),'--metric',String(100+index)];
|
|
18
|
+
// Keep a single resolver owner; init already supplies fallback DNS.
|
|
19
|
+
if(index>0)args.push('--no-dns');
|
|
20
|
+
args.push(name);
|
|
21
|
+
child=spawn(args,{stdin:'ignore',stdout:'pipe',stderr:'pipe'});
|
|
22
|
+
state={child,done:false,timer:null,killTimer:null,timedOut:false,address:null,errors:[],notes:[],readers:[]};active.set(name,state);
|
|
23
|
+
const remember=(list,text)=>{list.push(clean(text).slice(0,512));if(list.length>4)list.shift();};
|
|
24
|
+
const kill=signal=>{try{child.kill(signal);}catch(e){remember(state.errors,`${signal}: ${messageOf(e)}`);}};
|
|
25
|
+
state.timer=setTimeout(()=>{
|
|
26
|
+
if(state.done)return;
|
|
27
|
+
state.timedOut=true;kill('SIGTERM');
|
|
28
|
+
state.killTimer=setTimeout(()=>{if(!state.done)kill('SIGKILL');},killGraceMs);
|
|
29
|
+
state.killTimer.unref?.();
|
|
30
|
+
},watchdogMs);state.timer.unref?.();
|
|
31
|
+
const line=(text,isError)=>{
|
|
32
|
+
text=clean(text);if(!text)return;
|
|
33
|
+
const bound=!isError&&/^dhclient: bound to ([^,\s]+)/.exec(text);
|
|
34
|
+
if(bound)state.address=bound[1];
|
|
35
|
+
else remember(isError?state.errors:state.notes,text.replace(/^dhclient:\s*/,''));
|
|
36
|
+
};
|
|
37
|
+
const drain=async(stream,isError)=>{
|
|
38
|
+
if(!stream)return;
|
|
39
|
+
const reader=stream.getReader();state.readers.push(reader);
|
|
40
|
+
const decoder=new TextDecoder();let pending='';
|
|
41
|
+
try {
|
|
42
|
+
for(;;){
|
|
43
|
+
const {done,value}=await reader.read();if(done)break;
|
|
44
|
+
pending+=decoder.decode(value,{stream:true});
|
|
45
|
+
let at;while((at=pending.indexOf('\n'))>=0){line(pending.slice(0,at),isError);pending=pending.slice(at+1);}
|
|
46
|
+
if(pending.length>4096){line(pending.slice(0,4096),isError);pending='';}
|
|
47
|
+
}
|
|
48
|
+
if(pending)line(pending,isError);
|
|
49
|
+
}finally{reader.releaseLock();}
|
|
50
|
+
};
|
|
51
|
+
const drains=[drain(child.stdout,false),drain(child.stderr,true)].map(p=>p.catch(e=>remember(state.errors,`output: ${messageOf(e)}`)));
|
|
52
|
+
const finish=async(code,error)=>{
|
|
53
|
+
state.done=true;clearTimeout(state.timer);clearTimeout(state.killTimer);
|
|
54
|
+
// exited can resolve before the last stderr/stdout chunk arrives.
|
|
55
|
+
// Bound the wait even if an inherited pipe never closes.
|
|
56
|
+
let outputTimer;
|
|
57
|
+
try {
|
|
58
|
+
await Promise.race([Promise.all(drains),new Promise(resolve=>{outputTimer=setTimeout(resolve,outputGraceMs);})]);
|
|
59
|
+
}finally{
|
|
60
|
+
clearTimeout(outputTimer);
|
|
61
|
+
for(const reader of state.readers){try{void reader.cancel().catch(()=>{});}catch{}}
|
|
62
|
+
if(active.get(name)===state)active.delete(name);
|
|
63
|
+
}
|
|
64
|
+
if(state.timedOut)report(name,`failed: timed out; other interfaces are unaffected`);
|
|
65
|
+
else if(error!==undefined)report(name,`failed: ${messageOf(error)}`);
|
|
66
|
+
else if(code===0&&state.address)report(name,`configured ${state.address}; command finished`);
|
|
67
|
+
else if(code===0)report(name,'finished without configuring an address');
|
|
68
|
+
else {
|
|
69
|
+
const detail=state.errors.at(-1)??state.notes.at(-1)??'no error details';
|
|
70
|
+
report(name,`failed: ${detail} (exit ${code}); other interfaces are unaffected`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
void Promise.resolve(child.exited).then(code=>finish(code),error=>finish(null,error??'unknown exit error')).catch(e=>report(name,`result unavailable: ${messageOf(e)}`));
|
|
74
|
+
started.push(name);report(name,'requesting configuration');
|
|
75
|
+
}catch(error){
|
|
76
|
+
if(state){clearTimeout(state.timer);clearTimeout(state.killTimer);active.delete(name);}
|
|
77
|
+
if(child){try{child.kill('SIGKILL');}catch{}}
|
|
78
|
+
report(name,`failed to start: ${messageOf(error)}; other interfaces are unaffected`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}catch(error){report('setup',`failed: ${messageOf(error)}; init continues`);}
|
|
82
|
+
return started;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export const startDhcp=createDhcpLauncher();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {libc,ptr,check,errno,SysError,cString,withInetSocket} from './dlopen.js';
|
|
2
|
+
import {ipv4,wrapIPv4,unwrapIPv4} from './dhcp.js';
|
|
3
|
+
export function interfaceInfo(name) {
|
|
4
|
+
if(!/^[a-zA-Z0-9_.:-]{1,15}$/.test(name))throw new Error('invalid interface name');
|
|
5
|
+
return withInetSocket(ioctl=>{
|
|
6
|
+
const req=Buffer.alloc(40);req.write(name);ioctl(0x8933,req,'SIOCGIFINDEX');const index=req.readInt32LE(16);
|
|
7
|
+
ioctl(0x8927,req,'SIOCGIFHWADDR');if(req.readUInt16LE(16)!==1)throw new Error('DHCP requires an Ethernet-compatible interface');
|
|
8
|
+
return {index,mac:Buffer.from(req.subarray(18,24))};
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
const sockaddr=(ip,port)=>{const b=Buffer.alloc(16);b.writeUInt16LE(2);b.writeUInt16BE(port,2);ipv4(ip).copy(b,4);return b;};
|
|
12
|
+
export function openTransport(name,{index}) {
|
|
13
|
+
const f=libc.symbols, descriptors=[];
|
|
14
|
+
try {
|
|
15
|
+
// Packet sockets work before the interface has any IPv4 address or route.
|
|
16
|
+
const raw=check('socket(AF_PACKET): requires CAP_NET_RAW',f.socket(17,2|0x800|0x80000,0x0008));descriptors.push(raw);
|
|
17
|
+
const link=Buffer.alloc(20);link.writeUInt16LE(17);link.writeUInt16BE(0x0800,2);link.writeInt32LE(index,4);link[11]=6;link.fill(255,12,18);
|
|
18
|
+
check('bind(AF_PACKET)',f.bind(raw,ptr(link),link.length));
|
|
19
|
+
// Reserve the client UDP port and use kernel routing/ARP for unicast renew
|
|
20
|
+
// and release. The packet socket receives both broadcast and unicast replies.
|
|
21
|
+
const udp=check('socket(UDP)',f.socket(2,2|0x800|0x80000,17));descriptors.push(udp);
|
|
22
|
+
const device=cString(name);check('SO_BINDTODEVICE',f.setsockopt(udp,1,25,ptr(device),device.length));
|
|
23
|
+
const local=sockaddr('0.0.0.0',68);check('bind UDP port 68 (another DHCP client may be running)',f.bind(udp,ptr(local),16));
|
|
24
|
+
const buffer=Buffer.alloc(65535);
|
|
25
|
+
return {
|
|
26
|
+
send(payload,server){
|
|
27
|
+
const destination=server?sockaddr(server,67):link;
|
|
28
|
+
const bytes=server?payload:wrapIPv4(payload,payload.subarray(12,16).join('.'));
|
|
29
|
+
check('sendto(DHCP)',Number(f.sendto(server?udp:raw,ptr(bytes),bytes.length,0,ptr(destination),destination.length)));
|
|
30
|
+
},
|
|
31
|
+
receive(){
|
|
32
|
+
// Drain the UDP copy so its receive buffer does not remain full.
|
|
33
|
+
for(let i=0;i<64;i++)if(f.recv(udp,ptr(buffer),buffer.length,0)<0)break;
|
|
34
|
+
for(let i=0;i<64;i++){
|
|
35
|
+
const n=f.recv(raw,ptr(buffer),buffer.length,0);
|
|
36
|
+
if(n<0){const code=errno();if(code===11||code===4)return null;throw new SysError('recv(DHCP)',code);}
|
|
37
|
+
const payload=unwrapIPv4(buffer.subarray(0,Number(n)));if(payload)return Buffer.from(payload);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
},
|
|
41
|
+
close(){for(const fd of descriptors.splice(0))f.close(fd);},
|
|
42
|
+
};
|
|
43
|
+
}catch(error){for(const fd of descriptors)f.close(fd);throw error;}
|
|
44
|
+
}
|