Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added HAProxy support #3

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ let result = await mc.lookup({
timeout: 10000,
throwOnParseError: true,
SRVLookup: true,
JSONParse: true
JSONParse: true,
HAProxy: false
})

console.log(result);
Expand Down Expand Up @@ -89,6 +90,8 @@ console.log(result);
> Whether to perform a SRV lookup on the provided hostname. Set to `true` in order to skip. Useful to disable when you're only looking up the basic DNS records and a server with a specific port.
* <b>`JSONParse?:`</b> boolean <i>`default: true`</i>
> Whether to parse the JSON `status` field. Useful to disable when you only need the raw plaintext response. If false, the `status` field will be null.
* <b>`HAProxy?:`</b> boolean <i>`default: false`</i>
> Whether to send a HAProxy protocol header to the server. Useful when the server is behind a HAProxy load balancer. If true, the `status` field will be null.
* ServerStatus
* <b>`latency?:`</b> number
> The time it takes to receive back a response after sending a small payload to a server, in milliseconds. Will be null if the `ping` option is false.
Expand Down
16 changes: 13 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import { promises as dns } from "node:dns";
* protocolVersion: 767
* throwOnParseError: false,
* SRVLookup: true,
* JSONParse: true
* JSONParse: true,
* HAProxy: false
* })
* ```
*/
Expand All @@ -38,14 +39,23 @@ export async function lookup(options?: ServerStatusOptions): Promise<ServerStatu
let throwOnParseError = options.throwOnParseError != null ? options.throwOnParseError : true;
let SRVLookup = options.SRVLookup != null ? options.SRVLookup : true;
let JSONParse = options.JSONParse != null ? options.JSONParse : true;
let HAProxy = options.HAProxy != null ? options.HAProxy : false;
if (SRVLookup) ({ hostname, port } = await processSRV(hostname, port))
// Default port of 25565, default timeout of 10 seconds.
// Ping is sent by default.

let portal = net.createConnection({ port: port, host: hostname, lookup: customLookup }, async () => {
// Send first the handshake, and then the status request to the server.
let handshake = await packetGen.craftHandshake(hostname, port, protocolVersion);
// 1. Send HAProxy message if enabled
// 2. Send handshake
// 3. Send status request

if (HAProxy) {
// I don't know what other data I could send, so just send local IP and 0
let HAProxyMessage = packetGen.craftHAProxyMessagePacket("127.0.0.1", '127.0.0.1', 0, port);
portal.write(HAProxyMessage);
}

let handshake = await packetGen.craftHandshake(hostname, port, protocolVersion);
let statusRequest = await packetGen.craftEmptyPacket(0);

portal.write(handshake);
Expand Down
36 changes: 35 additions & 1 deletion src/packetGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ async function craftPingPacket() {
return craftedPacket;
}

export function craftHAProxyMessagePacket(
sourceIP: string,
destIP: string,
sourcePort: number,
destPort: number
): Buffer {
const signature = Buffer.from('0D0A0D0A000D0A515549540A', 'hex'); // HAProxy v2 signature
const versionCommand = 0x21; // Version 2, command PROXY
const protocol = 0x11; // TCP over IPv4

// Convert IP addresses and ports to binary format
const sourceIPParts = sourceIP.split('.').map(part => parseInt(part, 10));
const destIPParts = destIP.split('.').map(part => parseInt(part, 10));

const sourcePortBuffer = Buffer.alloc(2);
sourcePortBuffer.writeUInt16BE(sourcePort);

const destPortBuffer = Buffer.alloc(2);
destPortBuffer.writeUInt16BE(destPort);

const addresses = Buffer.concat([
Buffer.from(sourceIPParts),
Buffer.from(destIPParts),
sourcePortBuffer,
destPortBuffer
]);

const length = addresses.length;
const lengthBuffer = Buffer.alloc(2);
lengthBuffer.writeUInt16BE(length);

return Buffer.concat([signature, Buffer.from([versionCommand, protocol]), lengthBuffer, addresses]);
}



export default { craftHandshake, craftEmptyPacket, craftPingPacket }
export default { craftHandshake, craftEmptyPacket, craftPingPacket, craftHAProxyMessagePacket }
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ interface _ServerStatusOptions {
throwOnParseError?: boolean,
SRVLookup?: boolean,
JSONParse?: boolean,
HAProxy?: boolean
}

type NotBoth =
Expand Down