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.
@@ -0,0 +1,124 @@
1
+ // DHCPv4 codec and lease state machine. Network/configuration adapters are
2
+ // separate so protocol tests never change the host's addresses or resolver.
3
+ import { randomBytes } from 'node:crypto';
4
+ export const TYPE = { DISCOVER:1, OFFER:2, REQUEST:3, DECLINE:4, ACK:5, NAK:6, RELEASE:7 };
5
+ export const ipv4 = text => {
6
+ const parts=String(text).split('.');
7
+ if(parts.length!==4 || parts.some(p=>!/^\d{1,3}$/.test(p)||Number(p)>255))throw new Error(`invalid IPv4 address: ${text}`);
8
+ return Buffer.from(parts.map(Number));
9
+ };
10
+ const address=b=>[...b].join('.');
11
+ export function checksum(b) {let n=0;for(let i=0;i<b.length;i+=2)n+=(b[i]<<8)|(b[i+1]??0);while(n>>>16)n=(n&65535)+(n>>>16);return (~n)&65535;}
12
+ export function encode({type,xid,mac,requested,server,ciaddr='0.0.0.0',hostname,secs=0,broadcast=true}) {
13
+ const b=Buffer.alloc(240);b[0]=1;b[1]=1;b[2]=6;b.writeUInt32BE(xid,4);b.writeUInt16BE(Math.min(65535,secs),8);
14
+ b.writeUInt16BE(broadcast?0x8000:0,10);ipv4(ciaddr).copy(b,12);Buffer.from(mac).copy(b,28,0,6);b.writeUInt32BE(0x63825363,236);
15
+ const options=[];
16
+ const option=(code,value)=>{value=Buffer.from(value);if(value.length>255)throw new Error('DHCP option too long');options.push(Buffer.from([code,value.length]),value);};
17
+ option(53,[type]);option(61,[1,...mac]);
18
+ if(requested)option(50,ipv4(requested));if(server)option(54,ipv4(server));
19
+ if(hostname)option(12,Buffer.from(hostname));
20
+ if(type!==TYPE.RELEASE){option(55,[1,3,6,15,28,51,58,59]);option(57,[5,220]);}
21
+ const packet=Buffer.concat([b,...options,Buffer.from([255])]);
22
+ return packet.length<300?Buffer.concat([packet,Buffer.alloc(300-packet.length)]):packet;
23
+ }
24
+ export function decode(b,{xid,mac}={}) {
25
+ if(b.length<240||b[0]!==2||b[1]!==1||b[2]!==6||b.readUInt32BE(236)!==0x63825363)return null;
26
+ if(xid!==undefined&&b.readUInt32BE(4)!==xid)return null;
27
+ if(mac&&!b.subarray(28,34).equals(Buffer.from(mac)))return null;
28
+ const options=new Map();
29
+ function parse(start,end){for(let i=start;i<end;){const code=b[i++];if(code===255)return true;if(!code)continue;if(i>=end)return false;const len=b[i++];if(i+len>end)return false;options.set(code,Buffer.concat([options.get(code)??Buffer.alloc(0),b.subarray(i,i+len)]));i+=len;}return true;}
30
+ if(!parse(240,b.length))return null;
31
+ const overload=options.get(52);if(overload&&(overload.length!==1||overload[0]>3))return null;
32
+ if(overload?.[0]&1)if(!parse(108,236))return null;
33
+ if(overload?.[0]&2)if(!parse(44,108))return null;
34
+ if(options.get(53)?.length!==1)return null;
35
+ for(const code of [1,28,51,54,58,59])if(options.has(code)&&options.get(code).length!==4)return null;
36
+ for(const code of [3,6])if(options.has(code)&&(!options.get(code).length||options.get(code).length%4))return null;
37
+ const ip=code=>options.has(code)?address(options.get(code)):undefined;
38
+ const ips=code=>{const v=options.get(code),out=[];if(v)for(let i=0;i<v.length;i+=4)out.push(address(v.subarray(i,i+4)));return out;};
39
+ const number=code=>options.get(code)?.readUInt32BE(0);
40
+ return {type:options.get(53)[0],xid:b.readUInt32BE(4),address:address(b.subarray(16,20)),server:ip(54),mask:ip(1),routers:options.has(3)?ips(3):undefined,dns:options.has(6)?ips(6):undefined,lease:number(51),t1:number(58),t2:number(59)};
41
+ }
42
+ export function prefixOf(mask) {
43
+ const n=ipv4(mask).readUInt32BE(),inverse=(~n)>>>0;
44
+ if((inverse&(inverse+1))!==0)throw new Error('DHCP supplied a non-contiguous subnet mask');
45
+ return 32-Math.log2(inverse+1);
46
+ }
47
+ export function leaseFrom(reply,previous,now) {
48
+ const ip=reply.address==='0.0.0.0'?previous?.address:reply.address;
49
+ const server=reply.server,mask=reply.mask??previous?.mask;
50
+ if(!ip||ip==='0.0.0.0'||!server||!mask||!reply.lease)throw new Error('DHCP ACK is missing address, server, mask or lease duration');
51
+ const prefix=prefixOf(mask),seconds=reply.lease;
52
+ let t1=reply.t1??seconds*.5,t2=reply.t2??seconds*.875;
53
+ if(!(t1>0&&t1<t2&&t2<seconds)){t1=seconds*.5;t2=seconds*.875;}
54
+ return {address:ip,server,mask,prefix,routers:reply.routers??previous?.routers??[],dns:reply.dns??previous?.dns??[],seconds,
55
+ acquiredAt:now,renewAt:now+t1*1000,rebindAt:now+t2*1000,expiresAt:now+seconds*1000};
56
+ }
57
+ // SOCK_DGRAM AF_PACKET supplies/consumes the Ethernet header itself.
58
+ export function wrapIPv4(payload,source='0.0.0.0',destination='255.255.255.255') {
59
+ const b=Buffer.alloc(28+payload.length);b[0]=0x45;b.writeUInt16BE(b.length,2);b[8]=64;b[9]=17;
60
+ ipv4(source).copy(b,12);ipv4(destination).copy(b,16);b.writeUInt16BE(checksum(b.subarray(0,20)),10);
61
+ b.writeUInt16BE(68,20);b.writeUInt16BE(67,22);b.writeUInt16BE(8+payload.length,24);payload.copy(b,28);
62
+ // IPv4 allows zero UDP checksum; the IPv4 header is checksummed above.
63
+ return b;
64
+ }
65
+ export function unwrapIPv4(b) {
66
+ if(b.length<28||b[0]>>>4!==4||b[9]!==17)return null;
67
+ const h=(b[0]&15)*4,n=b.readUInt16BE(2);
68
+ if(h<20||n>b.length||n<h+8||(b.readUInt16BE(6)&0x3fff)||checksum(b.subarray(0,h)))return null;
69
+ const len=b.readUInt16BE(h+4);
70
+ if(b.readUInt16BE(h)!==67||b.readUInt16BE(h+2)!==68||len<8||h+len>n)return null;
71
+ if(b.readUInt16BE(h+6)){
72
+ const pseudo=Buffer.alloc(12);b.copy(pseudo,0,12,20);pseudo[9]=17;pseudo.writeUInt16BE(len,10);
73
+ if(checksum(Buffer.concat([pseudo,b.subarray(h,h+len)])))return null;
74
+ }
75
+ return b.subarray(h+8,h+len);
76
+ }
77
+ export class NakError extends Error {}
78
+ export class DhcpClient {
79
+ constructor({transport,mac,hostname,timeout=60,signal,log=()=>{},now=Date.now,sleep=ms=>Bun.sleep(ms),random=Math.random}) {
80
+ Object.assign(this,{transport,mac,hostname,timeout,signal,log,now,sleep,random});
81
+ }
82
+ get stopped(){return this.signal?.aborted;}
83
+ async exchange(fields,types,deadline,server) {
84
+ const xid=randomBytes(4).readUInt32BE(),start=this.now();let retry=0,next=start;
85
+ while(!this.stopped&&this.now()<deadline){
86
+ if(this.now()>=next){
87
+ const packet=encode({...fields,xid,mac:this.mac,hostname:this.hostname,secs:Math.floor((this.now()-start)/1000)});
88
+ this.transport.send(packet,fields.broadcast===false?server:undefined);
89
+ next=this.now()+Math.min(64000,4000*2**Math.min(retry++,4))*(.75+.5*this.random());
90
+ }
91
+ // Bound the work per iteration so unrelated traffic cannot starve timers.
92
+ for(let i=0;i<64;i++){
93
+ const packet=this.transport.receive();if(!packet)break;
94
+ const reply=decode(packet,{xid,mac:this.mac});
95
+ if(!reply||!types.includes(reply.type)||!reply.server||(server&&reply.server!==server))continue;
96
+ if(reply.type===TYPE.NAK)throw new NakError('DHCP server rejected the lease');
97
+ return {reply,sentAt:start};
98
+ }
99
+ await this.sleep(Math.min(20,Math.max(1,deadline-this.now())));
100
+ }
101
+ return null;
102
+ }
103
+ async acquire() {
104
+ const deadline=this.now()+this.timeout*1000;
105
+ while(!this.stopped&&this.now()<deadline){
106
+ this.log('DHCPDISCOVER');
107
+ const offer=await this.exchange({type:TYPE.DISCOVER},[TYPE.OFFER],deadline);
108
+ if(!offer)return null;
109
+ if(offer.reply.address==='0.0.0.0')continue;
110
+ this.log(`DHCPOFFER ${offer.reply.address} from ${offer.reply.server}`);
111
+ try {
112
+ const result=await this.exchange({type:TYPE.REQUEST,requested:offer.reply.address,server:offer.reply.server},[TYPE.ACK,TYPE.NAK],Math.min(deadline,this.now()+16000),offer.reply.server);
113
+ if(result){if(result.reply.address!==offer.reply.address)continue;return leaseFrom(result.reply,offer.reply,result.sentAt);}
114
+ }catch(error){if(!(error instanceof NakError))throw error;await this.sleep(1000);}
115
+ }
116
+ return null;
117
+ }
118
+ async renew(lease,rebind=false) {
119
+ this.log(rebind?'DHCPREQUEST broadcast (rebinding)':'DHCPREQUEST unicast (renewing)');
120
+ const result=await this.exchange({type:TYPE.REQUEST,ciaddr:lease.address,broadcast:rebind},[TYPE.ACK,TYPE.NAK],rebind?lease.expiresAt:lease.rebindAt,rebind?undefined:lease.server);
121
+ return result?leaseFrom(result.reply,lease,result.sentAt):null;
122
+ }
123
+ release(lease){this.transport.send(encode({type:TYPE.RELEASE,xid:randomBytes(4).readUInt32BE(),mac:this.mac,ciaddr:lease.address,server:lease.server,broadcast:false}),lease.server);}
124
+ }
@@ -3,8 +3,7 @@
3
3
  // init.js opens musl on its own because it runs before /proc exists and must
4
4
  // not import anything. Every other command imports this module instead, so
5
5
  // the symbol table, the errno plumbing and the error messages live in one
6
- // place. The library is located relative to this file (/lib in the guest),
7
- // which also lets the commands run from a checkout for testing.
6
+ // place. Automatic lookup follows the runtime ABI; BUNINU_LIBC overrides it.
8
7
 
9
8
  import { dlopen, FFIType, ptr, read, CString, toArrayBuffer } from "bun:ffi";
10
9
 
@@ -20,11 +19,13 @@ const architectures = {
20
19
  export const arch = architectures[process.arch]
21
20
  ?? (() => { throw new Error(`unsupported CPU architecture ${process.arch}`); })();
22
21
 
23
- // BUNINU_LIBC lets the commands run on a glibc host for testing:
24
- // BUNINU_LIBC=/lib/aarch64-linux-gnu/libc.so.6 bun initramfs/bin/ip addr
25
- export const libcPath = process.env.BUNINU_LIBC ?? `${import.meta.dir}/${arch.libc}`;
26
-
27
- export const libc = dlopen(libcPath, {
22
+ // Automatic lookup must not mix bionic, glibc and musl runtime ABIs.
23
+ // An explicit override is authoritative: report its error instead of hiding it.
24
+ const android = process.platform === "android";
25
+ const glibc = !android && !!process.report?.getReport?.().header?.glibcVersionRuntime;
26
+ export const libcFamily = android ? "bionic" : glibc ? "glibc" : "musl";
27
+ const errnoSymbol = android ? "__errno" : "__errno_location";
28
+ const definitions = {
28
29
  mount: {
29
30
  args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.ptr],
30
31
  returns: FFIType.i32,
@@ -41,7 +42,7 @@ export const libc = dlopen(libcPath, {
41
42
  args: [],
42
43
  returns: FFIType.void,
43
44
  },
44
- __errno_location: {
45
+ [errnoSymbol]: {
45
46
  args: [],
46
47
  returns: FFIType.ptr,
47
48
  },
@@ -77,6 +78,14 @@ export const libc = dlopen(libcPath, {
77
78
  args: [FFIType.i32, FFIType.i32, FFIType.i32],
78
79
  returns: FFIType.i32,
79
80
  },
81
+ bind: { args: [FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
82
+ inet_pton: { args: [FFIType.i32, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
83
+ connect: { args: [FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
84
+ getsockname: { args: [FFIType.i32, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
85
+ setsockopt: { args: [FFIType.i32, FFIType.i32, FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i32 },
86
+ send: { args: [FFIType.i32, FFIType.ptr, FFIType.u64, FFIType.i32], returns: FFIType.i64 },
87
+ sendto: { args: [FFIType.i32, FFIType.ptr, FFIType.u64, FFIType.i32, FFIType.ptr, FFIType.u32], returns: FFIType.i64 },
88
+ recv: { args: [FFIType.i32, FFIType.ptr, FFIType.u64, FFIType.i32], returns: FFIType.i64 },
80
89
  syscall: {
81
90
  args: [FFIType.i64, FFIType.i64, FFIType.ptr, FFIType.i64],
82
91
  returns: FFIType.i32,
@@ -93,13 +102,51 @@ export const libc = dlopen(libcPath, {
93
102
  args: [FFIType.ptr],
94
103
  returns: FFIType.i32,
95
104
  },
96
- });
105
+ };
106
+ const override = process.env.BUNINU_LIBC;
107
+ const candidates = override !== undefined ? [override]
108
+ : android ? ["libc.so", "/apex/com.android.runtime/lib64/bionic/libc.so", "/system/lib64/libc.so"]
109
+ : glibc ? ["libc.so.6", `/lib/${process.arch === "x64" ? "x86_64" : "aarch64"}-linux-gnu/libc.so.6`, `/usr/lib/${process.arch === "x64" ? "x86_64" : "aarch64"}-linux-gnu/libc.so.6`]
110
+ : [`${import.meta.dir}/${arch.libc}`, arch.libc, `/lib/${arch.libc}`, `/lib/ld-musl-${process.arch === "x64" ? "x86_64" : "aarch64"}.so.1`, "libc.so"];
111
+ // Bun requires at least one symbol per dlopen call. Defer opening until a
112
+ // symbol is requested; unrelated missing symbols never prevent an import.
113
+ export let libcPath;
114
+ const handles = [], bound = new Map();
115
+ let closed = false;
116
+ function resolve(name) {
117
+ if (bound.has(name)) return bound.get(name);
118
+ const failures = [];
119
+ for (const path of libcPath === undefined ? candidates : [libcPath]) {
120
+ try {
121
+ const handle = dlopen(path, { [name]: definitions[name] });
122
+ libcPath = path; handles.push(handle); bound.set(name, handle.symbols[name]);
123
+ return handle.symbols[name];
124
+ } catch (error) { failures.push(`${path}: ${error.message}`); }
125
+ }
126
+ throw new Error(`Cannot load libc symbol ${name}:\n${failures.join("\n")}`);
127
+ }
128
+ export const libc = {
129
+ symbols: new Proxy({}, {
130
+ get(_target, name) {
131
+ if (closed) throw new Error("libc is closed");
132
+ if (!Object.hasOwn(definitions, name)) return undefined;
133
+ const symbol = resolve(name);
134
+ // Compile the errno accessor before the native call, so first-use FFI
135
+ // compilation cannot clobber the errno that the caller wants to inspect.
136
+ // An absent accessor only fails when errno() is actually requested.
137
+ if (name !== errnoSymbol && !bound.has(errnoSymbol)) {
138
+ try { resolve(errnoSymbol); } catch {}
139
+ }
140
+ return symbol;
141
+ },
142
+ }),
143
+ close() { if (closed) return; closed = true; for (const handle of handles) handle.close(); },
144
+ };
97
145
 
98
146
  export const cString = (value) => new TextEncoder().encode(`${value}\0`);
99
147
 
100
148
  // musl reports failure as -1 plus errno; the raw syscall() wrapper does too.
101
- const errnoLocation = libc.symbols.__errno_location();
102
- export const errno = () => read.i32(errnoLocation);
149
+ export const errno = () => read.i32(libc.symbols[errnoSymbol]());
103
150
  export const strerror = (code) => new CString(libc.symbols.strerror(code)).toString();
104
151
 
105
152
  export const Errno = {
@@ -0,0 +1,110 @@
1
+ import { isIP } from 'node:net';
2
+ import { randomBytes } from 'node:crypto';
3
+
4
+ export function parsePing(args) {
5
+ const o={count:4,interval:1,timeout:2,deadline:Infinity,size:56,ttl:64,family:0,quiet:false,timestamp:false};
6
+ const values={c:'count',i:'interval',W:'timeout',w:'deadline',s:'size',t:'ttl',I:'source'};
7
+ let positional=false;
8
+ for(let i=0;i<args.length;i++){
9
+ const arg=args[i];
10
+ if(!positional&&arg==='--'){positional=true;continue;}
11
+ if(!positional&&(arg==='--help'||arg==='-h')){o.help=true;continue;}
12
+ if(!positional&&arg.startsWith('-')&&arg!=='-'){
13
+ for(let j=1;j<arg.length;j++){
14
+ const flag=arg[j];
15
+ if(values[flag]){const value=arg.slice(j+1)||args[++i];if(value===undefined||value==='')throw Error(`-${flag} requires a value`);if(flag!=='I'&&(!Number.isFinite(Number(value))||!value.trim()))throw Error(`invalid ${values[flag]}`);o[values[flag]]=flag==='I'?value:Number(value);break;}
16
+ if(flag==='4'||flag==='6')o.family=Number(flag);
17
+ else if(flag==='q')o.quiet=true;
18
+ else if(flag==='D')o.timestamp=true;
19
+ else if(flag!=='n')throw Error(`unknown option -${flag}`);
20
+ }
21
+ }else{if(o.host)throw Error('only one destination is supported');o.host=arg;}
22
+ }
23
+ if(o.help)return o;
24
+ if(!o.host)throw Error('destination required (see ping --help)');
25
+ for(const key of ['count','size','ttl'])if((key!=='count'||o.count!==Infinity)&&(!Number.isInteger(o[key])||o[key]<(key==='size'?0:1)))throw Error(`invalid ${key}`);
26
+ if(o.size>65507||o.ttl>255||o.count>Number.MAX_SAFE_INTEGER&&o.count!==Infinity)throw Error('count, packet size or TTL out of range');
27
+ for(const key of ['interval','timeout','deadline'])if(!(o[key]>0)||(!Number.isFinite(o[key])&&!(key==='deadline'&&o[key]===Infinity)))throw Error(`invalid ${key}`);
28
+ if(o.interval<.002)throw Error('minimum interval is 0.002 seconds');
29
+ return o;
30
+ }
31
+ export function checksum(bytes) {
32
+ let sum=0;for(let i=0;i<bytes.length;i+=2)sum+=(bytes[i]<<8)|(bytes[i+1]??0);
33
+ while(sum>>>16)sum=(sum&65535)+(sum>>>16);return (~sum)&65535;
34
+ }
35
+ export function echoPacket(family,id,seq,size) {
36
+ const b=Buffer.alloc(size+8);b[0]=family===6?128:8;b.writeUInt16BE(id,4);b.writeUInt16BE(seq,6);randomBytes(size).copy(b,8);
37
+ if(family===4)b.writeUInt16BE(checksum(b),2);return b;
38
+ }
39
+ export function isEchoReply(bytes,{family,raw,id,seq,packet}) {
40
+ let offset=0;
41
+ if(raw&&family===4){if(bytes.length<20||bytes[0]>>>4!==4||bytes[9]!==1)return false;offset=(bytes[0]&15)*4;if(offset<20||offset>bytes.length-8)return false;}
42
+ const b=bytes.subarray(offset);
43
+ return b.length===packet.length&&b[0]===(family===6?129:0)&&b[1]===0&&b.readUInt16BE(4)===id&&b.readUInt16BE(6)===seq&&b.subarray(8).equals(packet.subarray(8))&&(family===6||checksum(b)===0);
44
+ }
45
+ export async function runPing(o,{log=console.log,error=console.error,forceRaw=false}={}) {
46
+ if(!['linux','android'].includes(process.platform))throw Error('ping currently supports Linux and Android; Windows needs an ICMP API backend');
47
+ const {libc,ptr,cString,errno,SysError,check}=await import('./dlopen.js');
48
+ const c=libc.symbols;
49
+ let family=o.family||isIP(o.host)||4,address=o.host;
50
+ if(isIP(address)&&isIP(address)!==family)throw Error('destination does not match selected address family');
51
+ if(!isIP(address)){
52
+ const records=await Bun.dns.lookup(address,{family});address=records.find(r=>r.family===family)?.address;
53
+ if(!address)throw Error(`no IPv${family} address for ${o.host}`);
54
+ }
55
+ const af=family===6?10:2;
56
+ const sockaddr=value=>{
57
+ const b=Buffer.alloc(family===6?28:16);b.writeUInt16LE(af,0);
58
+ if(c.inet_pton(af,ptr(cString(value)),ptr(b.subarray(family===6?8:4)))!==1)throw Error(`invalid IPv${family} address: ${value}`);
59
+ return b;
60
+ };
61
+ let raw=forceRaw,fd=c.socket(af,(raw?3:2)|0x80800,family===6?58:1);
62
+ if(fd<0&&!raw){const code=errno();if(![1,13,93,97].includes(code))throw new SysError('ICMP socket',code);raw=true;fd=c.socket(af,3|0x80800,family===6?58:1);}
63
+ if(fd<0)throw new SysError('ICMP socket (allow ping_group_range or CAP_NET_RAW)');
64
+ let stop=false;const interrupt=()=>{stop=true};let transmitted=0,received=0,min=Infinity,max=0,sum=0,sumSquares=0;
65
+ const started=performance.now(),pending=new Map();let next=started;
66
+ const prefix=()=>o.timestamp?`[${(Date.now()/1000).toFixed(6)}] `:'';
67
+ try{
68
+ if(o.source){
69
+ if(isIP(o.source)){const src=sockaddr(o.source);check('bind source address',c.bind(fd,ptr(src),src.length));}
70
+ else{const name=cString(o.source);check('bind interface',c.setsockopt(fd,1,25,ptr(name),name.length));}
71
+ }
72
+ const ttl=Buffer.alloc(4);ttl.writeInt32LE(o.ttl);check('set TTL',c.setsockopt(fd,family===6?41:0,family===6?16:2,ptr(ttl),4));
73
+ const remote=sockaddr(address);check('connect',c.connect(fd,ptr(remote),remote.length));
74
+ const local=Buffer.alloc(28),length=Buffer.alloc(4);length.writeUInt32LE(local.length);check('getsockname',c.getsockname(fd,ptr(local),ptr(length)));
75
+ const id=raw?randomBytes(2).readUInt16BE():local.readUInt16BE(2);
76
+ log(`PING ${o.host} (${address}) ${o.size} data bytes`);
77
+ process.on('SIGINT',interrupt);process.on('SIGTERM',interrupt);
78
+ const buffer=Buffer.alloc(65536);
79
+ while(!stop&&performance.now()-started<o.deadline*1000){
80
+ let now=performance.now();
81
+ if(transmitted<o.count&&now>=next){
82
+ const seq=(transmitted+1)&65535;
83
+ // Avoid confusing a sequence that wrapped while an old probe is pending.
84
+ if(!pending.has(seq)){
85
+ const packet=echoPacket(family,id,seq,o.size);const sent=Number(c.send(fd,ptr(packet),packet.length,0));
86
+ if(sent!==packet.length)throw new SysError('send');
87
+ transmitted++;pending.set(seq,{packet,start:now});
88
+ }
89
+ next=now+o.interval*1000;
90
+ }
91
+ for(let n=0;n<64;n++){
92
+ const size=Number(c.recv(fd,ptr(buffer),buffer.length,0));
93
+ if(size<0){const code=errno();if(code===11)break;if(code===4)continue;if([101,111,113].includes(code)){if(!o.quiet)error(`${prefix()}ping: ${new SysError('receive',code).message}`);break;}throw new SysError('receive',code);}
94
+ const b=buffer.subarray(0,size),offset=raw&&family===4?(b[0]&15)*4:0;
95
+ if(size<offset+8)continue;const seq=b.readUInt16BE(offset+6),p=pending.get(seq);
96
+ if(!p||!isEchoReply(b,{family,raw,id,seq,packet:p.packet}))continue;
97
+ const ms=performance.now()-p.start;pending.delete(seq);received++;min=Math.min(min,ms);max=Math.max(max,ms);sum+=ms;sumSquares+=ms*ms;
98
+ if(!o.quiet)log(`${prefix()}${p.packet.length} bytes from ${address}: icmp_seq=${seq}${raw&&family===4?` ttl=${b[8]}`:''} time=${ms.toFixed(3)} ms`);
99
+ }
100
+ now=performance.now();for(const [seq,p] of pending)if(now-p.start>=o.timeout*1000){pending.delete(seq);if(!o.quiet)log(`${prefix()}Request timeout for icmp_seq=${seq}`);}
101
+ if(transmitted>=o.count&&!pending.size)break;
102
+ const sendDelay=transmitted<o.count?next-now:10;
103
+ await Bun.sleep(Math.max(1,Math.min(10,sendDelay,started+o.deadline*1000-now)));
104
+ }
105
+ }finally{process.removeListener('SIGINT',interrupt);process.removeListener('SIGTERM',interrupt);c.close(fd);}
106
+ log(`\n--- ${o.host} ping statistics ---`);
107
+ log(`${transmitted} packets transmitted, ${received} received, ${transmitted?((transmitted-received)/transmitted*100).toFixed(0):0}% packet loss, time ${Math.round(performance.now()-started)}ms`);
108
+ if(received)log(`rtt min/avg/max/mdev = ${min.toFixed(3)}/${(sum/received).toFixed(3)}/${max.toFixed(3)}/${Math.sqrt(Math.max(0,sumSquares/received-(sum/received)**2)).toFixed(3)} ms`);
109
+ return received>0&&!(Number.isFinite(o.deadline)&&Number.isFinite(o.count)&&received<o.count)?0:1;
110
+ }
@@ -35,7 +35,7 @@ identified from the process task name and its open console descriptor under
35
35
  | --- | --- |
36
36
  | `N`, `/dev/ttyN` | The virtual console to use for graphics and the keyboard; for example, `2` means `/dev/tty2`. Naming a VT other than the active one starts a detached bunterm there, switches the display to it, and immediately returns control to the calling shell. Without this argument, bunterm uses the active VT in the foreground. |
37
37
  | `-e command [argument...]` | The program to run; everything after `-e` belongs to it. Default `/bin/sh`. |
38
- | `-s N`, `--font-size N` | Font size in pixels (default 16). The cell is 0.6 × N wide and 1.16 × N tall with DejaVu Sans Mono, so 1024×768 at 16 px gives 106 × 41 cells. |
38
+ | `-s N`, `--font-size N` | Font size in pixels (default 13). The cell is 0.6 × N wide and 1.16 × N tall with DejaVu Sans Mono, so 1024×768 at 16 px gives 106 × 41 cells. |
39
39
  | `--line-height F` | Multiplies the cell height (default 1). |
40
40
  | `--fb /dev/fbN` | The framebuffer device (default `/dev/fb0`). |
41
41
  | `--no-blink` | A steady cursor. |
@@ -0,0 +1,82 @@
1
+ # dhclient — DHCPv4 client
2
+
3
+ Use `ip link` to list available network interfaces, then choose the interface
4
+ name for the command, for example `dhclient usb0`.
5
+
6
+ Acquire an IPv4 lease on an Ethernet-compatible interface. DHCP packets are
7
+ implemented in JavaScript; Bun FFI calls the runtime libc for packet/UDP
8
+ sockets and interface identification. Address and route changes invoke the
9
+ existing Buninu `ip` command, without an external DHCP binary or dependencies.
10
+
11
+ ```sh
12
+ dhclient eth0 # acquire, configure, then exit
13
+ dhclient -v -timeout 20 eth0 # show progress, fail after 20 seconds
14
+ dhclient -r eth0 # release a saved lease after the client stops
15
+ dhclient --no-dns eth0 # leave the resolver unchanged
16
+ ```
17
+
18
+ | Option | Meaning |
19
+ | --- | --- |
20
+ | `-4` | DHCPv4 (default) |
21
+ | `-d` | Accepted for compatibility; all invocations exit after configuration |
22
+ | `-1` | Acquire once (already the default) |
23
+ | `-r` | Send DHCPRELEASE and remove the saved lease's configuration |
24
+ | `-timeout SECONDS` | Initial acquisition deadline; default 60 seconds |
25
+ | `-v`, `-q` | Verbose protocol progress, or quiet output |
26
+ | `-H HOSTNAME` | Send DHCP hostname option |
27
+ | `-lf PATH` | JSON lease file; default `/var/lib/dhclient/INTERFACE.json` |
28
+ | `--metric NUMBER` | Default route metric, 0–32766 |
29
+ | `--no-dns` | Do not write DNS configuration |
30
+ | `--resolv-conf PATH` | Resolver file; default `/etc/resolv.conf` |
31
+ | `-h`, `--help` | This manual |
32
+
33
+ Specify one interface per process. The interface is brought up first. This
34
+ client replaces its primary IPv4 address and installs the first offered
35
+ router as its default gateway. DNS server addresses replace the resolver
36
+ file; its previous content is saved and restored on explicit release only if
37
+ another writer has not modified it. `--no-dns` is useful for secondary links.
38
+ Avoid simultaneous resolver management by multiple clients.
39
+
40
+ Each invocation acquires and configures one lease, then exits. No background
41
+ renewal process is started. `-1` is an optional alias for this default behavior.
42
+ A DHCP lease is still time-limited unless the server grants an infinite lease:
43
+ run the command again before expiry, or use an appropriate static address.
44
+ After exit this command cannot renew or remove an expired address. The reported
45
+ lease duration and JSON timestamps show the server's validity period.
46
+ Ctrl-C/SIGTERM during acquisition stops the attempt; successful configuration
47
+ remains after normal exit. Use `-r` to release it explicitly. Saved JSON leases
48
+ support release and resolver restoration; their addresses are never assumed
49
+ valid on a later acquisition.
50
+
51
+ The per-interface `/run/dhclient-INTERFACE.lock` prevents duplicate Buninu
52
+ clients, including callers using different lease files. Stop the running
53
+ client before `-r`. After an unclean kill, verify it is gone before removing
54
+ the stale lock. UDP port 68 is reserved exclusively to detect other clients.
55
+
56
+ Linux/Android on x86-64 or aarch64 is supported, subject to privileges:
57
+ CAP_NET_RAW for packet sockets, CAP_NET_ADMIN for interface/route changes,
58
+ and permission to bind UDP 68 and write lease/resolver files. Ordinary
59
+ unprivileged Termux cannot configure Android networking. Root in Buninu's
60
+ VM can. Library selection follows the shared `dlopen.js` runtime ABI rules.
61
+
62
+ This is a focused client, not an ISC dhclient CLI clone. DHCPv6, ISC config/
63
+ script hooks, address-conflict ARP probing/DECLINE, classless static routes,
64
+ and automatic renewal/daemonization are not implemented. Wi-Fi must already be
65
+ associated. A failed acquisition exits nonzero; successful one-shot/release
66
+ or clean signal termination returns zero.
67
+
68
+ Protocol: [RFC 2131](https://www.rfc-editor.org/rfc/rfc2131) and
69
+ [DHCP options, RFC 2132](https://www.rfc-editor.org/rfc/rfc2132).
70
+ Run `bun test test/dhcp.test.js test/libc.test.js` for codec, state-machine,
71
+ configuration and libc regression tests; tests do not reconfigure host links.
72
+
73
+ `cfg.net` launches foreground clients as background child processes. Each has
74
+ a 20-second acquisition deadline and an independent 25-second startup watchdog
75
+ (SIGTERM, then SIGKILL after 2 seconds if necessary). Successful clients exit after configuration; none remain to renew. All child and output failures are caught and logged. Metrics start
76
+ at 100 per interface; only the first interface may write DNS. Repeated calls
77
+ reuse active clients; a later call can retry clients which have exited.
78
+
79
+ Automatic calls report one final result per interface, prefixed with
80
+ `[dhcp INTERFACE]`. A failed interface does not change another interface's
81
+ success result or configuration; output is collected before reporting exit
82
+ status so a delayed stderr message cannot appear after its final result.
@@ -0,0 +1,91 @@
1
+ # ping — send ICMP echo requests
2
+
3
+ `ping` sends real IPv4 ICMP or IPv6 ICMPv6 echo requests through Bun FFI.
4
+ It does not execute a system ping, open a TCP connection, or use an HTTP probe.
5
+
6
+ ```sh
7
+ ping [-4|-6] [-nqD] [-c COUNT] [-i SECONDS] [-W SECONDS]
8
+ [-w SECONDS] [-s BYTES] [-t TTL] [-I ADDRESS|INTERFACE] DESTINATION
9
+ ping --help
10
+ ```
11
+
12
+ | Option | Meaning |
13
+ |---|---|
14
+ | `-c COUNT` | Stop after COUNT requests and their replies/timeouts. Default: 4 requests, then exit. |
15
+ | `-i SECONDS` | Send interval, default 1 second; fractional values supported, minimum 0.002 seconds. |
16
+ | `-W SECONDS` | Per-request reply timeout, default 2 seconds; fractional values supported. |
17
+ | `-w SECONDS` | Overall probing deadline, including outstanding requests. DNS resolution happens first. |
18
+ | `-s BYTES` | Payload size, default 56; 0–65507 bytes. The ICMP header adds 8 bytes. |
19
+ | `-t TTL` | IPv4 TTL or IPv6 hop limit, 1–255; default 64. |
20
+ | `-I ADDRESS` | Bind a source IPv4/IPv6 address. |
21
+ | `-I INTERFACE` | Bind an interface such as `eth0` or `wlan0`; OS permissions may be required. |
22
+ | `-4`, `-6` | Select IPv4 or IPv6. Numeric destinations infer their family; hostnames default to IPv4. |
23
+ | `-n` | Numeric reply addresses (currently always used; no reverse DNS). |
24
+ | `-q` | Print only the initial line and final statistics. |
25
+ | `-D` | Add Unix timestamps to per-packet output. |
26
+ | `-h`, `--help` | Show this document. |
27
+
28
+ Short options can be combined (`-nq`) and values attached (`-c3`). Use `--`
29
+ to end option parsing. Unknown options are rejected rather than ignored.
30
+
31
+ ```sh
32
+ ping -c 4 1.1.1.1
33
+ ping -c 3 localhost
34
+ ping -6 -c 3 ::1
35
+ ping -n -c 10 -i 0.2 -W 1 -s 1200 192.168.1.1
36
+ ping -q -w 5 192.168.1.1
37
+ ping -I eth0 -c 3 192.168.1.1
38
+ ```
39
+
40
+ Statistics include transmitted/received counts, loss and RTT min/average/max/
41
+ standard deviation. Ctrl-C prints statistics and closes the socket. Exit status
42
+ is 0 if any reply arrived, 1 if none arrived (or a combined `-c`/`-w` deadline
43
+ prevented receiving the requested count), and 2 for argument/setup/system errors.
44
+ RTT is measured by the Bun process, so event-loop scheduling affects it.
45
+
46
+ The preferred socket is an unprivileged ICMP datagram (ping) socket. If the OS
47
+ refuses that socket, a raw ICMP socket is attempted; this normally requires root
48
+ or `CAP_NET_RAW`. Linux controls datagram access with `ping_group_range`, and
49
+ Android may impose additional restrictions. The command does not change system
50
+ permissions. Replies are matched by identifier, sequence and payload; IPv4
51
+ checksums are checked. Concurrent outstanding probes are supported. Datagram
52
+ replies do not expose their received TTL through this implementation, so it is
53
+ not printed; raw IPv4 replies do include TTL.
54
+
55
+ Supported runtimes: 64-bit Linux (glibc or musl) and native Android/Termux
56
+ (bionic), on x86_64 or aarch64. `/lib/dlopen.js` chooses libc automatically:
57
+
58
+ - Android: `libc.so`, then the APEX and `/system/lib64` bionic paths; `__errno`.
59
+ - Linux glibc runtime: `libc.so.6`, then architecture-specific `/lib` and
60
+ `/usr/lib` glibc paths; `__errno_location`.
61
+ - Linux musl runtime: the adjacent `libc.musl-<arch>.so.1`, its soname,
62
+ `/lib/libc.musl-<arch>.so.1`, `/lib/ld-musl-<arch>.so.1`, then `libc.so`;
63
+ `__errno_location`.
64
+ - Search stays within the runtime ABI: loading musl into glibc can crash on
65
+ errno/TLS access.
66
+ - `BUNINU_LIBC=/absolute/path` overrides the search. A bad override fails
67
+ explicitly rather than silently selecting a different library. Symbols resolve
68
+ on first use: an unavailable mount symbol does not prevent ping from using
69
+ the socket symbols. An explicit override is not rejected based on its filename;
70
+ matching the runtime ABI remains the caller's responsibility.
71
+
72
+ Windows requires a separate `iphlpapi.dll` ICMP backend and is not implemented.
73
+ Flood/adaptive modes, IPv4 record-route/timestamps, reverse DNS, broadcast ping,
74
+ and IPv6 scoped address notation (`fe80::1%eth0`) are not implemented.
75
+
76
+ ## Tests
77
+
78
+ ```sh
79
+ bun test test/ping.test.js test/libc.test.js
80
+ ```
81
+
82
+ These compare local IPv4/IPv6 echo replies, payload sizes, counts, quiet output,
83
+ source selection, deadlines and exit codes against the installed system ping.
84
+ RTT values are not expected to match. A missing system ping or unavailable
85
+ loopback ICMP support skips the corresponding network comparisons. Older
86
+ Android systems have a separate `ping6` and a 200 ms interval restriction; the
87
+ reference tests accommodate those without limiting this command's options.
88
+ Packet validation, argument errors, fast intervals and Ctrl-C also have tests.
89
+
90
+ References: [Linux ICMP sockets](https://man7.org/linux/man-pages/man7/icmp.7.html),
91
+ [Android errno](https://android.googlesource.com/platform/bionic/+/master/libc/include/errno.h).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu-linux",
3
- "version": "0.13.56",
3
+ "version": "0.14.57",
4
4
  "description": "A Linux distribution running Bun as PID 1 with the Buninu userspace.",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -37,6 +37,6 @@
37
37
  "fetch": "./index.js -f",
38
38
  "pack": "./index.js -b",
39
39
  "start": "./index.js -r",
40
- "check": "for f in ./index.js ./initramfs/init.js ./initramfs/lib/document.js ./initramfs/lib/dlopen.js ./initramfs/lib/fbdev.js ./initramfs/lib/canvas.js ./initramfs/lib/bunterm/glyphs.js ./initramfs/lib/bunterm/renderer.js ./initramfs/lib/bunterm/images.js ./initramfs/lib/bunterm/input.js ./initramfs/lib/bunterm/mouse.js ./initramfs/lib/bunterm/terminal.js ./initramfs/lib/modprobe.js ./initramfs/lib/power.js ./initramfs/bin/bunterm ./initramfs/bin/chroot ./initramfs/bin/mount ./initramfs/bin/umount ./initramfs/bin/ip ./initramfs/bin/ps ./initramfs/bin/top ./initramfs/bin/poweroff ./initramfs/bin/reboot ./initramfs/bin/tar ./initramfs/bin/stripansi; do node --check \"$f\" || exit 1; done"
40
+ "check": "for f in ./index.js ./initramfs/init.js ./initramfs/lib/document.js ./initramfs/lib/dlopen.js ./initramfs/lib/ping.js ./initramfs/lib/dhcp.js ./initramfs/lib/dhcp-transport.js ./initramfs/lib/dhclient.js ./initramfs/lib/dhcp-start.js ./initramfs/lib/fbdev.js ./initramfs/lib/canvas.js ./initramfs/lib/bunterm/glyphs.js ./initramfs/lib/bunterm/renderer.js ./initramfs/lib/bunterm/images.js ./initramfs/lib/bunterm/input.js ./initramfs/lib/bunterm/mouse.js ./initramfs/lib/bunterm/terminal.js ./initramfs/lib/modprobe.js ./initramfs/lib/power.js ./initramfs/bin/bunterm ./initramfs/bin/chroot ./initramfs/bin/mount ./initramfs/bin/umount ./initramfs/bin/ip ./initramfs/bin/ping ./initramfs/bin/dhclient ./initramfs/bin/ps ./initramfs/bin/top ./initramfs/bin/poweroff ./initramfs/bin/reboot ./initramfs/bin/tar ./initramfs/bin/stripansi; do node --check \"$f\" || exit 1; done"
41
41
  }
42
42
  }
package/scripts/arch.js CHANGED
@@ -16,8 +16,8 @@ export const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
16
16
 
17
17
  const pins = {
18
18
  x86_64: {
19
- linuxVirtSha256: "755deb045cfeb8c2bcaa2b2febe01ab2101d747921e64d4cd54d0312321e8d5d",
20
- linuxLtsSha256: "b0d8cc4d7e031b5619221e7978920d051bf1dd1e335f6d05d03964b2285fc3b4",
19
+ linuxVirtSha256: "414aa2572ef88164b1af9905fa6dfbe863bf50f6130d895d57282dea3e15f7b9",
20
+ linuxLtsSha256: "7614556471859956a436b95813daf409b444aa849ec899178b404e828c5a9d98",
21
21
  muslSha256: "573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71",
22
22
  stubSha256: "8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9",
23
23
  libstdcppSha256: "14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6",
@@ -32,8 +32,8 @@ const pins = {
32
32
  serialMajor: 4,
33
33
  },
34
34
  aarch64: {
35
- linuxVirtSha256: "e3839bfd751ff0a4421484f712b9b29efb514024523736a0ef62521521571b12",
36
- linuxLtsSha256: "795bcd49840be7f4ff9566f5f669201bd41202376a455b720a98eb814f0e72f8",
35
+ linuxVirtSha256: "b3820fee22e0366a15f7c139e5bc00348e333f3e7f77802271df7ded795aee4f",
36
+ linuxLtsSha256: "67b8f61c5cfb6ac9b5a65cb1610b67d467e6c4ffc17e7d29dbea4bd8c447ff32",
37
37
  muslSha256: "5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e",
38
38
  stubSha256: "a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38",
39
39
  libstdcppSha256: "2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7",
@@ -67,7 +67,7 @@ export const config = ({
67
67
  // The Alpine repository keeps only the newest build of a package, so a pin
68
68
  // stops downloading when Alpine bumps it; the hashes then fail loudly.
69
69
  const alpineBase = `${mirror}/v3.24/main/${arch}`;
70
- const kernelVersion = "6.18.56";
70
+ const kernelVersion = "6.18.57";
71
71
  const kernelRelease = `${kernelVersion}-0-${flavor}`;
72
72
  const bunVersion = "1.4.2";
73
73
  const downloadsDir = join(rootDir, "downloads", arch);
package/scripts/arch.sh CHANGED
@@ -44,7 +44,7 @@ fi
44
44
  alpine_mirror=${ALPINE_MIRROR:-https://dl-cdn.alpinelinux.org/alpine}
45
45
  alpine_base="$alpine_mirror/v3.24/main/$arch"
46
46
 
47
- kernel_version=6.18.56
47
+ kernel_version=6.18.57
48
48
  kernel_release="$kernel_version-0-$linux_flavor"
49
49
  linux_package="linux-$linux_flavor-$kernel_version-r0.apk"
50
50
  musl_package=musl-1.2.6-r2.apk
@@ -63,8 +63,8 @@ musl_libc="libc.musl-$arch.so.1"
63
63
 
64
64
  case $arch in
65
65
  x86_64)
66
- linux_virt_sha256=755deb045cfeb8c2bcaa2b2febe01ab2101d747921e64d4cd54d0312321e8d5d
67
- linux_lts_sha256=b0d8cc4d7e031b5619221e7978920d051bf1dd1e335f6d05d03964b2285fc3b4
66
+ linux_virt_sha256=414aa2572ef88164b1af9905fa6dfbe863bf50f6130d895d57282dea3e15f7b9
67
+ linux_lts_sha256=7614556471859956a436b95813daf409b444aa849ec899178b404e828c5a9d98
68
68
  musl_sha256=573712e2f49c15bfc20a2699f204acdfc74c772722b15e7353d768057fae0e71
69
69
  stub_sha256=8e64a5a3afee5f930e6e6716be726dc6d405530ac7f8fa5be6251dae68671ec9
70
70
  libstdcpp_sha256=14c987b556f5385a5db18376e788c75f37d85321b8dc1920d926ea7daac1d6f6
@@ -82,8 +82,8 @@ case $arch in
82
82
  serial_major=4
83
83
  ;;
84
84
  aarch64)
85
- linux_virt_sha256=e3839bfd751ff0a4421484f712b9b29efb514024523736a0ef62521521571b12
86
- linux_lts_sha256=795bcd49840be7f4ff9566f5f669201bd41202376a455b720a98eb814f0e72f8
85
+ linux_virt_sha256=b3820fee22e0366a15f7c139e5bc00348e333f3e7f77802271df7ded795aee4f
86
+ linux_lts_sha256=67b8f61c5cfb6ac9b5a65cb1610b67d467e6c4ffc17e7d29dbea4bd8c447ff32
87
87
  musl_sha256=5e9674b7f41152fe2119093b5cb4c13eaaadb19c2d5422b2d7267913e663ee6e
88
88
  stub_sha256=a1823d2d7082db555d528f82c1809f276c818aeb5b40f198f4565f104e055c38
89
89
  libstdcpp_sha256=2302e766d4e4926038ec166ecb85837ee884576115236ddb565e3a5fca4a11d7
@@ -21,6 +21,7 @@ export const requests = ({ arch, flavor, real }) => [
21
21
  "failover",
22
22
  "net_failover",
23
23
  "virtio_net",
24
+ "af_packet", // DHCP before an interface has an IPv4 address
24
25
  ]),
25
26
  ...optional([
26
27
  "virtio_pci",
@@ -1,15 +1,15 @@
1
1
  # npm packages
2
2
  # Latest versions checked on 2026-10-11; SHA-256 pins verified against registry SHA-512 integrity.
3
- # buninu-browser updated and verified on 2026-10-12.
4
- # casty 1.3.7 and buninu-land 0.1.3 verified on 2026-10-16.
3
+ # buninu-browser 0.2.4 and buninu-land 0.1.4 verified on 2026-10-18.
4
+ # casty 1.3.7 verified on 2026-10-16.
5
5
 
6
- "buninu-browser@0.2.3":
7
- - https://registry.npmjs.org/buninu-browser/-/buninu-browser-0.2.3.tgz
8
- - sha256:0f7c9cd9df689d7b0900fdf3172851125ebd8aba12bed555be7e091d427146e8
6
+ "buninu-browser@0.2.4":
7
+ - https://registry.npmjs.org/buninu-browser/-/buninu-browser-0.2.4.tgz
8
+ - sha256:247c1ae9644b5016d1dc3f74d1c6c18a986e3961b1d86d2497862e4007bc50aa
9
9
 
10
- "buninu-land@0.1.3":
11
- - https://registry.npmjs.org/buninu-land/-/buninu-land-0.1.3.tgz
12
- - sha256:b2c94f7fa0f7275ed739f4949d0be3ef45a99bf0b61e63a71ea9d6459f9f1c2e
10
+ "buninu-land@0.1.4":
11
+ - https://registry.npmjs.org/buninu-land/-/buninu-land-0.1.4.tgz
12
+ - sha256:5cda65c60a7af8c7a05ec519a254e0af3f9913ea94f6ae493e5ddc93a8d87407
13
13
 
14
14
  "@drxiaozhi/casty@1.3.7":
15
15
  - https://registry.npmjs.org/@drxiaozhi/casty/-/casty-1.3.7.tgz