@xcanwin/manyoyo 8.5.1 → 8.6.2

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/bin/manyoyo.js CHANGED
@@ -49,7 +49,7 @@ const { applyAptMirror } = require('../lib/mirrors');
49
49
  const containerState = require('../lib/container-state');
50
50
  const { buildExecArgs, resolveContainerId } = require('../lib/container-exec');
51
51
  const { createNetworkManager, NETWORK_NAME } = require('../lib/container-network');
52
- const { normalizePolicy } = require('../lib/network-policy');
52
+ const { normalizePolicy, isUnrestricted: isUnrestrictedPolicy } = require('../lib/network-policy');
53
53
  const { withEnvEndpoints } = require('../lib/network-endpoints');
54
54
  const { resolveManageOptions } = require('../lib/container-manage-options');
55
55
  const {
@@ -354,7 +354,7 @@ function installServeProcessDiagnostics(logger) {
354
354
  * @property {string} [containerRuntime] - 容器运行时(auto/docker/podman,默认 auto;仅全局配置生效)
355
355
  * @property {string} [autostart] - 容器每次启动时由容器内 init 执行的 bash 脚本(新建容器时写入状态目录)
356
356
  * @property {boolean} [autostartOnServe] - serve 启动时自动拉起此容器
357
- * @property {object} [network] - 网络策略(preset / host / egress / peers,见 lib/network-policy.js),不写为默认收紧
357
+ * @property {object} [network] - 网络策略(preset / outbound / inbound / expose,见 lib/network-policy.js),不写为默认收紧
358
358
  * @property {{apt?: string, npm?: string, pip?: string}} [mirrors] - 容器内 apt/npm/pip 软件源(http/https URL,空/缺省为官方默认;仅全局配置生效)
359
359
  * @property {boolean} [updateCheck] - serve 是否每天检查一次新版本(默认 true;仅全局配置生效,请求不附带任何本机信息)
360
360
  * @property {number} [cacheTTL] - 缓存过期天数
@@ -2141,8 +2141,8 @@ async function createNewContainer(runtime) {
2141
2141
 
2142
2142
  await ensureRunImage(runtime);
2143
2143
 
2144
- const networkPolicy = await withEnvEndpoints(normalizePolicy(runtime.network), [...userEnvLines, ...containerState.readEnvFilePaths(runtime.envFiles || []).flatMap(file => file.entries.map(entry => `${entry.key}=${entry.value}`))]);
2145
2144
  const networkManager = createCliNetworkManager(runtime);
2145
+ const networkPolicy = await withEnvEndpoints(await networkManager.resolveRefs(normalizePolicy(runtime.network)), [...userEnvLines, ...containerState.readEnvFilePaths(runtime.envFiles || []).flatMap(file => file.entries.map(entry => `${entry.key}=${entry.value}`))]);
2146
2146
  if (!hasNetworkArg([...(runtime.contModeArgs || []), ...(runtime.containerExtraArgs || [])])) {
2147
2147
  try {
2148
2148
  await networkManager.ensureBridgeNetwork();
@@ -2156,7 +2156,7 @@ async function createNewContainer(runtime) {
2156
2156
  envFiles: runtime.envFiles,
2157
2157
  autostart: runtime.autostart,
2158
2158
  network: networkPolicy,
2159
- netRequired: networkPolicy.preset !== 'open',
2159
+ netRequired: !isUnrestrictedPolicy(networkPolicy),
2160
2160
  meta: { name: runtime.containerName }
2161
2161
  });
2162
2162
 
@@ -129,4 +129,23 @@ function buildExecArgs(ctx, containerName, options = {}) {
129
129
  return { args, cleanup, invalid, containerId };
130
130
  }
131
131
 
132
- module.exports = { ID_LABEL, resolveContainerId, composeEnvLines, buildExecArgs };
132
+ /**
133
+ * 读出一个容器 exec 时会拿到的合并后 env(managed → 环境变量文件 → box/env),只在宿主机上读文件、不进容器。
134
+ * 旧容器(没有状态目录)返回空对象。调用方只能挑非敏感字段(如 *_MODEL)展示,不要整份下发给前端。
135
+ * @returns {Record<string, string>}
136
+ */
137
+ function readMergedEnv(ctx, containerName) {
138
+ const homeDir = ctx.homeDir || os.homedir();
139
+ const containerId = resolveContainerId(ctx.dockerExecArgs, containerName);
140
+ if (!containerId || !state.stateExists(homeDir, containerId)) return {};
141
+ const p = state.paths(homeDir, containerId);
142
+ const { lines } = composeEnvLines(readTextOrEmpty(p.managedEnv), readTextOrEmpty(p.env, true), [], state.envFileEntries(homeDir, containerId));
143
+ const env = {};
144
+ lines.forEach(line => {
145
+ const i = line.indexOf('=');
146
+ if (i > 0) env[line.slice(0, i)] = line.slice(i + 1);
147
+ });
148
+ return env;
149
+ }
150
+
151
+ module.exports = { ID_LABEL, resolveContainerId, composeEnvLines, buildExecArgs, readMergedEnv };
@@ -98,6 +98,11 @@ function parseUrlHostPort(value) {
98
98
  }
99
99
  }
100
100
 
101
+ // 指向宿主机的代理(host.*.internal / loopback)要放行到容器里看到的宿主机 IP,而不是在宿主机上解析
102
+ function isHostTarget(host) {
103
+ return HOST_NAMES.includes(host) || host === 'localhost' || host === '::1' || /^127\./.test(host);
104
+ }
105
+
101
106
  function uniqueBy(items, keyFn) {
102
107
  const seen = new Set();
103
108
  return items.filter(item => {
@@ -148,7 +153,7 @@ function createNetworkManager(options) {
148
153
  });
149
154
  }
150
155
 
151
- // 域名白名单的过滤代理:manyoyo 网络里的 sidecar 容器,按来源 IP 认容器(见 egress-sidecar.js)
156
+ // 域名规则的过滤代理:manyoyo 网络里的 sidecar 容器,按来源 IP 认容器(见 egress-sidecar.js)
152
157
  const sidecar = createSidecarManager({ run, command, homeDir, imageRef: getImageRef, networkName: NETWORK_NAME, ensureNetwork: ensureBridgeNetwork, getUpstream });
153
158
 
154
159
  // buildExecArgs 只需要同步 inspect 取 id;这里 withEnv:false 不会用到
@@ -180,6 +185,13 @@ function createNetworkManager(options) {
180
185
  })).filter(item => state.isValidId(item.id));
181
186
  }
182
187
 
188
+ // 把策略里按名称写的 @container:<名称> 换成 id;名称找不到抛错
189
+ async function resolveRefs(policy) {
190
+ if (!policyLib.hasNameRefs(policy)) return policy;
191
+ const byName = new Map((await listManaged()).map(item => [item.name, item.id]));
192
+ return policyLib.resolveContainerRefs(policy, name => byName.get(name) || '');
193
+ }
194
+
183
195
  function loadPolicy(id) {
184
196
  const raw = state.readNetworkRaw(homeDir, id);
185
197
  return policyLib.normalizePolicy(raw || undefined);
@@ -228,6 +240,29 @@ function createNetworkManager(options) {
228
240
  return await run(args, { timeout: 30000 });
229
241
  }
230
242
 
243
+ // 容器间:别的容器入站规则里允许了本容器(来源写 @containers 或 @container:<本容器>),本容器才能出去到它(派生行)。
244
+ // 来源写 @any 不派生:别的容器开放自己,不能顺带放宽本容器的出站限制
245
+ function computeDerived(self, all) {
246
+ const derived = [];
247
+ for (const other of all) {
248
+ if (other.id === self.id || !other.running || !state.stateExists(homeDir, other.id)) continue;
249
+ let otherPolicy;
250
+ try { otherPolicy = loadPolicy(other.id); } catch (e) { continue; }
251
+ const b = safeBridge(other.info);
252
+ if (!b) continue;
253
+ otherPolicy.inbound.filter(rule => rule.enabled && rule.action === 'allow' && (rule.source === '@containers' || rule.source === `@container:${self.id}`)).forEach(rule => {
254
+ derived.push({ id: other.id, name: other.name, ip: b.ip, ports: rule.ports, proto: rule.proto });
255
+ });
256
+ }
257
+ return derived;
258
+ }
259
+
260
+ async function derivedOf(name) {
261
+ const all = await listManaged();
262
+ const self = all.find(item => item.name === name);
263
+ return self ? computeDerived(self, all) : [];
264
+ }
265
+
231
266
  // 下发时现算:容器看到的宿主机 IP / DNS / 代理 / 镜像源 / peer 的当前 IP
232
267
  async function resolveEndpoints(self, policy, all) {
233
268
  const hostsText = await readRuntimeNetworkFacts(self.info);
@@ -239,10 +274,10 @@ function createNetworkManager(options) {
239
274
  const allow = [];
240
275
  const addHost = (ports, proto) => hostIps.forEach(ip => allow.push({ ip, ports: String(ports), proto }));
241
276
 
242
- // 必需端点:上游代理、过滤代理、Playwright、镜像源
277
+ // 必需端点(@manyoyo):上游代理、过滤代理、Playwright、镜像源
243
278
  const env = envMapOf(self.info);
244
279
  const proxyTargets = [];
245
- // allowlist 下容器只能经过滤代理出网,上游代理不能直连放行(否则绕过域名白名单)
280
+ // allowlist 下容器只能经过滤代理出网,上游代理不能直连放行(否则绕过域名规则)
246
281
  if (policy.preset !== 'allowlist') {
247
282
  Object.keys(env).filter(key => PROXY_ENV_KEYS.has(key)).forEach(key => {
248
283
  const parsed = env[key] && parseUrlHostPort(env[key]);
@@ -254,15 +289,20 @@ function createNetworkManager(options) {
254
289
  if (parsed) proxyTargets.push(parsed);
255
290
  });
256
291
  for (const target of uniqueBy(proxyTargets, t => `${t.host}:${t.port}`)) {
292
+ if (isHostTarget(target.host)) {
293
+ addHost(target.port, 'tcp');
294
+ continue;
295
+ }
257
296
  for (const ip of await resolveHostToIps(target.host)) allow.push({ ip, ports: String(target.port), proto: 'tcp' });
258
297
  }
259
- // 过滤代理(sidecar 容器):allowlist 容器经它出网,只放行到它的 IP:端口;
298
+ // 过滤代理(sidecar 容器):有启用的域名规则时,容器的 HTTP(S) 经它出网,只放行到它的 IP:端口;
260
299
  // 容器发往它的包来源必须是自己的 IP(第二道保险:识别身份靠的就是来源 IP)
261
300
  let managedEnv = [];
262
301
  const antiSpoof = [];
263
- if (policy.preset === 'allowlist' && policy.egress.domains.length) {
302
+ const proxied = policyLib.usesProxy(policy);
303
+ if (proxied) {
264
304
  const selfBridge = safeBridge(self.info);
265
- if (!selfBridge) throw new Error('域名白名单需要容器在 manyoyo 网络里,这个容器的网络不支持');
305
+ if (!selfBridge) throw new Error('域名规则需要容器在 manyoyo 网络里,这个容器的网络不支持');
266
306
  const proxy = await sidecar.ensure();
267
307
  allow.push({ ip: proxy.ip, ports: String(proxy.port), proto: 'tcp' });
268
308
  antiSpoof.push({ daddr: proxy.ip, saddr: selfBridge.ip });
@@ -270,50 +310,55 @@ function createNetworkManager(options) {
270
310
  managedEnv = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy'].map(key => `${key}=${url}`);
271
311
  }
272
312
  playwrightHostPorts().forEach(port => addHost(port, 'tcp'));
273
- policy.host.forEach(entry => addHost(entry.ports, entry.proto));
274
-
275
- // 容器间:我的 peers.inbound 里的容器能进来;别人的 inbound 列了我,我才能出去
276
- const peerIn = [];
277
- const byId = new Map(all.map(item => [item.id, item]));
278
- policy.peers.inbound.forEach(entry => {
279
- const peer = byId.get(entry.from);
280
- const b = peer && peer.running ? safeBridge(peer.info) : null;
281
- if (b) peerIn.push({ ip: b.ip, ports: entry.ports, proto: entry.proto });
313
+
314
+ const containers = {};
315
+ all.forEach(item => {
316
+ const b = item.running ? safeBridge(item.info) : null;
317
+ if (b) containers[item.id] = b.ip;
282
318
  });
283
- for (const other of all) {
284
- if (other.id === self.id || !other.running || !state.stateExists(homeDir, other.id)) continue;
285
- let otherPolicy;
286
- try { otherPolicy = loadPolicy(other.id); } catch (e) { continue; }
287
- const b = safeBridge(other.info);
288
- if (!b) continue;
289
- otherPolicy.peers.inbound.filter(entry => entry.from === self.id).forEach(entry => {
290
- allow.push({ ip: b.ip, ports: entry.ports, proto: entry.proto });
291
- });
292
- }
293
- return {
319
+ const derived = computeDerived(self, all);
320
+ derived.forEach(item => allow.push({ ip: item.ip, ports: item.ports, proto: item.proto }));
321
+ const bridge = bridgeOf(self.info);
322
+ const endpoints = {
294
323
  managedEnv,
295
324
  antiSpoof,
296
325
  dns: dnsServers,
297
326
  allow: uniqueBy(allow, a => `${a.ip}|${a.ports}|${a.proto}`),
298
- peerIn,
299
- bridge: bridgeOf(self.info) ? { subnet: bridgeOf(self.info).subnet, gateway: bridgeOf(self.info).gateway } : null
327
+ derived: uniqueBy(derived, d => `${d.id}|${d.ports}|${d.proto}`),
328
+ hostIps,
329
+ containers,
330
+ bridge: bridge ? { subnet: bridge.subnet, gateway: bridge.gateway } : null
300
331
  };
332
+ endpoints.proxyPolicy = proxied ? policyLib.compileProxyPolicy(policy, endpoints) : null;
333
+ return endpoints;
301
334
  }
302
335
 
303
336
  function safeBridge(info) {
304
337
  try { return bridgeOf(info); } catch (e) { return null; }
305
338
  }
306
339
 
307
- // 过滤代理的「来源 IP → 容器 id + 策略」:所有运行中、属于本 HOME、策略是 allowlist 的容器
340
+ // 过滤代理的「来源 IP → 容器 id + 编译好的规则」:所有运行中、属于本 HOME、出站有域名规则的容器。
341
+ // 规则在容器自己下发时编译并缓存;缓存没有(serve 刚重启、CLI 新进程)就沿用 clients.json 里同 id 同 IP 的旧项
342
+ const clientCache = new Map();
343
+ function clientEntryOf(item, previous) {
344
+ const bridge = safeBridge(item.info);
345
+ if (!bridge) return null;
346
+ const cached = clientCache.get(item.id);
347
+ if (cached && cached.ip === bridge.ip) return { ip: bridge.ip, entry: cached.entry };
348
+ const old = previous[bridge.ip];
349
+ return old && old.id === item.id ? { ip: bridge.ip, entry: old } : null;
350
+ }
351
+
308
352
  function syncEgressClients(all) {
353
+ const previous = sidecar.readClients();
309
354
  const clients = {};
310
355
  for (const item of all) {
311
356
  if (!item.running || !state.stateExists(homeDir, item.id)) continue;
312
357
  let policy;
313
358
  try { policy = loadPolicy(item.id); } catch (e) { continue; }
314
- if (policy.preset !== 'allowlist') continue;
315
- const bridge = safeBridge(item.info);
316
- if (bridge) clients[bridge.ip] = { id: item.id, preset: policy.preset, domains: policy.egress.domains, rules: policy.egress.rules };
359
+ if (!policyLib.usesProxy(policy)) continue;
360
+ const found = clientEntryOf(item, previous);
361
+ if (found) clients[found.ip] = found.entry;
317
362
  }
318
363
  sidecar.writeClients(clients);
319
364
  }
@@ -366,10 +411,13 @@ function createNetworkManager(options) {
366
411
  state.writeNetStatus(homeDir, self.id, status);
367
412
  return { ...status, id: self.id };
368
413
  }
369
- state.setNetRequired(homeDir, self.id, policy.preset !== 'open');
414
+ state.setNetRequired(homeDir, self.id, !policyLib.isUnrestricted(policy));
370
415
  const endpoints = await resolveEndpoints(self, policy, all);
371
416
  state.syncFilesEnv(homeDir, self.id); // 环境变量文件的最新快照(供自启动脚本用)
372
417
  state.writeManagedEnv(homeDir, self.id, endpoints.managedEnv);
418
+ const selfBridge = safeBridge(self.info);
419
+ if (endpoints.proxyPolicy && selfBridge) clientCache.set(self.id, { ip: selfBridge.ip, entry: { id: self.id, ...endpoints.proxyPolicy } });
420
+ else clientCache.delete(self.id);
373
421
  syncEgressClients(all); // 先让代理认得这个容器,再放行门闩
374
422
  await sendRuleset(name, policyLib.buildNftRuleset(policy, endpoints));
375
423
  await execInContainer(name, ['/bin/sh', '-c', `mkdir -p ${path.posix.dirname(GATE_READY)} && : > ${GATE_READY}`]);
@@ -378,7 +426,7 @@ function createNetworkManager(options) {
378
426
  status: 'applied',
379
427
  startedAt,
380
428
  policyHash: policyHashOf(policy),
381
- warning: privileged && policy.preset !== 'open' ? '特权容器(dind / sock 模式)里的进程自带 NET_ADMIN,可以改动这些规则,网络限制没有约束力' : '',
429
+ warning: privileged && !policyLib.isUnrestricted(policy) ? '特权容器(dind / sock 模式)里的进程自带 NET_ADMIN,可以改动这些规则,网络限制没有约束力' : '',
382
430
  at: new Date().toISOString()
383
431
  };
384
432
  state.writeNetStatus(homeDir, self.id, status);
@@ -386,7 +434,7 @@ function createNetworkManager(options) {
386
434
  } catch (e) {
387
435
  const message = `网络规则下发失败: ${e.message}`;
388
436
  try { state.writeNetStatus(homeDir, self.id, { status: 'error', message, startedAt, at: new Date().toISOString() }); } catch (err) { /* 状态目录可能已被删 */ }
389
- // 旧规则可能还在(比如 open → allowlist 失败):清掉门闩,让 manyoyo 的 exec 与自启动都按“未下发”处理
437
+ // 旧规则可能还在(比如不限制 → 收紧失败):清掉门闩,让 manyoyo 的 exec 与自启动都按“未下发”处理
390
438
  await clearGate(name);
391
439
  const error = new Error(message);
392
440
  error.cause = e;
@@ -420,7 +468,7 @@ function createNetworkManager(options) {
420
468
  } catch (e) {
421
469
  policy = null; // 策略文件损坏:按收紧处理,下发时会报错
422
470
  }
423
- if (policy && policy.preset === 'open') return { status: 'open' };
471
+ if (policy && policyLib.isUnrestricted(policy)) return { status: 'unrestricted' };
424
472
  // 判断依据在宿主机侧(net-status.json:同一次启动 + 同一份策略 + 下发成功),不依赖容器里的任何文件或命令
425
473
  const status = state.readNetStatus(homeDir, id);
426
474
  const startedAt = String((info.State && info.State.StartedAt) || '');
@@ -428,20 +476,25 @@ function createNetworkManager(options) {
428
476
  if (sameRun && status.status === 'unsupported') return { status: 'unsupported' };
429
477
  if (policy && sameRun && status.status === 'applied' && status.policyHash === policyHashOf(policy)) {
430
478
  // 规则还在,但过滤代理可能意外退了(容器出网此时是失败即关闭):顺手拉起,失败不拦 exec
431
- if (policy.preset === 'allowlist' && policy.egress.domains.length) {
479
+ if (policyLib.usesProxy(policy)) {
432
480
  try {
433
481
  if (!await sidecar.isHealthy()) {
434
482
  await sidecar.ensure();
435
483
  syncEgressClients(await listManaged());
436
484
  }
437
- } catch (e) { warn(`域名白名单代理恢复失败: ${e.message}`); }
485
+ } catch (e) { warn(`域名规则代理恢复失败: ${e.message}`); }
486
+ // 代理不认识这个容器(规则没缓存、clients.json 里也没有):重新下发一次
487
+ if (!clientEntryOf({ id, info }, sidecar.readClients())) return await apply(name, { expectId: id });
438
488
  }
439
489
  return { status: 'applied' };
440
490
  }
441
491
  return await apply(name, { expectId: id });
442
492
  }
443
493
 
444
- // 与 `name` 有 peers 关系的其他运行中容器(本容器重启后 IP 变化时要重算它们)
494
+ // 与 `name` 有容器间规则关系的其他运行中容器(本容器重启后 IP 变化时要重算它们)
495
+ const rulesOf = policy => [...policy.outbound, ...policy.inbound].filter(rule => rule.enabled);
496
+ const refersTo = (policy, id) => rulesOf(policy).some(rule => (rule.target || rule.source) === `@container:${id}`);
497
+ const opensToContainers = policy => policy.inbound.some(rule => rule.enabled && rule.action === 'allow' && rule.source === '@containers');
445
498
  async function relatedContainers(name) {
446
499
  const all = await listManaged();
447
500
  const self = all.find(item => item.name === name);
@@ -452,7 +505,7 @@ function createNetworkManager(options) {
452
505
  let p;
453
506
  let mine;
454
507
  try { p = loadPolicy(other.id); mine = loadPolicy(self.id); } catch (e) { continue; }
455
- if (p.peers.inbound.some(e => e.from === self.id) || mine.peers.inbound.some(e => e.from === other.id)) related.push(other.name);
508
+ if (refersTo(p, self.id) || refersTo(mine, other.id) || opensToContainers(p) || opensToContainers(mine)) related.push(other.name);
456
509
  }
457
510
  return related;
458
511
  }
@@ -468,22 +521,20 @@ function createNetworkManager(options) {
468
521
  }
469
522
  }
470
523
  // 没有容器需要重新下发规则时,过滤代理也要在(运行时重启后它同样是停着的)
471
- await healSidecar().catch(e => warn(`域名白名单代理恢复失败: ${e.message}`));
524
+ await healSidecar().catch(e => warn(`域名规则代理恢复失败: ${e.message}`));
472
525
  }
473
526
 
474
- // 订阅容器 start 事件:容器被外部重启(podman restart、machine 重启后手动 start)后 netns 重建、规则丢失,
475
- // 事件到来就重新下发,并重算与它有 peers 关系的容器(它的 IP 可能变了)。
476
- async function needsSidecar(all) {
527
+ async function needsSidecar(all) {
477
528
  return all.some(item => {
478
529
  if (!state.stateExists(homeDir, item.id)) return false;
479
530
  try {
480
531
  const policy = loadPolicy(item.id);
481
- return policy.preset === 'allowlist' && policy.egress.domains.length > 0;
532
+ return policyLib.usesProxy(policy);
482
533
  } catch (e) { return false; }
483
534
  });
484
535
  }
485
536
 
486
- // 过滤代理容器自己退出 / 被删:有 allowlist 容器就拉起来,并重写映射
537
+ // 过滤代理容器自己退出 / 被删:有用到它的容器就拉起来,并重写映射
487
538
  let lastHealAt = 0;
488
539
  let healTimer = null;
489
540
  async function healSidecar() {
@@ -494,7 +545,7 @@ function createNetworkManager(options) {
494
545
  if (!healTimer) {
495
546
  healTimer = setTimeout(() => {
496
547
  healTimer = null;
497
- healSidecar().catch(e => warn(`域名白名单代理恢复失败: ${e.message}`));
548
+ healSidecar().catch(e => warn(`域名规则代理恢复失败: ${e.message}`));
498
549
  }, wait + 50);
499
550
  if (healTimer.unref) healTimer.unref();
500
551
  }
@@ -601,7 +652,7 @@ function createNetworkManager(options) {
601
652
  };
602
653
  }
603
654
 
604
- return { NETWORK_NAME, sidecar, run, listManaged, loadPolicy, ensureBridgeNetwork, apply, ensureReady, relatedContainers, resolveEndpoints, reconcileAll, watchStarts };
655
+ return { NETWORK_NAME, sidecar, run, listManaged, loadPolicy, resolveRefs, derivedOf, ensureBridgeNetwork, apply, ensureReady, relatedContainers, resolveEndpoints, reconcileAll, watchStarts };
605
656
  }
606
657
 
607
658
  module.exports = {
@@ -611,5 +662,6 @@ module.exports = {
611
662
  bridgeOf,
612
663
  parseHostsAndResolv,
613
664
  parseUrlHostPort,
665
+ isHostTarget,
614
666
  createNetworkManager
615
667
  };
@@ -27,7 +27,7 @@ function buildContainerRunArgs(options) {
27
27
  '--name', options.containerName,
28
28
  '--entrypoint', `${SYS_MOUNT}/init.sh`,
29
29
  // docker 默认带 NET_RAW(podman 默认不带):有它的容器能发原始包 / 伪造 ARP,冒充别的容器的来源 IP,
30
- // 域名白名单的过滤代理就是按来源 IP 认容器的,所以统一去掉
30
+ // 域名规则的过滤代理就是按来源 IP 认容器的,所以统一去掉
31
31
  '--cap-drop', 'NET_RAW',
32
32
  ...(options.contModeArgs || []),
33
33
  ...(options.containerExtraArgs || []),