From 94cb03f4b53e59fc5967a9beb076b438988e5064 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 1 May 2018 12:43:51 +0100 Subject: [PATCH] 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 --- nodes/core/io/32-udp.html | 2 +- nodes/core/io/32-udp.js | 113 ++++++++++++++++++------- nodes/core/locales/en-US/messages.json | 14 +-- nodes/core/locales/ja/messages.json | 3 +- nodes/core/locales/zh-CN/messages.json | 3 +- 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/nodes/core/io/32-udp.html b/nodes/core/io/32-udp.html index 3711978c4..126f1da6f 100644 --- a/nodes/core/io/32-udp.html +++ b/nodes/core/io/32-udp.html @@ -29,7 +29,7 @@
- +
diff --git a/nodes/core/io/32-udp.js b/nodes/core/io/32-udp.js index fac5135cf..096de479c 100644 --- a/nodes/core/io/32-udp.js +++ b/nodes/core/io/32-udp.js @@ -16,6 +16,7 @@ module.exports = function(RED) { "use strict"; + var os = require('os'); var dgram = require('dgram'); var udpInputPortsInUse = {}; @@ -30,6 +31,29 @@ module.exports = function(RED) { this.ipv = n.ipv || "udp4"; 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}; if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } var server; @@ -39,7 +63,7 @@ module.exports = function(RED) { udpInputPortsInUse[this.port] = server; } 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 } @@ -121,12 +145,38 @@ module.exports = function(RED) { this.ipv = n.ipv || "udp4"; 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}; if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } var sock; - if (udpInputPortsInUse[this.outport || this.port]) { - sock = udpInputPortsInUse[this.outport || this.port]; + var p = 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 { 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 // down. }); - udpInputPortsInUse[this.outport || this.port] = sock; - } + udpInputPortsInUse[p] = sock; - 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.setBroadcast(true); // turn on broadcast - if (node.multicast == "multi") { - try { - sock.setMulticastTTL(128); - sock.addMembership(node.addr,node.iface); // Add to the multicast group - node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port})); - } catch (e) { - if (e.errno == "EINVAL") { - node.error(RED._("udp.errors.bad-mcaddress")); - } else if (e.errno == "ENODEV") { - node.error(RED._("udp.errors.interface")); - } else { - node.error(RED._("udp.errors.error",{error:e.errno})); + if (node.multicast != "false") { + sock.bind(node.outport, function() { // have to bind before you can enable broadcast... + sock.setBroadcast(true); // turn on broadcast + if (node.multicast == "multi") { + try { + sock.setMulticastTTL(128); + 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})); + } catch (e) { + if (e.errno == "EINVAL") { + node.error(RED._("udp.errors.bad-mcaddress")); + } else if (e.errno == "ENODEV") { + node.error(RED._("udp.errors.interface")); + } else { + 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 { - node.log(RED._("udp.status.ready-nolocal",{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 { + node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port})); + } } node.on("input", function(msg) { @@ -198,8 +247,8 @@ module.exports = function(RED) { }); node.on("close", function() { - if (udpInputPortsInUse.hasOwnProperty(node.outport || node.port)) { - delete udpInputPortsInUse[node.outport || node.port]; + if (udpInputPortsInUse.hasOwnProperty(p)) { + delete udpInputPortsInUse[p]; } try { sock.close(); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 9fc8f729b..62d2ea46a 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -495,15 +495,15 @@ "using": "using", "output": "Output", "group": "Group", - "interface": "Local IP", - "interfaceprompt": "(optional) local ip address to bind to", + "interface": "Local IF", "send": "Send a", "toport": "to port", "address": "Address", "decode-base64": "Decode Base64 encoded payload?" }, "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" }, "udpmsgs": "udp messages", @@ -531,10 +531,11 @@ "mc-group": "udp multicast group __group__", "listener-stopped": "udp listener 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__", "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": { "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", "port-notset": "udp: port not set", "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": { diff --git a/nodes/core/locales/ja/messages.json b/nodes/core/locales/ja/messages.json index 83b29565b..65a860ab1 100644 --- a/nodes/core/locales/ja/messages.json +++ b/nodes/core/locales/ja/messages.json @@ -526,7 +526,8 @@ "mc-ready": "udpノードはマルチキャストの準備ができています: __outport__ -> __host__:__port__", "bc-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": { "access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります", diff --git a/nodes/core/locales/zh-CN/messages.json b/nodes/core/locales/zh-CN/messages.json index ebac0ceda..f674e5417 100644 --- a/nodes/core/locales/zh-CN/messages.json +++ b/nodes/core/locales/zh-CN/messages.json @@ -525,7 +525,8 @@ "mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__", "bc-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": { "access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口",