forked from coolaj86/goldilocks.js
		
	added first implementation of DDNS
This commit is contained in:
		
							parent
							
								
									44d11e094b
								
							
						
					
					
						commit
						bc301b94c9
					
				@ -154,6 +154,9 @@ function fillConfig(config, args) {
 | 
			
		||||
  if (!config.dns) {
 | 
			
		||||
    config.dns = { bind: [ 53 ], modules: [{ name: 'proxy', port: 3053 }] };
 | 
			
		||||
  }
 | 
			
		||||
  if (!config.ddns) {
 | 
			
		||||
    config.ddns = { enabled: false };
 | 
			
		||||
  }
 | 
			
		||||
  // Use Object.assign to add any properties needed but not defined in the mdns config.
 | 
			
		||||
  // It will first copy the defaults into an empty object, then copy any real config over that.
 | 
			
		||||
  var mdnsDefaults = { port: 5353, broadcast: '224.0.0.251', ttl: 300 };
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										166
									
								
								lib/ddns.js
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								lib/ddns.js
									
									
									
									
									
								
							@ -1,88 +1,116 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
module.exports.create = function (opts/*, servers*/) {
 | 
			
		||||
  var PromiseA = opts.PromiseA;
 | 
			
		||||
  var dns = PromiseA.promisifyAll(require('dns'));
 | 
			
		||||
module.exports.create = function (deps, conf) {
 | 
			
		||||
  var PromiseA = deps.PromiseA;
 | 
			
		||||
  var request = PromiseA.promisify(require('request'));
 | 
			
		||||
  var OAUTH3 = require('../packages/assets/org.oauth3');
 | 
			
		||||
  require('../packages/assets/org.oauth3/oauth3.dns.js');
 | 
			
		||||
 | 
			
		||||
  return PromiseA.all([
 | 
			
		||||
    dns.resolve4Async(opts._old_server_name).then(function (results) {
 | 
			
		||||
      return results;
 | 
			
		||||
    }, function () {})
 | 
			
		||||
  , dns.resolve6Async(opts._old_server_name).then(function (results) {
 | 
			
		||||
      return results;
 | 
			
		||||
    }, function () {})
 | 
			
		||||
  ]).then(function (results) {
 | 
			
		||||
    var ipv4 = results[0] || [];
 | 
			
		||||
    var ipv6 = results[1] || [];
 | 
			
		||||
    var record;
 | 
			
		||||
  function dnsType(addr) {
 | 
			
		||||
    if (/^\d+\.\d+\.\d+\.\d+$/.test(addr)) {
 | 
			
		||||
      return 'A';
 | 
			
		||||
    }
 | 
			
		||||
    if (-1 !== addr.indexOf(':') && /^[a-f:\.\d]+$/i.test(addr)) {
 | 
			
		||||
      return 'AAAA';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    opts.dnsRecords = {
 | 
			
		||||
      A: ipv4
 | 
			
		||||
    , AAAA: ipv6
 | 
			
		||||
    };
 | 
			
		||||
  function setDeviceAddress(addr) {
 | 
			
		||||
    return deps.storage.owners.all().then(function (sessions) {
 | 
			
		||||
      return sessions.filter(function (sess) {
 | 
			
		||||
        return sess.token.scp.indexOf('dns') >= 0;
 | 
			
		||||
      })[0];
 | 
			
		||||
    }).then(function (session) {
 | 
			
		||||
      if (!session) {
 | 
			
		||||
        return PromiseA.reject(new Error('no sessions with DNS grants'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    Object.keys(opts.ifaces).some(function (ifacename) {
 | 
			
		||||
      var iface = opts.ifaces[ifacename];
 | 
			
		||||
 | 
			
		||||
      return iface.ipv4.some(function (localIp) {
 | 
			
		||||
        return ipv4.some(function (remoteIp) {
 | 
			
		||||
          if (localIp.address === remoteIp) {
 | 
			
		||||
            record = localIp;
 | 
			
		||||
            return record;
 | 
			
		||||
      return OAUTH3.discover(session.aud).then(function (directives) {
 | 
			
		||||
        return request({
 | 
			
		||||
          url: 'https://'+directives.api+'/api/org.oauth3.dns/acl/devices/' + conf.device.hostname
 | 
			
		||||
        , method: 'POST'
 | 
			
		||||
        , headers: {
 | 
			
		||||
            'Authorization': 'Bearer ' + session.refresh_token
 | 
			
		||||
          , 'Accept': 'application/json; charset=utf-8'
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      }) || iface.ipv6.some(function (localIp) {
 | 
			
		||||
        return ipv6.forEach(function (remoteIp) {
 | 
			
		||||
          if (localIp.address === remoteIp) {
 | 
			
		||||
            record = localIp;
 | 
			
		||||
            return record;
 | 
			
		||||
        , json: {
 | 
			
		||||
            addresses: [
 | 
			
		||||
              { value: addr, type:  dnsType(addr) }
 | 
			
		||||
            ]
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    if (!record) {
 | 
			
		||||
      console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address.");
 | 
			
		||||
      console.info("Use --ddns to allow the people of the Internet to access your server.");
 | 
			
		||||
  function getDeviceAddresses() {
 | 
			
		||||
    return deps.storage.owners.all().then(function (sessions) {
 | 
			
		||||
      return sessions.filter(function (sess) {
 | 
			
		||||
        return sess.token.scp.indexOf('dns') >= 0;
 | 
			
		||||
      })[0];
 | 
			
		||||
    }).then(function (session) {
 | 
			
		||||
      if (!session) {
 | 
			
		||||
        return PromiseA.reject(new Error('no sessions with DNS grants'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return OAUTH3.discover(session.aud).then(function (directives) {
 | 
			
		||||
        return request({
 | 
			
		||||
          url: 'https://'+directives.api+'/api/org.oauth3.dns/acl/devices'
 | 
			
		||||
        , method: 'GET'
 | 
			
		||||
        , headers: {
 | 
			
		||||
            'Authorization': 'Bearer ' + session.refresh_token
 | 
			
		||||
          , 'Accept': 'application/json; charset=utf-8'
 | 
			
		||||
          }
 | 
			
		||||
        , json: true
 | 
			
		||||
        });
 | 
			
		||||
      }).then(function (result) {
 | 
			
		||||
        if (!result.body) {
 | 
			
		||||
          return PromiseA.reject(new Error('No response body in request for device addresses'));
 | 
			
		||||
        }
 | 
			
		||||
        if (result.body.error) {
 | 
			
		||||
          var err = new Error(result.body.error.message);
 | 
			
		||||
          return PromiseA.reject(Object.assign(err, result.body.error));
 | 
			
		||||
        }
 | 
			
		||||
        return result.body.devices.filter(function (dev) {
 | 
			
		||||
          return dev.name === conf.device.hostname;
 | 
			
		||||
        })[0];
 | 
			
		||||
      }).then(function (dev) {
 | 
			
		||||
        return (dev || {}).addresses || [];
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var publicAddress;
 | 
			
		||||
  function recheckPubAddr() {
 | 
			
		||||
    if (!conf.ddns.enabled) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    opts.externalIps.ipv4.some(function (localIp) {
 | 
			
		||||
      return ipv4.some(function (remoteIp) {
 | 
			
		||||
        if (localIp.address === remoteIp) {
 | 
			
		||||
          record = localIp;
 | 
			
		||||
          return record;
 | 
			
		||||
    deps.storage.owners.all().then(function (sessions) {
 | 
			
		||||
      return sessions.filter(function (sess) {
 | 
			
		||||
        return sess.token.scp.indexOf('dns') >= 0;
 | 
			
		||||
      })[0];
 | 
			
		||||
    }).then(function (session) {
 | 
			
		||||
      if (!session) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      OAUTH3.discover(session.aud).then(function (directives) {
 | 
			
		||||
        return deps.loopback.checkPublicAddr(directives.api);
 | 
			
		||||
      }).then(function (addr) {
 | 
			
		||||
        if (publicAddress !== addr) {
 | 
			
		||||
          publicAddress = addr;
 | 
			
		||||
          setDeviceAddress(addr);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    opts.externalIps.ipv6.some(function (localIp) {
 | 
			
		||||
      return ipv6.some(function (remoteIp) {
 | 
			
		||||
        if (localIp.address === remoteIp) {
 | 
			
		||||
          record = localIp;
 | 
			
		||||
          return record;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  setInterval(recheckPubAddr, 5*60*1000);
 | 
			
		||||
 | 
			
		||||
    if (!record) {
 | 
			
		||||
      console.info("DNS Record '" + ipv4.concat(ipv6).join(',') + "' does not match any local IP address.");
 | 
			
		||||
      console.info("Use --ddns to allow the people of the Internet to access your server.");
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
if (require.main === module) {
 | 
			
		||||
  var opts = {
 | 
			
		||||
    _old_server_name: 'aj.daplie.me'
 | 
			
		||||
  , PromiseA: require('bluebird')
 | 
			
		||||
  return {
 | 
			
		||||
    setDeviceAddress:   setDeviceAddress
 | 
			
		||||
  , getDeviceAddresses: getDeviceAddresses
 | 
			
		||||
  , recheckPubAddr:     recheckPubAddr
 | 
			
		||||
  };
 | 
			
		||||
  // ifaces
 | 
			
		||||
  opts.ifaces = require('./local-ip.js').find();
 | 
			
		||||
  console.log('opts.ifaces');
 | 
			
		||||
  console.log(opts.ifaces);
 | 
			
		||||
  require('./match-ips.js').match(opts._old_server_name, opts).then(function (ips) {
 | 
			
		||||
    opts.matchingIps = ips.matchingIps || [];
 | 
			
		||||
    opts.externalIps = ips.externalIps;
 | 
			
		||||
    module.exports.create(opts);
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
module.exports.create = function (deps) {
 | 
			
		||||
module.exports.create = function (deps, conf) {
 | 
			
		||||
  var PromiseA = require('bluebird');
 | 
			
		||||
  var request = PromiseA.promisify(require('request'));
 | 
			
		||||
  var pending = {};
 | 
			
		||||
@ -49,7 +49,7 @@ module.exports.create = function (deps) {
 | 
			
		||||
      // If the loopback requests don't go to us then there are all kinds of ways it could
 | 
			
		||||
      // error, but none of them really provide much extra information so we don't do
 | 
			
		||||
      // anything that will break the PromiseA.all out and mask the other results.
 | 
			
		||||
      if (result.body.error) {
 | 
			
		||||
      if (conf.debug && result.body.error) {
 | 
			
		||||
        console.log('error on remote side of port '+port+' loopback', result.body.error);
 | 
			
		||||
      }
 | 
			
		||||
      return !!result.body.success;
 | 
			
		||||
@ -68,7 +68,10 @@ module.exports.create = function (deps) {
 | 
			
		||||
          return checkSinglePort(directives.api, address, port);
 | 
			
		||||
        }))
 | 
			
		||||
        .then(function (values) {
 | 
			
		||||
          console.log(pending);
 | 
			
		||||
          if (conf.debug) {
 | 
			
		||||
            console.log('remaining loopback tokens', pending);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          var result = {error: null, address: address};
 | 
			
		||||
          ports.forEach(function (port, ind) {
 | 
			
		||||
            result[port] = values[ind];
 | 
			
		||||
@ -79,6 +82,7 @@ module.exports.create = function (deps) {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loopback.checkPublicAddr = checkPublicAddr;
 | 
			
		||||
  loopback.server = require('http').createServer(function (req, res) {
 | 
			
		||||
    var parsed = require('url').parse(req.url);
 | 
			
		||||
    var token = parsed.pathname.replace('/.well-known/cloud-challenge/', '');
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										117
									
								
								lib/match-ips.js
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								lib/match-ips.js
									
									
									
									
									
								
							@ -1,117 +0,0 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
var PromiseA = require('bluebird');
 | 
			
		||||
 | 
			
		||||
module.exports.match = function (servername, opts) {
 | 
			
		||||
  return PromiseA.promisify(require('ipify'))().then(function (externalIp) {
 | 
			
		||||
    var dns = PromiseA.promisifyAll(require('dns'));
 | 
			
		||||
 | 
			
		||||
    opts.externalIps = [ { address: externalIp, family: 'IPv4' } ];
 | 
			
		||||
    opts.ifaces = require('./local-ip.js').find({ externals: opts.externalIps });
 | 
			
		||||
    opts.externalIfaces = Object.keys(opts.ifaces).reduce(function (all, iname) {
 | 
			
		||||
      var iface = opts.ifaces[iname];
 | 
			
		||||
 | 
			
		||||
      iface.ipv4.forEach(function (addr) {
 | 
			
		||||
        if (addr.external) {
 | 
			
		||||
          addr.iface = iname;
 | 
			
		||||
          all.push(addr);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      iface.ipv6.forEach(function (addr) {
 | 
			
		||||
        if (addr.external) {
 | 
			
		||||
          addr.iface = iname;
 | 
			
		||||
          all.push(addr);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return all;
 | 
			
		||||
    }, []).filter(Boolean);
 | 
			
		||||
 | 
			
		||||
    function resolveIps(hostname) {
 | 
			
		||||
      var allIps = [];
 | 
			
		||||
 | 
			
		||||
      return PromiseA.all([
 | 
			
		||||
        dns.resolve4Async(hostname).then(function (records) {
 | 
			
		||||
            records.forEach(function (ip) {
 | 
			
		||||
              allIps.push({
 | 
			
		||||
                address: ip
 | 
			
		||||
              , family: 'IPv4'
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
          }, function () {})
 | 
			
		||||
        , dns.resolve6Async(hostname).then(function (records) {
 | 
			
		||||
            records.forEach(function (ip) {
 | 
			
		||||
              allIps.push({
 | 
			
		||||
                address: ip
 | 
			
		||||
              , family: 'IPv6'
 | 
			
		||||
              });
 | 
			
		||||
            });
 | 
			
		||||
          }, function () {})
 | 
			
		||||
      ]).then(function () {
 | 
			
		||||
        return allIps;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function resolveIpsAndCnames(hostname) {
 | 
			
		||||
      return PromiseA.all([
 | 
			
		||||
        resolveIps(hostname)
 | 
			
		||||
      , dns.resolveCnameAsync(hostname).then(function (records) {
 | 
			
		||||
          return PromiseA.all(records.map(function (hostname) {
 | 
			
		||||
            return resolveIps(hostname);
 | 
			
		||||
          })).then(function (allIps) {
 | 
			
		||||
            return allIps.reduce(function (all, ips) {
 | 
			
		||||
              return all.concat(ips);
 | 
			
		||||
            }, []);
 | 
			
		||||
          });
 | 
			
		||||
        }, function () {
 | 
			
		||||
          return [];
 | 
			
		||||
        })
 | 
			
		||||
      ]).then(function (ips) {
 | 
			
		||||
        return ips.reduce(function (all, set) {
 | 
			
		||||
          return all.concat(set);
 | 
			
		||||
        }, []);
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return resolveIpsAndCnames(servername).then(function (allIps) {
 | 
			
		||||
      var matchingIps = [];
 | 
			
		||||
 | 
			
		||||
      if (!allIps.length) {
 | 
			
		||||
        console.warn("Could not resolve '" + servername + "'");
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // { address, family }
 | 
			
		||||
      allIps.some(function (ip) {
 | 
			
		||||
        function match(addr) {
 | 
			
		||||
          if (ip.address === addr.address) {
 | 
			
		||||
            matchingIps.push(addr);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        opts.externalIps.forEach(match);
 | 
			
		||||
        // opts.externalIfaces.forEach(match);
 | 
			
		||||
 | 
			
		||||
        Object.keys(opts.ifaces).forEach(function (iname) {
 | 
			
		||||
          var iface = opts.ifaces[iname];
 | 
			
		||||
 | 
			
		||||
          iface.ipv4.forEach(match);
 | 
			
		||||
          iface.ipv6.forEach(match);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return matchingIps.length;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      matchingIps.externalIps = {
 | 
			
		||||
        ipv4: [
 | 
			
		||||
          { address: externalIp
 | 
			
		||||
          , family: 'IPv4'
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      , ipv6: [
 | 
			
		||||
        ]
 | 
			
		||||
      };
 | 
			
		||||
      matchingIps.matchingIps = matchingIps;
 | 
			
		||||
      return matchingIps;
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@ -31,6 +31,7 @@ function create(conf) {
 | 
			
		||||
  deps.proxy = require('./proxy-conn').create(deps, conf);
 | 
			
		||||
  deps.socks5 = require('./socks5-server').create(deps, conf);
 | 
			
		||||
  deps.loopback = require('./loopback').create(deps, conf);
 | 
			
		||||
  deps.ddns = require('./ddns').create(deps, conf);
 | 
			
		||||
 | 
			
		||||
  require('./goldilocks.js').create(deps, conf);
 | 
			
		||||
  process.removeListener('message', create);
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,6 @@
 | 
			
		||||
    "http-proxy": "^1.16.2",
 | 
			
		||||
    "human-readable-ids": "git+https://git.daplie.com/Daplie/human-readable-ids-js#master",
 | 
			
		||||
    "ipaddr.js": "git+https://github.com/whitequark/ipaddr.js.git#v1.3.0",
 | 
			
		||||
    "ipify": "^1.1.0",
 | 
			
		||||
    "js-yaml": "^3.8.3",
 | 
			
		||||
    "jsonwebtoken": "^7.4.0",
 | 
			
		||||
    "le-challenge-ddns": "git+https://git.daplie.com/Daplie/le-challenge-ddns.git#master",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user