| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  | 'use strict'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var PromiseA = require('bluebird'); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  | var os = require('os'); | 
					
						
							| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  | module.exports = function (args) { | 
					
						
							| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  |   if (args.debug) { | 
					
						
							|  |  |  |     console.log('[HP] create holepuncher'); | 
					
						
							|  |  |  |     console.log(args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |   var interfaces = os.networkInterfaces(); | 
					
						
							|  |  |  |   var ifacenames = Object.keys(interfaces).filter(function (ifacename) { | 
					
						
							|  |  |  |     // http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/
 | 
					
						
							|  |  |  |     // https://wiki.archlinux.org/index.php/Network_configuration#Device_names
 | 
					
						
							|  |  |  |     // we do not include tun and bridge devices because we're trying
 | 
					
						
							|  |  |  |     // to see if any physical interface is internet-connected first
 | 
					
						
							|  |  |  |     return /^(en|sl|wl|ww|eth|net|lan|wifi|inet)/.test(ifacename); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |   function getExternalIps() { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |     if (!args.ipifyUrls || !args.ipifyUrls.length) { | 
					
						
							|  |  |  |       return PromiseA.resolve(args.ifaces.map(function (iface) { | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           family: iface.family | 
					
						
							|  |  |  |         //, address: addr
 | 
					
						
							|  |  |  |         , address: iface.address // TODO check where this is used
 | 
					
						
							|  |  |  |         , localAddress: iface.address | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       })); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |     return PromiseA.any(args.ipifyUrls.map(function (ipifyUrl) { | 
					
						
							| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  |       var getIp = require('./external-ip'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return getIp({ hostname: ipifyUrl, debug: args.debug }); | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |   function testOpenPort(ip, portInfo) { | 
					
						
							|  |  |  |     var requestAsync = require('./request'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 08:46:22 +00:00
										 |  |  |     if (args.debug) { | 
					
						
							|  |  |  |       console.log('[HP] hostname', args.loopbackHostname); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |     return requestAsync({ | 
					
						
							|  |  |  |       secure: portInfo.secure | 
					
						
							|  |  |  |     , rejectUnauthorized: false | 
					
						
							|  |  |  |     , hostname: ip.address | 
					
						
							|  |  |  |       // '/.well-known/com.daplie.loopback/'
 | 
					
						
							|  |  |  |     , path: args.loopbackPrefix + args.key | 
					
						
							|  |  |  |       // 'loopback.daplie.invalid'
 | 
					
						
							|  |  |  |     , servername: args.loopbackHostname | 
					
						
							|  |  |  |     , localAddress: ip.localAddress | 
					
						
							|  |  |  |     , port: portInfo.external || portInfo.internal | 
					
						
							|  |  |  |     , headers: { | 
					
						
							|  |  |  |         // 'loopback.daplie.invalid'
 | 
					
						
							|  |  |  |         Host: args.loopbackHostname | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }).then(function (val) { | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |       if (args.debug) { | 
					
						
							|  |  |  |         console.log('[HP] loopback test reached', val); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       if (val !== args.value) { | 
					
						
							|  |  |  |         return PromiseA.reject(new Error("invalid loopback token value")); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       ip.validated = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       ip.ports.push(portInfo); | 
					
						
							|  |  |  |       portInfo.ips.push(ip); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return portInfo; | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |     }, function (err) { | 
					
						
							|  |  |  |       if (args.debug) { | 
					
						
							| 
									
										
										
										
											2015-12-30 21:40:52 +00:00
										 |  |  |         console.log('[HP] loopback did not complete'); | 
					
						
							|  |  |  |         console.log(err.stack); | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return PromiseA.reject(err); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   function testPort(opts) { | 
					
						
							|  |  |  |     // TODO should ip.address === ip.localAddress be treated differently?
 | 
					
						
							|  |  |  |     // TODO check local firewall?
 | 
					
						
							|  |  |  |     // TODO would it ever make sense for a public ip to respond to upnp?
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // TODO should we pass any or require all?
 | 
					
						
							|  |  |  |     opts.portInfo.ips = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return PromiseA.any(opts.ips.map(function (ip) { | 
					
						
							|  |  |  |       ip.ports = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |       if (opts.debug) { | 
					
						
							| 
									
										
										
										
											2015-12-30 21:40:52 +00:00
										 |  |  |         console.log('[HP] pretest = ', opts.pretest); | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       if (!opts.pretest) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         return PromiseA.reject(new Error("[not an error]: skip the loopback test")); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return testOpenPort(ip, opts.portInfo); | 
					
						
							|  |  |  |     })); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |   args.ifaces = ifacenames.reduce(function (all, ifacename) { | 
					
						
							|  |  |  |     var ifs = interfaces[ifacename]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ifs.forEach(function (iface) { | 
					
						
							|  |  |  |       if (!iface.internal && !/^fe80/.test(iface.address)) { | 
					
						
							|  |  |  |         all.push(iface); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return all; | 
					
						
							|  |  |  |   }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (args.debug) { | 
					
						
							|  |  |  |     console.log('[HP] external ifaces:'); | 
					
						
							|  |  |  |     console.log(args.ifaces); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |   return getExternalIps().then(function (ips) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |     var portInfos = args.mappings; | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |     return PromiseA.all(portInfos.map(function (mapping) { | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       // TODO clone-merge args
 | 
					
						
							|  |  |  |       return testPort({ | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         portInfo: mapping | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       , ips: ips | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |       , pretest: mapping.loopback | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       }); | 
					
						
							|  |  |  |     })).then(function (portInfos) { | 
					
						
							|  |  |  |       if (args.debug) { | 
					
						
							|  |  |  |         console.log('[HP] all done on the first try'); | 
					
						
							|  |  |  |         console.log(portInfos); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return portInfos; | 
					
						
							|  |  |  |     }, function () { | 
					
						
							|  |  |  |       // at least one port could not be mapped
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |       var upnps = []; | 
					
						
							|  |  |  |       var pmps = []; | 
					
						
							|  |  |  |       var pu = PromiseA.resolve(); | 
					
						
							|  |  |  |       var pm = PromiseA.resolve(); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |       if (args.upnp) { | 
					
						
							| 
									
										
										
										
											2015-12-30 21:40:52 +00:00
										 |  |  |         if (args.debug) { | 
					
						
							|  |  |  |           console.log('[HP] will try upnp'); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         upnps.push(require('./upnp')); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |       if (args.pmp) { | 
					
						
							| 
									
										
										
										
											2015-12-30 21:40:52 +00:00
										 |  |  |         if (args.debug) { | 
					
						
							|  |  |  |           console.log('[HP] will try nat-pmp'); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         pmps.push(require('./pmp')); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return PromiseA.all(portInfos.map(function (portInfo) { | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         /* | 
					
						
							|  |  |  |         // TODO create single dgram listeners and serialize upnp requests
 | 
					
						
							|  |  |  |         // because we can't have multiple requests  bound to the same port, duh
 | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |         return PromiseA.any(mappers.map(function (fn) { | 
					
						
							|  |  |  |           var p = fn(args, ips, portInfo); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |           if (portInfo.ips.length) { | 
					
						
							|  |  |  |             return portInfo; | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return p; | 
					
						
							|  |  |  |         })); | 
					
						
							| 
									
										
										
										
											2015-12-31 02:21:29 +00:00
										 |  |  |         */ | 
					
						
							|  |  |  |         var good; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function nextu(fn) { | 
					
						
							|  |  |  |           pu = pu.then(function () { | 
					
						
							|  |  |  |             return fn(args, ips, portInfo); | 
					
						
							|  |  |  |           }).then(function (results) { | 
					
						
							|  |  |  |             good = results; | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |           }, function (/*err*/) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function nextm(fn) { | 
					
						
							|  |  |  |           pm = pm.then(function () { | 
					
						
							|  |  |  |             return fn(args, ips, portInfo); | 
					
						
							|  |  |  |           }).then(function (results) { | 
					
						
							|  |  |  |             good = results; | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |           }, function (/*err*/) { | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         upnps.forEach(nextu); | 
					
						
							|  |  |  |         pmps.forEach(nextm); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return PromiseA.any([pu, pm]).then(function () { | 
					
						
							|  |  |  |           if (!good) { | 
					
						
							|  |  |  |             return PromiseA.reject(new Error("no port map success")); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           return null; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       })).then(function () { | 
					
						
							|  |  |  |         if (args.debug) { | 
					
						
							|  |  |  |           console.log("[HP] all ports successfully mapped"); | 
					
						
							|  |  |  |           console.log(portInfos); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return portInfos; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }).then(function () { | 
					
						
							|  |  |  |       return portInfos; | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |     }, function (err) { | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       console.warn('[HP] RVPN not implemented'); | 
					
						
							| 
									
										
										
										
											2015-12-30 08:22:04 +00:00
										 |  |  |       console.warn(err.stack); | 
					
						
							| 
									
										
										
										
											2015-12-30 06:35:21 +00:00
										 |  |  |       return portInfos; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2015-12-30 03:36:14 +00:00
										 |  |  |   }); | 
					
						
							|  |  |  | }; |