socket-function 0.22.0 → 0.24.0

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.
@@ -38,6 +38,7 @@ export type SocketExposedShape<ExposedType extends SocketExposedInterface = Sock
38
38
  hooks?: SocketFunctionHook<ExposedType>[];
39
39
  clientHooks?: SocketFunctionClientHook<ExposedType>[];
40
40
  noDefaultHooks?: boolean;
41
+ /** BUG: I think this is broken if it is on the default hooks function? */
41
42
  noClientHooks?: boolean;
42
43
  };
43
44
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socket-function",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "note1": "note on node-forge fork, see https://github.com/digitalbazaar/forge/issues/744 for details",
@@ -122,13 +122,13 @@ export const runClientHooks = measureWrap(async function runClientHooks(
122
122
  ): Promise<ClientHookContext> {
123
123
  let context: ClientHookContext = { call: callType, connectionId };
124
124
 
125
- let clientHooks = (
126
- globalClientHooks
127
- .concat(hooks.clientHooks || [])
128
- );
129
- for (let otherClientHook of globalHooks.concat(hooks.hooks || []).map(x => x.clientHook)) {
130
- if (otherClientHook) {
131
- clientHooks.push(otherClientHook);
125
+ let clientHooks = hooks.clientHooks?.slice() || [];
126
+ if (!hooks.noClientHooks) {
127
+ clientHooks = globalClientHooks.concat(clientHooks);
128
+ for (let otherClientHook of globalHooks.concat(hooks.hooks || []).map(x => x.clientHook)) {
129
+ if (otherClientHook) {
130
+ clientHooks.push(otherClientHook);
131
+ }
132
132
  }
133
133
  }
134
134
  for (let hook of clientHooks) {
@@ -1,5 +1,7 @@
1
+ import debugbreak from "debugbreak";
1
2
  import * as dgram from "dgram";
2
3
  import os from "os";
4
+ import { timeInHour } from "./misc";
3
5
 
4
6
  const SSDP_DISCOVER_MX = 2;
5
7
  const SSDP_DISCOVER_MSG = `M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: "ssdp:discover"\r\nMX: ${SSDP_DISCOVER_MX}\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n\r\n`;
@@ -7,13 +9,16 @@ const SSDP_DISCOVER_MSG = `M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\n
7
9
  export async function forwardPort(config: {
8
10
  externalPort: number;
9
11
  internalPort: number;
12
+ duration?: number;
10
13
  }) {
11
14
  const { externalPort, internalPort } = config;
15
+ let duration = config.duration ?? timeInHour;
12
16
 
13
17
  const localObj = getLocalInterfaceAddress();
14
18
  if (!localObj) throw new Error("Could not find the local address / gateway");
15
19
 
16
20
  const { internalIP, gatewayIP } = localObj;
21
+ console.log(`Local IP: ${internalIP}, Gateway IP: ${gatewayIP}`);
17
22
  let gateway = await discoverGateway(internalIP);
18
23
  let controlURLs = await getControlPaths(gateway);
19
24
  let controlPort = Number(new URL(gateway).port);
@@ -26,30 +31,72 @@ export async function forwardPort(config: {
26
31
  controlPort,
27
32
  controlPath: controlURL,
28
33
  internalIP,
34
+ duration,
29
35
  });
36
+ console.log(`Port mapping created on ${gatewayIP}:${externalPort} -> ${internalIP}:${internalPort}`);
30
37
  return;
31
38
  } catch (e) {
32
- console.error(e);
39
+ console.error(`Failed to create port mapping using controlURL ${controlURL}`, e);
33
40
  }
34
41
  }
35
42
  console.error("Failed to create port mapping, could not find controlURL");
36
43
  }
37
44
 
38
45
  function getLocalInterfaceAddress(): { internalIP: string; gatewayIP: string; } | undefined {
39
- const interfaces = os.networkInterfaces() as any;
40
- for (const name of Object.keys(interfaces)) {
41
- for (const iface of interfaces[name]) {
42
- if (iface.family === "IPv4" && !iface.internal) {
43
- // TOOD: Correctly resolve the cidr?
44
- let gatewayIP = iface.cidr.split(".").slice(0, 3).join(".") + ".1";
45
- // TOOD: We try discovery on all gateways, so we can know for sure which one it is
46
- // (and maybe even port forward all gateway, if multiple respond?)
47
- if (gatewayIP.startsWith("10.0.0") || gatewayIP.startsWith("10.0.1") || gatewayIP.startsWith("192.168.0")) {
48
- return { internalIP: iface.address, gatewayIP };
46
+ let looksLikeRouter = (ip: string) => ip.startsWith("10.0.0") || ip.startsWith("10.0.1") || ip.startsWith("192.168.0");
47
+
48
+ // On windows, run `ipconfig` and parse the output
49
+ // Otherwise, ifconfig
50
+ if (os.platform() === "win32") {
51
+ let output = require("child_process").execSync("ipconfig").toString();
52
+ let sections = output.split("\r\n\r\n");
53
+
54
+ for (let section of sections) {
55
+ if (section.includes("IPv4 Address")) {
56
+ let ipv4Match = section.match(/IPv4 Address[.\s]*: ([\d.]+)/);
57
+ let gatewayMatch = section.match(/Default Gateway[.\s]*: ([\d.]+)/);
58
+
59
+ if (ipv4Match && gatewayMatch && looksLikeRouter(gatewayMatch[1])) {
60
+ return {
61
+ internalIP: ipv4Match[1],
62
+ gatewayIP: gatewayMatch[1]
63
+ };
49
64
  }
50
65
  }
51
66
  }
67
+ } else {
68
+ // For Unix-like systems (Linux, macOS)
69
+ // Try to get gateway from route command first
70
+ let routeOutput = require("child_process").execSync("route -n").toString();
71
+ let gatewayMatch = routeOutput.match(/0\.0\.0\.0\s+(\d+\.\d+\.\d+\.\d+)/);
72
+
73
+ if (!gatewayMatch) {
74
+ // Fallback for macOS
75
+ routeOutput = require("child_process").execSync("netstat -nr").toString();
76
+ gatewayMatch = routeOutput.match(/default\s+(\d+\.\d+\.\d+\.\d+)/);
77
+ }
78
+
79
+ if (gatewayMatch && looksLikeRouter(gatewayMatch[1])) {
80
+ // Now get the internal IP from ifconfig/ip addr
81
+ let ipCommand = os.platform() === "darwin" ? "ifconfig" : "ip addr";
82
+ let ipOutput = require("child_process").execSync(ipCommand).toString();
83
+
84
+ let ipMatch;
85
+ if (os.platform() === "darwin") {
86
+ ipMatch = ipOutput.match(/inet ((?!127\.0\.0\.1)\d+\.\d+\.\d+\.\d+)/);
87
+ } else {
88
+ ipMatch = ipOutput.match(/inet ((?!127\.0\.0\.1)\d+\.\d+\.\d+\.\d+)\/\d+/);
89
+ }
90
+
91
+ if (ipMatch) {
92
+ return {
93
+ internalIP: ipMatch[1],
94
+ gatewayIP: gatewayMatch[1]
95
+ };
96
+ }
97
+ }
52
98
  }
99
+
53
100
  return undefined;
54
101
  }
55
102
 
@@ -117,7 +164,7 @@ async function createPortMapping(config: {
117
164
  controlPort: number;
118
165
  controlPath: string;
119
166
  internalIP: string;
120
-
167
+ duration: number;
121
168
  }): Promise<void> {
122
169
  const { externalPort, internalPort, internalIP, controlPath, controlPort, gatewayIP } = config;
123
170
  const action = "\"urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping\"";
@@ -134,7 +181,7 @@ async function createPortMapping(config: {
134
181
  <NewInternalClient>${internalIP}</NewInternalClient>
135
182
  <NewEnabled>1</NewEnabled>
136
183
  <NewPortMappingDescription>My Port Mapping</NewPortMappingDescription>
137
- <NewLeaseDuration>0</NewLeaseDuration>
184
+ <NewLeaseDuration>${Math.ceil(config.duration / 1000)}</NewLeaseDuration>
138
185
  </u:AddPortMapping>
139
186
  </s:Body>
140
187
  </s:Envelope>
package/src/networking.ts CHANGED
@@ -33,7 +33,7 @@ const ipServers = [
33
33
  export const getExternalIP = lazy(measureWrap(async function getExternalIP(): Promise<string> {
34
34
  for (let server of ipServers) {
35
35
  try {
36
- return (await httpsRequest(server)).toString();
36
+ return (await httpsRequest(server, undefined, undefined, false)).toString();
37
37
  } catch (e) {
38
38
  console.warn(`Failed to get external ip from ${server}: ${e}`);
39
39
  }
@@ -11,7 +11,7 @@ import { parseSNIExtension, parseTLSHello, SNIType } from "./tlsParsing";
11
11
  import debugbreak from "debugbreak";
12
12
  import { getNodeId } from "./nodeCache";
13
13
  import crypto from "crypto";
14
- import { Watchable, timeInHour } from "./misc";
14
+ import { Watchable, timeInHour, timeInMinute } from "./misc";
15
15
  import { delay, runInfinitePoll, runInfinitePollCallAtStart } from "./batching";
16
16
  import { magenta, red } from "./formatting/logColors";
17
17
  import { yellow } from "./formatting/logColors";
@@ -35,7 +35,8 @@ export type SocketServerConfig = (
35
35
  /** Tries forwarding ports (using UPnP), if we detect they aren't externally reachable.
36
36
  * - This causes an extra request and delay during startup, so should only be used
37
37
  * during development.
38
- * - Ignored if public is false
38
+ * - Ignored if public is false (in which case we mount on 127.0.0.1, so port forwarding
39
+ * wouldn't matter anyways).
39
40
  */
40
41
  autoForwardPort?: boolean;
41
42
  ip?: string;
@@ -180,6 +181,7 @@ export async function startSocketServer(
180
181
  });
181
182
 
182
183
  let realServer = net.createServer(socket => {
184
+ //console.log("Received TCP connection from " + socket.remoteAddress);
183
185
  const remoteAddress = socket.remoteAddress;
184
186
  function handleTLSHello(buffer: Buffer, packetCount: number): void | "more" {
185
187
  // All HTTPS requests start with 22, and no HTTP requests start with 22,
@@ -296,7 +298,7 @@ export async function startSocketServer(
296
298
  console.log(magenta(`Forwarded port ${port} to our machine`));
297
299
  }
298
300
  // Every hour, in case our network configuration changes
299
- runInfinitePollCallAtStart(timeInHour * 1, forward).catch(e => console.error(red(`Error in port forwarding ${e.stack}`)));
301
+ runInfinitePollCallAtStart(timeInMinute * 30, forward).catch(e => console.error(red(`Error in port forwarding ${e.stack}`)));
300
302
  }
301
303
 
302
304
  let nodeId = getNodeId(getCommonName(config.cert), port);
@@ -200,10 +200,15 @@ const TimeController = SocketFunction.register(
200
200
  "TimeController-ddf4753e-fc8a-413f-8cc2-b927dd449976",
201
201
  new TimeControllerBase(),
202
202
  () => ({
203
- getTrueTime: {},
204
- }),
205
- () => ({
203
+ getTrueTime: {
204
+ // No hooks, as this needs to run very early on. Also, it is basically just a ping,
205
+ // so it should be safe for anyone to use (we might even make it just a regular HTTPS endpoint,
206
+ // or even just set up a dedicated domain for this).
207
+ noDefaultHooks: true,
208
+ noClientHooks: true,
209
+ },
206
210
  }),
211
+ () => ({}),
207
212
  {
208
213
  // NOTE: Autoexpose, because our exposed endpoints are incredibly lightweight
209
214
  // (just a ping), and don't expose really expose any data.