@bitcall/webrtc-sip-gateway 0.3.3 → 0.3.5

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/README.md CHANGED
@@ -7,10 +7,15 @@ Latest updates:
7
7
  startup, so advertise/origin/SIP transport values are runtime-accurate.
8
8
  - `init` and `reconfigure` stop an existing stack before preflight checks so
9
9
  the gateway's own `:5060` listener does not trigger false port conflicts.
10
- - `update` now syncs `BITCALL_GATEWAY_IMAGE` to the CLI target image tag
11
- before pulling and restarting.
10
+ - `update` now syncs `BITCALL_GATEWAY_IMAGE` to the CLI target image tag,
11
+ pulls, and force-recreates containers so new image layers are applied.
12
12
  - Docker image includes `sngrep` and `tcpdump` for SIP troubleshooting.
13
- - `sip-trace` opens a live SIP message viewer using `sngrep` in the container.
13
+ - `sip-trace` opens a live SIP message viewer using `sngrep` in the container
14
+ via compose service execution.
15
+ - Fixed nftables media firewall rule generation for IPv6 media-block mode
16
+ (nft-compatible port ranges and rule action order).
17
+ - Media firewall status now checks both nft and ip6tables marker rules so
18
+ legacy ip6tables protections are reported correctly.
14
19
  - `TURN_MODE=coturn` now generates a compose stack with a dedicated coturn
15
20
  container.
16
21
 
package/lib/firewall.js CHANGED
@@ -94,8 +94,11 @@ function buildNftRuleset(options = {}) {
94
94
  ];
95
95
 
96
96
  for (const rule of rules) {
97
+ const nftDport = String(rule.dport).includes(":")
98
+ ? String(rule.dport).replace(":", "-")
99
+ : String(rule.dport);
97
100
  lines.push(
98
- ` meta nfproto ipv6 ${rule.proto} dport ${rule.dport} comment \"${MARKER}\" drop`
101
+ ` meta nfproto ipv6 ${rule.proto} dport ${nftDport} drop comment \"${MARKER}\"`
99
102
  );
100
103
  }
101
104
 
@@ -282,8 +285,19 @@ function applyMediaIpv4OnlyRules(options = {}, runtime = {}) {
282
285
  const backend = runtime.backend || detectFirewallBackend(d);
283
286
 
284
287
  if (backend === "nft") {
285
- applyNftRules(options, d);
286
- return { backend };
288
+ try {
289
+ applyNftRules(options, d);
290
+ return { backend };
291
+ } catch (error) {
292
+ if (runtime.backend || !d.commandExists("ip6tables")) {
293
+ throw error;
294
+ }
295
+ applyIp6tablesRules(options, d);
296
+ return {
297
+ backend: "ip6tables",
298
+ fallbackFrom: "nft",
299
+ };
300
+ }
287
301
  }
288
302
 
289
303
  if (backend === "ip6tables") {
@@ -296,33 +310,77 @@ function applyMediaIpv4OnlyRules(options = {}, runtime = {}) {
296
310
 
297
311
  function removeMediaIpv4OnlyRules(options = {}, runtime = {}) {
298
312
  const d = withDeps(runtime.deps);
299
- const backend = runtime.backend || detectFirewallBackend(d);
313
+ const backend = runtime.backend;
300
314
 
301
- if (backend === "nft") {
315
+ if (!backend) {
316
+ const removed = [];
317
+
318
+ if (d.commandExists("nft") && isNftPresent(d)) {
319
+ removeNftRules(d);
320
+ removed.push("nft");
321
+ }
322
+ if (d.commandExists("ip6tables") && isIp6tablesPresent(d)) {
323
+ removeIp6tablesRules(options, d);
324
+ removed.push("ip6tables");
325
+ }
326
+
327
+ if (removed.length > 0) {
328
+ return { backend: removed.join("+") };
329
+ }
330
+ }
331
+
332
+ const selectedBackend = backend || detectFirewallBackend(d);
333
+
334
+ if (selectedBackend === "nft") {
302
335
  removeNftRules(d);
303
- return { backend };
336
+ return { backend: selectedBackend };
304
337
  }
305
338
 
306
- if (backend === "ip6tables") {
339
+ if (selectedBackend === "ip6tables") {
307
340
  removeIp6tablesRules(options, d);
308
- return { backend };
341
+ return { backend: selectedBackend };
309
342
  }
310
343
 
311
- throw new Error(`Unsupported firewall backend: ${backend}`);
344
+ throw new Error(`Unsupported firewall backend: ${selectedBackend}`);
312
345
  }
313
346
 
314
347
  function isMediaIpv4OnlyRulesPresent(runtime = {}) {
315
348
  const d = withDeps(runtime.deps);
316
- let backend = runtime.backend;
349
+ const backend = runtime.backend;
350
+
351
+ if (!backend) {
352
+ const nftEnabled = d.commandExists("nft") ? isNftPresent(d) : false;
353
+ if (nftEnabled) {
354
+ return {
355
+ enabled: true,
356
+ backend: "nft",
357
+ marker: MARKER,
358
+ };
359
+ }
317
360
 
318
- try {
319
- backend = backend || detectFirewallBackend(d);
320
- } catch (error) {
321
- return {
322
- enabled: false,
323
- backend: null,
324
- error: error.message,
325
- };
361
+ const ip6tablesEnabled = d.commandExists("ip6tables") ? isIp6tablesPresent(d) : false;
362
+ if (ip6tablesEnabled) {
363
+ return {
364
+ enabled: true,
365
+ backend: "ip6tables",
366
+ marker: MARKER,
367
+ };
368
+ }
369
+
370
+ try {
371
+ const preferredBackend = detectFirewallBackend(d);
372
+ return {
373
+ enabled: false,
374
+ backend: preferredBackend,
375
+ marker: MARKER,
376
+ };
377
+ } catch (error) {
378
+ return {
379
+ enabled: false,
380
+ backend: null,
381
+ error: error.message,
382
+ };
383
+ }
326
384
  }
327
385
 
328
386
  if (backend === "nft") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitcall/webrtc-sip-gateway",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Linux CLI for bootstrapping and managing the Bitcall WebRTC-to-SIP Gateway",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.js CHANGED
@@ -1424,16 +1424,23 @@ function logsCommand(service, options) {
1424
1424
 
1425
1425
  function sipTraceCommand() {
1426
1426
  ensureInitialized();
1427
- const compose = detectComposeCommand();
1428
- const containerName = "bitcall-gateway";
1427
+ const serviceName = "gateway";
1428
+ const containerNameFallback = "bitcall-gateway";
1429
1429
 
1430
1430
  // Check if sngrep is available in the container
1431
- const check = run(
1432
- compose.command,
1433
- [...compose.prefixArgs, "exec", "-T", containerName, "which", "sngrep"],
1434
- { cwd: GATEWAY_DIR, check: false, stdio: "pipe" }
1431
+ let check = runCompose(
1432
+ ["exec", "-T", serviceName, "which", "sngrep"],
1433
+ { check: false, stdio: "pipe" }
1435
1434
  );
1436
1435
 
1436
+ // Backward-compatible fallback for stacks where service naming diverged.
1437
+ if (check.status !== 0) {
1438
+ check = run("docker", ["exec", containerNameFallback, "which", "sngrep"], {
1439
+ check: false,
1440
+ stdio: "pipe",
1441
+ });
1442
+ }
1443
+
1437
1444
  if (check.status !== 0) {
1438
1445
  console.error("sngrep is not available in this gateway image.");
1439
1446
  console.error("Update to the latest image: sudo bitcall-gateway update");
@@ -1441,11 +1448,10 @@ function sipTraceCommand() {
1441
1448
  }
1442
1449
 
1443
1450
  // Run sngrep interactively inside the container
1444
- const result = run(
1445
- compose.command,
1446
- [...compose.prefixArgs, "exec", containerName, "sngrep"],
1447
- { cwd: GATEWAY_DIR, stdio: "inherit" }
1448
- );
1451
+ const result = runCompose(["exec", serviceName, "sngrep"], {
1452
+ check: false,
1453
+ stdio: "inherit",
1454
+ });
1449
1455
 
1450
1456
  process.exit(result.status || 0);
1451
1457
  }
@@ -1624,7 +1630,8 @@ function updateCommand() {
1624
1630
  }
1625
1631
 
1626
1632
  runCompose(["pull"], { stdio: "inherit" });
1627
- runSystemctl(["reload", SERVICE_NAME], ["up", "-d", "--remove-orphans"]);
1633
+ runCompose(["up", "-d", "--force-recreate", "--remove-orphans"], { stdio: "inherit" });
1634
+ run("systemctl", ["start", SERVICE_NAME], { check: false, stdio: "ignore" });
1628
1635
 
1629
1636
  console.log(`\n${clr(_c.green, "✓")} Gateway updated to ${clr(_c.bold, PACKAGE_VERSION)}.`);
1630
1637
  console.log(clr(_c.dim, " To update the CLI: sudo npm i -g @bitcall/webrtc-sip-gateway@latest"));
@@ -1835,7 +1842,16 @@ function buildProgram() {
1835
1842
 
1836
1843
  async function main(argv = process.argv) {
1837
1844
  const program = buildProgram();
1838
- await program.parseAsync(argv);
1845
+ const normalizedArgv = argv.map((value, index) => {
1846
+ if (index <= 1) {
1847
+ return value;
1848
+ }
1849
+ if (value === "--sip-trace" || value === "-sip-trace") {
1850
+ return "sip-trace";
1851
+ }
1852
+ return value;
1853
+ });
1854
+ await program.parseAsync(normalizedArgv);
1839
1855
  }
1840
1856
 
1841
1857
  module.exports = {