1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

bind to correct port when doing udp broadcast/multicast (#1686)

* bind to correct port when doing broadcast/multicast

to allow better re-use of ports.

* allow udp multicast to work out if ip address

makes life easier for mortals

* udp also handle bind to ipv6 multicast if

tidy prompts to suit new function

* udp node, add face to debug log for multicast if known
This commit is contained in:
Dave Conway-Jones 2018-05-01 12:43:51 +01:00 committed by Nick O'Leary
parent e691351976
commit 94cb03f4b5
5 changed files with 94 additions and 41 deletions

View File

@ -29,7 +29,7 @@
</div> </div>
<div class="form-row node-input-iface"> <div class="form-row node-input-iface">
<label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label> <label for="node-input-iface"><i class="fa fa-random"></i> <span data-i18n="udp.label.interface"></span></label>
<input type="text" id="node-input-iface" data-i18n="[placeholder]udp.label.interfaceprompt"> <input type="text" id="node-input-iface" data-i18n="[placeholder]udp.placeholder.interfaceprompt">
</div> </div>
<div class="form-row"> <div class="form-row">
<label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label> <label for="node-input-port"><i class="fa fa-sign-in"></i> <span data-i18n="udp.label.onport"></span></label>

View File

@ -16,6 +16,7 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var os = require('os');
var dgram = require('dgram'); var dgram = require('dgram');
var udpInputPortsInUse = {}; var udpInputPortsInUse = {};
@ -30,6 +31,29 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4"; this.ipv = n.ipv || "udp4";
var node = this; var node = this;
if (node.iface && node.iface.indexOf(".") === -1) {
try {
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][1].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][0].address;
}
}
else {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][0].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][1].address;
}
}
}
catch(e) {
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
node.iface = null;
}
}
var opts = {type:node.ipv, reuseAddr:true}; var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var server; var server;
@ -39,7 +63,7 @@ module.exports = function(RED) {
udpInputPortsInUse[this.port] = server; udpInputPortsInUse[this.port] = server;
} }
else { else {
node.warn(RED._("udp.errors.alreadyused",node.port)); node.warn(RED._("udp.errors.alreadyused",{port:node.port}));
server = udpInputPortsInUse[this.port]; // re-use existing server = udpInputPortsInUse[this.port]; // re-use existing
} }
@ -121,12 +145,38 @@ module.exports = function(RED) {
this.ipv = n.ipv || "udp4"; this.ipv = n.ipv || "udp4";
var node = this; var node = this;
if (node.iface && node.iface.indexOf(".") === -1) {
try {
if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][1].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][0].address;
}
}
else {
if (node.ipv === "udp4") {
node.iface = (os.networkInterfaces())[node.iface][0].address;
} else {
node.iface = (os.networkInterfaces())[node.iface][1].address;
}
}
}
catch(e) {
node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface}));
node.iface = null;
}
}
var opts = {type:node.ipv, reuseAddr:true}; var opts = {type:node.ipv, reuseAddr:true};
if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; }
var sock; var sock;
if (udpInputPortsInUse[this.outport || this.port]) { var p = this.port;
sock = udpInputPortsInUse[this.outport || this.port]; if (node.multicast != "false") { p = this.outport||"0"; }
if (udpInputPortsInUse[p]) {
sock = udpInputPortsInUse[p];
node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port}));
} }
else { else {
sock = dgram.createSocket(opts); // default to udp4 sock = dgram.createSocket(opts); // default to udp4
@ -136,36 +186,35 @@ module.exports = function(RED) {
// prevent it going to the global error handler and shutting node-red // prevent it going to the global error handler and shutting node-red
// down. // down.
}); });
udpInputPortsInUse[this.outport || this.port] = sock; udpInputPortsInUse[p] = sock;
}
if (node.multicast != "false") { if (node.multicast != "false") {
if (node.outport === "") { node.outport = node.port; } sock.bind(node.outport, function() { // have to bind before you can enable broadcast...
sock.bind(node.outport, function() { // have to bind before you can enable broadcast... sock.setBroadcast(true); // turn on broadcast
sock.setBroadcast(true); // turn on broadcast if (node.multicast == "multi") {
if (node.multicast == "multi") { try {
try { sock.setMulticastTTL(128);
sock.setMulticastTTL(128); sock.addMembership(node.addr,node.iface); // Add to the multicast group
sock.addMembership(node.addr,node.iface); // Add to the multicast group node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port}));
node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port})); } catch (e) {
} catch (e) { if (e.errno == "EINVAL") {
if (e.errno == "EINVAL") { node.error(RED._("udp.errors.bad-mcaddress"));
node.error(RED._("udp.errors.bad-mcaddress")); } else if (e.errno == "ENODEV") {
} else if (e.errno == "ENODEV") { node.error(RED._("udp.errors.interface"));
node.error(RED._("udp.errors.interface")); } else {
} else { node.error(RED._("udp.errors.error",{error:e.errno}));
node.error(RED._("udp.errors.error",{error:e.errno})); }
} }
} else {
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port}));
} }
} else { });
node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port})); } else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) {
} sock.bind(node.outport);
}); node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port}));
} else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) { } else {
sock.bind(node.outport); node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port})); }
} else {
node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port}));
} }
node.on("input", function(msg) { node.on("input", function(msg) {
@ -198,8 +247,8 @@ module.exports = function(RED) {
}); });
node.on("close", function() { node.on("close", function() {
if (udpInputPortsInUse.hasOwnProperty(node.outport || node.port)) { if (udpInputPortsInUse.hasOwnProperty(p)) {
delete udpInputPortsInUse[node.outport || node.port]; delete udpInputPortsInUse[p];
} }
try { try {
sock.close(); sock.close();

View File

@ -495,15 +495,15 @@
"using": "using", "using": "using",
"output": "Output", "output": "Output",
"group": "Group", "group": "Group",
"interface": "Local IP", "interface": "Local IF",
"interfaceprompt": "(optional) local ip address to bind to",
"send": "Send a", "send": "Send a",
"toport": "to port", "toport": "to port",
"address": "Address", "address": "Address",
"decode-base64": "Decode Base64 encoded payload?" "decode-base64": "Decode Base64 encoded payload?"
}, },
"placeholder": { "placeholder": {
"interface": "(optional) ip address of eth0", "interface": "(optional) local interface or address to bind to",
"interfaceprompt": "(optional) local interface or address to bind to",
"address": "destination ip" "address": "destination ip"
}, },
"udpmsgs": "udp messages", "udpmsgs": "udp messages",
@ -531,10 +531,11 @@
"mc-group": "udp multicast group __group__", "mc-group": "udp multicast group __group__",
"listener-stopped": "udp listener stopped", "listener-stopped": "udp listener stopped",
"output-stopped": "udp output stopped", "output-stopped": "udp output stopped",
"mc-ready": "udp multicast ready: __outport__ -> __host__:__port__", "mc-ready": "udp multicast ready: __iface__:__outport__ -> __host__:__port__",
"bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__", "bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__",
"ready": "udp ready: __outport__ -> __host__:__port__", "ready": "udp ready: __outport__ -> __host__:__port__",
"ready-nolocal": "udp ready: __host__:__port__" "ready-nolocal": "udp ready: __host__:__port__",
"re-use": "udp re-use socket: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP access error, you may need root access for ports below 1024", "access-error": "UDP access error, you may need root access for ports below 1024",
@ -544,7 +545,8 @@
"ip-notset": "udp: ip address not set", "ip-notset": "udp: ip address not set",
"port-notset": "udp: port not set", "port-notset": "udp: port not set",
"port-invalid": "udp: port number not valid", "port-invalid": "udp: port number not valid",
"alreadyused": "udp: port already in use" "alreadyused": "udp: port __port__ already in use",
"ifnotfound": "udp: interface __iface__ not found"
} }
}, },
"switch": { "switch": {

View File

@ -526,7 +526,8 @@
"mc-ready": "udpードはマルチキャストの準備ができています: __outport__ -> __host__:__port__", "mc-ready": "udpードはマルチキャストの準備ができています: __outport__ -> __host__:__port__",
"bc-ready": "udpードはブロードキャストの準備ができています: __outport__ -> __host__:__port__", "bc-ready": "udpードはブロードキャストの準備ができています: __outport__ -> __host__:__port__",
"ready": "udpードは準備ができています: __outport__ -> __host__:__port__", "ready": "udpードは準備ができています: __outport__ -> __host__:__port__",
"ready-nolocal": "udpードは準備ができています: __host__:__port__" "ready-nolocal": "udpードは準備ができています: __host__:__port__",
"re-use": "udp再利用ソケット: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります", "access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります",

View File

@ -525,7 +525,8 @@
"mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__", "mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__",
"bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__", "bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__",
"ready": "udp 已准备好: __outport__ -> __host__:__port__", "ready": "udp 已准备好: __outport__ -> __host__:__port__",
"ready-nolocal": "udp 已准备好: __host__:__port__" "ready-nolocal": "udp 已准备好: __host__:__port__",
"re-use": "udp 重用套接字: __outport__ -> __host__:__port__"
}, },
"errors": { "errors": {
"access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口", "access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",