Add support for udp bind port override (3.0.5) Fixes #149

This commit is contained in:
Michael Morrison 2021-05-18 23:13:18 -05:00
parent 68b8dfd684
commit ce4e728493
8 changed files with 59 additions and 26 deletions

View file

@ -1,3 +1,7 @@
### 3.0.5
* Add support for `listenUdpPort` to specify a fixed bind port.
* Improved udp bind failure detection.
### 3.0.4 ### 3.0.4
* Add support for Discord widget * Add support for Discord widget

View file

@ -467,25 +467,41 @@ Valheim servers will only respond to queries if they are started in public mode
For many valve games, additional 'rules' may be fetched into the unstable `raw` field by passing the additional For many valve games, additional 'rules' may be fetched into the unstable `raw` field by passing the additional
option: `requestRules: true`. Beware that this may increase query time. option: `requestRules: true`. Beware that this may increase query time.
Important note about Firewalls (replit / docker / some VPS providers) Common Issues
--- ---
### Firewalls block incoming UDP
*(replit / docker / some VPS providers)*
Most game query protocols require a UDP request and response. This means that in some environments, gamedig may not be able to receive the reponse required due to environmental restrictions. Most game query protocols require a UDP request and response. This means that in some environments, gamedig may not be able to receive the reponse required due to environmental restrictions.
Some examples include: Some examples include:
* Docker containers * Docker containers
* You may need to run the container in `--network host` mode so that gamedig can bind a UDP listen port * You may need to run the container in `--network host` mode so that gamedig can bind a UDP listen port.
* Alternatively, you can forward a single UDP port to your container, and force gamedig to listen on that port using the
instructions in the section down below.
* replit * replit
* Most online IDEs run in an isolated container, which will never receive UDP responses from outside networks. * Most online IDEs run in an isolated container, which will never receive UDP responses from outside networks.
* Various VPS / server providers * Various VPS / server providers
* Even if your server provider doesn't explicitly block incoming UDP packets, some server hosts block other server hosts from connecting to them for DDOS-mitigation and anti-botting purposes. * Even if your server provider doesn't explicitly block incoming UDP packets, some server hosts block other server hosts from connecting to them for DDOS-mitigation and anti-botting purposes.
Important note about gamedig in the browser ### Gamedig doesn't work in the browser
---
Gamedig cannot operate within a browser. This means you cannot package it as part of your webpack / browserify / rollup / parcel package. Gamedig cannot operate within a browser. This means you cannot package it as part of your webpack / browserify / rollup / parcel package.
Even if you were able to get it packaged into a bundle, unfortunately no browsers support the UDP protocols required to query server status Even if you were able to get it packaged into a bundle, unfortunately no browsers support the UDP protocols required to query server status
from most game servers. As an alternative, we'd recommend using gamedig on your server-side, then expose your own API to your webapp's frontend from most game servers. As an alternative, we'd recommend using gamedig on your server-side, then expose your own API to your webapp's frontend
displaying the status information. If your application is thin (with no constant server component), you may wish to investigate a server-less lambda provider. displaying the status information. If your application is thin (with no constant server component), you may wish to investigate a server-less lambda provider.
### Specifying a listen UDP port override
In some very rare scenarios, you may need to bind / listen on a fixed local UDP port. The is usually not needed except behind
some extremely strict firewalls, or within a docker container (where you only wish to forward a single UDP port).
To use a fixed listen udp port, construct a new Gamedig object like this:
```
const gamedig = new Gamedig({
listenUdpPort: 13337
});
gamedig.query(...)
```
Usage from Command Line Usage from Command Line
--- ---

View file

@ -5,7 +5,7 @@ const Minimist = require('minimist'),
const argv = Minimist(process.argv.slice(2), { const argv = Minimist(process.argv.slice(2), {
boolean: ['pretty','debug','givenPortOnly'], boolean: ['pretty','debug','givenPortOnly'],
string: ['guildId'] string: ['guildId','listenUdpPort']
}); });
const debug = argv.debug; const debug = argv.debug;
@ -41,7 +41,8 @@ if (givenPortOnly) {
options.givenPortOnly = true; options.givenPortOnly = true;
} }
Gamedig.query(options) const gamedig = new Gamedig(options);
gamedig.query(options)
.then((state) => { .then((state) => {
if(pretty) { if(pretty) {
console.log(JSON.stringify(state,null,' ')); console.log(JSON.stringify(state,null,' '));

View file

@ -1,25 +1,29 @@
const dgram = require('dgram'), const dgram = require('dgram');
HexUtil = require('./HexUtil'), const HexUtil = require('./HexUtil');
Logger = require('./Logger'); const Logger = require('./Logger');
const util = require('util');
class GlobalUdpSocket { class GlobalUdpSocket {
constructor() { constructor({port}) {
this.socket = null; this.socket = null;
this.callbacks = new Set(); this.callbacks = new Set();
this.debuggingCallbacks = new Set(); this.debuggingCallbacks = new Set();
this.logger = new Logger(); this.logger = new Logger();
this.port = port;
} }
_getSocket() { async _getSocket() {
if (!this.socket) { if (!this.socket) {
const udpSocket = this.socket = dgram.createSocket('udp4'); const udpSocket = dgram.createSocket({
type: 'udp4',
reuseAddr: true
});
udpSocket.unref(); udpSocket.unref();
udpSocket.bind();
udpSocket.on('message', (buffer, rinfo) => { udpSocket.on('message', (buffer, rinfo) => {
const fromAddress = rinfo.address; const fromAddress = rinfo.address;
const fromPort = rinfo.port; const fromPort = rinfo.port;
this.logger.debug(log => { this.logger.debug(log => {
log(fromAddress + ':' + fromPort + " <--UDP"); log(fromAddress + ':' + fromPort + " <--UDP(" + this.port + ")");
log(HexUtil.debugDump(buffer)); log(HexUtil.debugDump(buffer));
}); });
for (const cb of this.callbacks) { for (const cb of this.callbacks) {
@ -29,12 +33,22 @@ class GlobalUdpSocket {
udpSocket.on('error', e => { udpSocket.on('error', e => {
this.logger.debug("UDP ERROR:", e); this.logger.debug("UDP ERROR:", e);
}); });
await util.promisify(udpSocket.bind).bind(udpSocket)(this.port);
this.port = udpSocket.address().port;
this.socket = udpSocket;
} }
return this.socket; return this.socket;
} }
send(buffer, address, port) { async send(buffer, address, port, debug) {
this._getSocket().send(buffer,0,buffer.length,port,address); const socket = await this._getSocket();
if (debug) {
this.logger._print(log => {
log(address + ':' + port + " UDP(" + this.port + ")-->");
log(HexUtil.debugDump(buffer));
});
}
await util.promisify(socket.send).bind(socket)(buffer,0,buffer.length,port,address);
} }
addCallback(callback, debug) { addCallback(callback, debug) {

View file

@ -9,8 +9,10 @@ const defaultOptions = {
}; };
class QueryRunner { class QueryRunner {
constructor() { constructor(runnerOpts = {}) {
this.udpSocket = new GlobalUdpSocket(); this.udpSocket = new GlobalUdpSocket({
port: runnerOpts.listenUdpPort
});
this.gameResolver = new GameResolver(); this.gameResolver = new GameResolver();
this.protocolResolver = new ProtocolResolver(); this.protocolResolver = new ProtocolResolver();
} }

View file

@ -3,8 +3,8 @@ const QueryRunner = require('./QueryRunner');
let singleton = null; let singleton = null;
class Gamedig { class Gamedig {
constructor() { constructor(runnerOpts) {
this.queryRunner = new QueryRunner(); this.queryRunner = new QueryRunner(runnerOpts);
} }
async query(userOptions) { async query(userOptions) {

View file

@ -24,7 +24,7 @@
], ],
"main": "lib/index.js", "main": "lib/index.js",
"author": "GameDig Contributors", "author": "GameDig Contributors",
"version": "3.0.4", "version": "3.0.5",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/gamedig/node-gamedig.git" "url": "https://github.com/gamedig/node-gamedig.git"

View file

@ -252,13 +252,9 @@ class Core extends EventEmitter {
this.assertValidPort(port); this.assertValidPort(port);
if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary'); if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary');
this.debugLog(log => {
log(address+':'+port+" UDP-->");
log(HexUtil.debugDump(buffer));
});
const socket = this.udpSocket; const socket = this.udpSocket;
socket.send(buffer, address, port); await socket.send(buffer, address, port, this.options.debug);
if (!onPacket && !onTimeout) { if (!onPacket && !onTimeout) {
return null; return null;