1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23const common = require('../common'); 24// Skip test in FreeBSD jails. 25if (common.inFreeBSDJail) 26 common.skip('In a FreeBSD jail'); 27 28const assert = require('assert'); 29const dgram = require('dgram'); 30const fork = require('child_process').fork; 31const LOCAL_BROADCAST_HOST = '224.0.0.114'; 32const LOCAL_HOST_IFADDR = '0.0.0.0'; 33const TIMEOUT = common.platformTimeout(5000); 34const messages = [ 35 Buffer.from('First message to send'), 36 Buffer.from('Second message to send'), 37 Buffer.from('Third message to send'), 38 Buffer.from('Fourth message to send'), 39]; 40const workers = {}; 41const listeners = 3; 42let listening, sendSocket, done, timer, dead; 43 44 45function launchChildProcess() { 46 const worker = fork(__filename, ['child']); 47 workers[worker.pid] = worker; 48 49 worker.messagesReceived = []; 50 51 // Handle the death of workers. 52 worker.on('exit', function(code) { 53 // Don't consider this the true death if the worker has finished 54 // successfully or if the exit code is 0. 55 if (worker.isDone || code === 0) { 56 return; 57 } 58 59 dead += 1; 60 console.error('[PARENT] Worker %d died. %d dead of %d', 61 worker.pid, 62 dead, 63 listeners); 64 65 if (dead === listeners) { 66 console.error('[PARENT] All workers have died.'); 67 console.error('[PARENT] Fail'); 68 process.exit(1); 69 } 70 }); 71 72 worker.on('message', function(msg) { 73 if (msg.listening) { 74 listening += 1; 75 76 if (listening === listeners) { 77 // All child process are listening, so start sending. 78 sendSocket.sendNext(); 79 } 80 return; 81 } 82 if (msg.message) { 83 worker.messagesReceived.push(msg.message); 84 85 if (worker.messagesReceived.length === messages.length) { 86 done += 1; 87 worker.isDone = true; 88 console.error('[PARENT] %d received %d messages total.', 89 worker.pid, 90 worker.messagesReceived.length); 91 } 92 93 if (done === listeners) { 94 console.error('[PARENT] All workers have received the ' + 95 'required number of messages. Will now compare.'); 96 97 Object.keys(workers).forEach(function(pid) { 98 const worker = workers[pid]; 99 100 let count = 0; 101 102 worker.messagesReceived.forEach(function(buf) { 103 for (let i = 0; i < messages.length; ++i) { 104 if (buf.toString() === messages[i].toString()) { 105 count++; 106 break; 107 } 108 } 109 }); 110 111 console.error('[PARENT] %d received %d matching messages.', 112 worker.pid, count); 113 114 assert.strictEqual(count, messages.length); 115 }); 116 117 clearTimeout(timer); 118 console.error('[PARENT] Success'); 119 killSubprocesses(workers); 120 } 121 } 122 }); 123} 124 125function killSubprocesses(subprocesses) { 126 Object.keys(subprocesses).forEach(function(key) { 127 const subprocess = subprocesses[key]; 128 subprocess.kill(); 129 }); 130} 131 132if (process.argv[2] !== 'child') { 133 listening = 0; 134 dead = 0; 135 let i = 0; 136 done = 0; 137 138 // Exit the test if it doesn't succeed within TIMEOUT. 139 timer = setTimeout(function() { 140 console.error('[PARENT] Responses were not received within %d ms.', 141 TIMEOUT); 142 console.error('[PARENT] Fail'); 143 144 killSubprocesses(workers); 145 146 process.exit(1); 147 }, TIMEOUT); 148 149 // Launch child processes. 150 for (let x = 0; x < listeners; x++) { 151 launchChildProcess(x); 152 } 153 154 sendSocket = dgram.createSocket('udp4'); 155 156 // The socket is actually created async now. 157 sendSocket.on('listening', function() { 158 sendSocket.setTTL(1); 159 sendSocket.setBroadcast(true); 160 sendSocket.setMulticastTTL(1); 161 sendSocket.setMulticastLoopback(true); 162 sendSocket.setMulticastInterface(LOCAL_HOST_IFADDR); 163 }); 164 165 sendSocket.on('close', function() { 166 console.error('[PARENT] sendSocket closed'); 167 }); 168 169 sendSocket.sendNext = function() { 170 const buf = messages[i++]; 171 172 if (!buf) { 173 try { sendSocket.close(); } catch { 174 // Continue regardless of error. 175 } 176 return; 177 } 178 179 sendSocket.send( 180 buf, 181 0, 182 buf.length, 183 common.PORT, 184 LOCAL_BROADCAST_HOST, 185 function(err) { 186 assert.ifError(err); 187 console.error('[PARENT] sent "%s" to %s:%s', 188 buf.toString(), 189 LOCAL_BROADCAST_HOST, common.PORT); 190 process.nextTick(sendSocket.sendNext); 191 }, 192 ); 193 }; 194} 195 196if (process.argv[2] === 'child') { 197 const receivedMessages = []; 198 const listenSocket = dgram.createSocket({ 199 type: 'udp4', 200 reuseAddr: true, 201 }); 202 203 listenSocket.on('listening', function() { 204 listenSocket.addMembership(LOCAL_BROADCAST_HOST, LOCAL_HOST_IFADDR); 205 206 listenSocket.on('message', function(buf, rinfo) { 207 console.error('[CHILD] %s received "%s" from %j', process.pid, 208 buf.toString(), rinfo); 209 210 receivedMessages.push(buf); 211 212 process.send({ message: buf.toString() }); 213 214 if (receivedMessages.length === messages.length) { 215 // .dropMembership() not strictly needed but here as a sanity check. 216 listenSocket.dropMembership(LOCAL_BROADCAST_HOST); 217 process.nextTick(function() { 218 listenSocket.close(); 219 }); 220 } 221 }); 222 223 listenSocket.on('close', function() { 224 // HACK: Wait to exit the process to ensure that the parent 225 // process has had time to receive all messages via process.send() 226 // This may be indicative of some other issue. 227 setTimeout(function() { 228 process.exit(); 229 }, common.platformTimeout(1000)); 230 }); 231 process.send({ listening: true }); 232 }); 233 234 listenSocket.bind(common.PORT); 235} 236