Skip to content

Instantly share code, notes, and snippets.

@andris9
Created July 3, 2020 11:19
Show Gist options
  • Save andris9/abc94332be671d7bbbc3a2f617a810db to your computer and use it in GitHub Desktop.
Save andris9/abc94332be671d7bbbc3a2f617a810db to your computer and use it in GitHub Desktop.
dns2 udp/tcp servers
// public domain code
// $ npm install dns2
// $ node app.js
// $ dig A example.com @127.0.0.1 -p5053
// $ dig A example.com +tcp @127.0.0.1 -p5053
const dns = require("dns2");
const { createDNSUdpServer, createDNSTcpServer } = require("./dns-server.js");
const host = "127.0.0.1";
const port = 5053;
// DNS request handler both for UDP and TCP
const requestHandler = function (request, send) {
const response = new dns.Packet(request);
response.header.qr = 1;
response.answers.push({
name: "example.com",
address: "8.8.8.8",
type: dns.Packet.TYPE.A,
class: dns.Packet.CLASS.IN,
});
response.additionals = [];
send(response);
};
// Setup UDP server
createDNSUdpServer(requestHandler)
.listen(port, host, () => {
console.log({ msg: "DNS server listening", protocol: "udp", host, port });
})
.on("error", (err) => {
console.error({ msg: "DNS server error", protocol: "udp", err });
});
// Setup TCP server
createDNSTcpServer(requestHandler)
.listen(port, host, () => {
console.log({ msg: "DNS server listening", protocol: "tcp", host, port });
})
.on("error", (err) => {
console.error({ msg: "DNS server error", protocol: "tcp", err });
});
// public domain code
"use strict";
const udp = require("dgram");
const net = require("net");
const EventEmitter = require("events");
const Packet = require("dns2/packet");
class DNSUdpServer extends EventEmitter {
constructor(options, callback) {
super();
if (typeof options === "function") {
callback = options;
options = {};
this.on("request", callback);
}
this.socket = udp.createSocket("udp4");
this.socket.on("message", this.parse.bind(this));
}
parse(buffer, rinfo) {
let request;
try {
request = Packet.parse(buffer);
} catch (err) {
console.error({
msg: "Failed to parse DNS package",
type: "udp",
err,
buffer,
port: rinfo.port,
address: rinfo.address,
});
}
request.source = {
type: "udp",
port: rinfo.port,
address: rinfo.address,
};
this.emit("request", request, this.send.bind(this, rinfo), rinfo);
}
send(rinfo, message) {
if (message instanceof Packet) message = message.toBuffer();
return new Promise((resolve, reject) => {
this.socket.send(message, rinfo.port, rinfo.address, (err) => {
if (err) return reject(err);
resolve(message);
});
});
}
listen(port, address, callback) {
this.socket.bind(port, address, callback);
return this;
}
close() {
this.socket.close();
this.socket = null;
return this;
}
}
class DNSTcpServer extends EventEmitter {
constructor(options, callback) {
super();
if (typeof options === "function") {
callback = options;
options = {};
this.on("request", callback);
}
this.server = net.createServer((socket) => {
let chunks = [];
let chunklen = 0;
let received = false;
let expected = false;
socket.setTimeout(10 * 1000);
socket.on("timeout", () => {
try {
socket.end();
} catch (err) {
// ignore
}
});
let processMessage = () => {
if (received) {
return;
}
received = true;
let buffer = Buffer.concat(chunks, chunklen);
let request;
try {
request = Packet.parse(buffer.slice(2));
} catch (err) {
console.error({
msg: "Failed to parse DNS package",
type: "tcp",
err,
buffer,
port: socket.remotePort,
address: socket.remoteAddress,
});
try {
socket.end();
} catch (err) {
// ignore
}
}
request.source = {
type: "tcp",
port: socket.remotePort,
address: socket.remoteAddress,
};
this.emit("request", request, (message) => {
return this.send(socket, message);
});
};
socket.on("readable", () => {
let chunk;
while ((chunk = socket.read()) !== null) {
chunks.push(chunk);
chunklen += chunk.length;
}
if (!expected && chunklen >= 2) {
if (chunks.length > 1) {
chunks = [Buffer.concat(chunks, chunklen)];
}
expected = chunks[0].readUInt16BE(0);
}
if (chunklen >= 2 + expected) {
processMessage();
}
});
socket.on("error", (err) => {
this.emit("error", err);
});
socket.on("end", () => {
processMessage();
});
});
this.server.on("error", (err) => {
this.emit("error", err);
});
}
send(socket, message) {
if (message instanceof Packet) message = message.toBuffer();
return new Promise((resolve) => {
try {
let len = Buffer.alloc(2);
len.writeUInt16BE(message.length);
socket.end(Buffer.concat([len, message]));
} catch (err) {
// ignore
}
resolve(message);
});
}
listen(port, address, callback) {
this.server.listen(port, address, callback);
return this;
}
close(cb) {
this.server.close(cb);
return this;
}
}
module.exports = {
createDNSUdpServer(options) {
return new DNSUdpServer(options);
},
createDNSTcpServer(options) {
return new DNSTcpServer(options);
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment