Skip to content

Commit

Permalink
Support tcp socket
Browse files Browse the repository at this point in the history
  • Loading branch information
tsutsu3 committed Nov 28, 2024
1 parent ae43257 commit a03a85c
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 24 deletions.
10 changes: 5 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ abstract class UnboundControlClient {
private control: UnboundControl;

constructor(
unixSocketName: string | null,
unixSocketName?: string,
host?: string,
port?: number,
tlsConfig?: TlsConfig | null,
tlsConfig?: TlsConfig,
) {
if (unixSocketName) {
this.control = new UnboundControl(unixSocketName);
} else {
this.control = new UnboundControl(null, host, port, tlsConfig);
this.control = new UnboundControl(undefined, host, port, tlsConfig);
}
}

Expand Down Expand Up @@ -865,7 +865,7 @@ export class UnixUnboundClient extends UnboundControlClient {
}

export class TcpUnboundClient extends UnboundControlClient {
constructor(host: string, port: number, tlsConfig?: TlsConfig) {
super(null, host, port, tlsConfig);
constructor(host: string, port: number, tlsConfig: TlsConfig) {
super(undefined, host, port, tlsConfig);
}
}
52 changes: 35 additions & 17 deletions src/control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { TlsConfig } from "./types";
*/
export class UnboundControl {
/** The path to the Unix domain socket (if applicable). */
private readonly unixSocketName: string | null;
private readonly unixSocketName?: string;

/** The host address for TCP connections. */
private readonly host: string;
Expand All @@ -19,10 +19,10 @@ export class UnboundControl {
private readonly port: number;

/** Optional TLS configuration for secure connections. */
private readonly tlsConfig: TlsConfig | null = null;
private readonly tlsConfig?: TlsConfig;

/** The underlying network socket for communication. */
private socket: net.Socket | null = null;
private socket?: net.Socket | null;

/**
* Creates a new instance of the UnboundControl class.
Expand All @@ -33,10 +33,10 @@ export class UnboundControl {
* @param tlsConfig - Optional TLS configuration for secure connections.
*/
constructor(
unixSocketName: string | null = null,
unixSocketName?: string,
host: string = "localhost",
port: number = 8953,
tlsConfig: TlsConfig | null = null,
tlsConfig?: TlsConfig,
) {
this.unixSocketName = unixSocketName;
this.host = host;
Expand All @@ -61,22 +61,40 @@ export class UnboundControl {
return new Promise((resolve, reject) => {
let socket: net.Socket;

if (this.unixSocketName !== null) {
if (this.unixSocketName) {
socket = net.createConnection(this.unixSocketName);
} else {
if (this.tlsConfig) {
socket = tls.connect({
host: this.host,
port: this.port,
rejectUnauthorized: this.tlsConfig.ca ? true : false,
cert: fs.readFileSync(this.tlsConfig.cert),
key: fs.readFileSync(this.tlsConfig.key),
ca: this.tlsConfig.ca
? fs.readFileSync(this.tlsConfig.ca)
: undefined,
});
// Connect via TLS
socket = tls.connect(
{
host: this.host,
port: this.port,
rejectUnauthorized: !!this.tlsConfig.ca,
cert: fs.readFileSync(this.tlsConfig.cert),
key: fs.readFileSync(this.tlsConfig.key),
ca: this.tlsConfig.ca
? fs.readFileSync(this.tlsConfig.ca)
: undefined,
},
() => {
const tlsSocket = socket as tls.TLSSocket;
if (tlsSocket.authorized || !this.tlsConfig?.ca) {
resolve(tlsSocket);
} else {
reject(
new ConnectionError(
`TLS authorization failed: ${tlsSocket.authorizationError}`,
),
);
}
},
);
} else {
socket = net.createConnection(this.port, this.host);
// Connect via plain TCP
socket = net.createConnection(this.port, this.host, () => {
resolve(socket);
});
}
}

Expand Down
73 changes: 71 additions & 2 deletions tests/control.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { UnixMockServer, MockServer } from "./mockServer";
import { UnixUnboundClient } from "../src/index";
import fs from "fs";
import path from "path";
import YAML from "yaml";
import { UnixMockServer, TcpTlsMockServer, MockServer } from "./mockServer";
import { UnixUnboundClient, TcpUnboundClient } from "../src/index";
import { TlsConfig } from "../src/types";

const baseDir = path.resolve(__dirname);
const unboundVersion = process.env.UNBOUND_VERSION || "1.22.0";
Expand Down Expand Up @@ -84,3 +85,71 @@ describe(`Unix domain socket mock server tests. Unbound version: ${unboundVersio
}
}
});

describe(`TCP socket docker server tests. Unbound version: ${unboundVersion}`, () => {
let server: MockServer;
let client: TcpUnboundClient;
const keyPath = path.join(baseDir, "./key/unbound_control.key");
const certPath = path.join(baseDir, "./key/unbound_control.pem");
const tlsConfig: TlsConfig = {
cert: certPath,
key: keyPath,
};

const options = {
key: fs.readFileSync(path.join(baseDir, "./key/unbound_server.key")),
cert: fs.readFileSync(path.join(baseDir, "./key/unbound_server.pem")),
requestCert: false,
};

beforeAll(() => {
server = new TcpTlsMockServer("localhost", 8953, options);
client = new TcpUnboundClient("localhost", 8953, tlsConfig);
});

afterEach(async () => {
await server.stop();
});

const files = fs
.readdirSync(dataDir)
.filter((file) => file.endsWith(".yaml"));

for (const file of files) {
const command = file.replace(".yaml", "");
const fileContent = fs.readFileSync(path.join(dataDir, file), "utf-8");
const contents = YAML.parse(fileContent) as ParsedData;

for (const { title, options, raw, expected, exception } of contents.data) {
it(`${command} test: ${title}`, async () => {
server.start(raw);

const method = client[command as keyof UnixUnboundClient].bind(
client,
) as (...args: any[]) => Promise<Response>; // eslint-disable-line @typescript-eslint/no-explicit-any
if (typeof method !== "function") {
throw new Error(`Invalid command: ${command}`);
}

const args =
options !== undefined
? Array.isArray(options)
? options
: [options]
: [];

let result;

if (exception) {
await expect(method.apply(client, args)).rejects.toThrow(exception);
} else {
result = await method.apply(client, args);
expect(result.raw).toEqual(raw);
expect(result.json).toEqual(expected);
}

await client.disconnect();
});
}
}
});

0 comments on commit a03a85c

Please sign in to comment.