Skip to content

Commit

Permalink
isssue #1303 refactor: Add 'host' parameter to OPCUAServer for specif…
Browse files Browse the repository at this point in the history
…ic interface binding
  • Loading branch information
iliareshetov committed Nov 16, 2023
1 parent 9e5a34a commit 73a423a
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
15 changes: 11 additions & 4 deletions packages/node-opcua-server/source/opcua_server.ts
Expand Up @@ -719,7 +719,14 @@ export interface OPCUAServerEndpointOptions {
* @default getFullyQualifiedDomainName()
*/
hostname?: string;

/**
* Host IP address or hostname where the TCP server listens for connections.
* If omitted, defaults to listening on all network interfaces:
* - Unspecified IPv6 address (::) if IPv6 is available,
* - Unspecified IPv4 address (0.0.0.0) otherwise.
* Use this to bind the server to a specific interface or IP.
*/
host?: string;
/**
* the TCP port to listen to.
* @default 26543
Expand Down Expand Up @@ -1163,7 +1170,7 @@ export class OPCUAServer extends OPCUABaseServer {

endpointDefinitions.push({
port: options.port === undefined ? 26543 : options.port,

host: options.host,
allowAnonymous: options.allowAnonymous,
alternateHostname: options.alternateHostname,
disableDiscovery: options.disableDiscovery,
Expand Down Expand Up @@ -3498,12 +3505,12 @@ export class OPCUAServer extends OPCUABaseServer {

private createEndpoint(
port1: number,
serverOptions: { defaultSecureTokenLifetime?: number; timeout?: number }
serverOptions: { defaultSecureTokenLifetime?: number; timeout?: number; host?:string }
): OPCUAServerEndPoint {
// add the tcp/ip endpoint with no security
const endPoint = new OPCUAServerEndPoint({
port: port1,

host: serverOptions.host,
certificateManager: this.serverCertificateManager,

certificateChain: this.getCertificateChain(),
Expand Down
16 changes: 13 additions & 3 deletions packages/node-opcua-server/source/server_end_point.ts
Expand Up @@ -120,6 +120,10 @@ export interface OPCUAServerEndPointOptions {
* the tcp port
*/
port: number;
/**
* the tcp host
*/
host?: string;
/**
* the DER certificate chain
*/
Expand Down Expand Up @@ -207,6 +211,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
* the tcp port
*/
public port: number;
public host: string | undefined;
public certificateManager: OPCUACertificateManager;
public defaultSecureTokenLifetime: number;
public maxConnections: number;
Expand Down Expand Up @@ -244,6 +249,7 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
options.port = options.port || 0;

this.port = parseInt(options.port.toString(), 10);
this.host = options.host;
assert(typeof this.port === "number");

this._certificateChain = options.certificateChain;
Expand Down Expand Up @@ -294,9 +300,8 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
}

public toString(): string {
const privateKeyThumpPrint = makePrivateKeyThumbPrint(this.getPrivateKey());

const privateKeyThumpPrint = makePrivateKeyThumbPrint(this.getPrivateKey())

const txt =
" end point" +
this._counter +
Expand Down Expand Up @@ -510,8 +515,13 @@ export class OPCUAServerEndPoint extends EventEmitter implements ServerSecureCha
debugLog("server is listening");
});

const listenOptions: net.ListenOptions = {
port: this.port,
host: this.host
};

this._server!.listen(
this.port,
listenOptions,
/*"::",*/ (err?: Error) => {
// 'listening' listener
debugLog(chalk.green.bold("LISTENING TO PORT "), this.port, "err ", err);
Expand Down
87 changes: 87 additions & 0 deletions packages/node-opcua-server/test/test_server_issue_1303.ts
@@ -0,0 +1,87 @@
import net from "net";
import os from "os";
import should from "should";
import { OPCUAServer } from "..";

async function findAvailablePort(): Promise<number> {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(0);
server.on("listening", function () {
const port = (server.address() as net.AddressInfo).port;
console.log(`INFO: Found available port ${port}`);
server.close(() => resolve(port));
});
});
}

function findLanIp(): string | undefined {
const networkInterfaces = os.networkInterfaces();

for (const iface of Object.values(networkInterfaces)) {
if (!iface) {
continue;
}

for (const alias of iface) {
if (alias.family === "IPv4" && !alias.internal) {
return alias.address;
}
}
}

return undefined;
}

function checkServer(host: string | undefined, port: number): Promise<boolean> {
return new Promise((resolve) => {
const socket = net.createConnection(port, host, () => {
socket.end();
resolve(true);
});

socket.on("error", () => {
resolve(false);
});
});
}

async function testServerStartupAndShutdown(host: string | undefined, port: number) {
try {
const serverOptions = host === undefined ? { port } : { host, port };
console.log("INFO: running with serverOptions: ", serverOptions);

const server = new OPCUAServer(serverOptions);
await server.start();

const isServerListening = await checkServer(host, port);
isServerListening.should.be.true;

await server.shutdown();
server.dispose();
} catch (err) {
should.fail(err, undefined, `Error in server startup/shutdown: ${err.message}`);
}
}

describe("OPCUAServer - issue#1303", () => {
it("should start a net.Server on loopback address", async () => {
const port = await findAvailablePort();
const host = "localhost";
await testServerStartupAndShutdown(host, port);
});

it("should start a net.Server on LAN IP", async () => {
const port = await findAvailablePort();
const host = findLanIp();
console.log(host ? `INFO: LAN IP found: ${host}` : "WARNING: No LAN IP available, skiping test...");
if (host) {
await testServerStartupAndShutdown(host, port);
}
});

it("should start a net.Server on default host (ensures backward compatibility)", async () => {
const port = await findAvailablePort();
await testServerStartupAndShutdown(undefined, port);
});
});

0 comments on commit 73a423a

Please sign in to comment.