ip: better tor support.
This commit is contained in:
parent
2c0f8ce226
commit
07c3177227
@ -151,6 +151,7 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce
|
||||
|
||||
try {
|
||||
raw = IP.toBuffer(host);
|
||||
host = IP.toString(raw);
|
||||
} catch (e) {
|
||||
this.log('Client gave a bad host: %s (%s).', host, state.host);
|
||||
ws.emit('tcp error', {
|
||||
@ -161,7 +162,7 @@ WSProxy.prototype._handleConnect = function _handleConnect(ws, port, host, nonce
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IP.isRoutable(raw)) {
|
||||
if (!IP.isRoutable(raw) || IP.isTor(raw)) {
|
||||
this.log(
|
||||
'Client is trying to connect to a bad ip: %s (%s).',
|
||||
host, state.host);
|
||||
@ -257,7 +258,7 @@ SocketState.prototype.toInfo = function toInfo() {
|
||||
|
||||
SocketState.prototype.connect = function connect(port, host) {
|
||||
this.socket = net.connect(port, host);
|
||||
this.remoteHost = IP.hostname(host, port);
|
||||
this.remoteHost = IP.toHostname(host, port);
|
||||
return this.socket;
|
||||
};
|
||||
|
||||
|
||||
@ -529,7 +529,7 @@ AuthDB.prototype.addKnown = function addKnown(host, key) {
|
||||
|
||||
addr = IP.parseHost(host);
|
||||
|
||||
if (addr.version === -1) {
|
||||
if (addr.type === IP.types.DNS) {
|
||||
// Defer this for resolution.
|
||||
this.dnsKnown.push([addr, key]);
|
||||
return;
|
||||
@ -632,7 +632,7 @@ AuthDB.prototype.discover = co(function* discover() {
|
||||
AuthDB.prototype.populate = co(function* populate(addr, key) {
|
||||
var i, hosts, host;
|
||||
|
||||
assert(addr.version === -1, 'Resolved host passed.');
|
||||
assert(addr.type === IP.types.DNS, 'Resolved host passed.');
|
||||
|
||||
if (this.logger)
|
||||
this.logger.info('Resolving authorized hosts from: %s.', addr.host);
|
||||
@ -649,7 +649,7 @@ AuthDB.prototype.populate = co(function* populate(addr, key) {
|
||||
host = hosts[i];
|
||||
|
||||
if (addr.port !== 0)
|
||||
host = IP.hostname(host, addr.port);
|
||||
host = IP.toHostname(host, addr.port);
|
||||
|
||||
this.known[host] = key;
|
||||
}
|
||||
|
||||
@ -664,7 +664,7 @@ HostList.prototype.toArray = function toArray() {
|
||||
HostList.prototype.addSeed = function addSeed(host) {
|
||||
var addr = IP.parseHost(host, this.network.port);
|
||||
|
||||
if (addr.version === -1) {
|
||||
if (addr.type === IP.types.DNS) {
|
||||
// Defer for resolution.
|
||||
this.dnsSeeds.push(addr);
|
||||
return;
|
||||
@ -685,7 +685,7 @@ HostList.prototype.addSeed = function addSeed(host) {
|
||||
HostList.prototype.addNode = function addNode(host) {
|
||||
var addr = IP.parseHost(host, this.network.port);
|
||||
|
||||
if (addr.version === -1) {
|
||||
if (addr.type === IP.types.DNS) {
|
||||
// Defer for resolution.
|
||||
this.dnsNodes.push(addr);
|
||||
return;
|
||||
@ -798,7 +798,7 @@ HostList.prototype.populate = co(function* populate(target) {
|
||||
var addrs = [];
|
||||
var i, addr, hosts, host;
|
||||
|
||||
assert(target.version === -1, 'Resolved host passed.');
|
||||
assert(target.type === IP.types.DNS, 'Resolved host passed.');
|
||||
|
||||
if (this.logger)
|
||||
this.logger.info('Resolving host: %s.', target.host);
|
||||
|
||||
@ -439,7 +439,7 @@ Pool.prototype.handleSocket = function handleSocket(socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
host = IP.hostname(host, socket.remotePort);
|
||||
host = IP.toHostname(host, socket.remotePort);
|
||||
|
||||
assert(!this.peers.map[host], 'Port collision.');
|
||||
|
||||
|
||||
@ -80,7 +80,7 @@ NetAddress.prototype.fromOptions = function fromOptions(options) {
|
||||
this.ts = options.ts;
|
||||
}
|
||||
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -175,7 +175,7 @@ NetAddress.prototype.isTor = function isTor() {
|
||||
NetAddress.prototype.setNull = function setNull() {
|
||||
this.raw = IP.ZERO_IP;
|
||||
this.host = '0.0.0.0';
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -186,7 +186,7 @@ NetAddress.prototype.setNull = function setNull() {
|
||||
NetAddress.prototype.setHost = function setHost(host) {
|
||||
this.raw = IP.toBuffer(host);
|
||||
this.host = IP.toString(this.raw);
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -196,7 +196,7 @@ NetAddress.prototype.setHost = function setHost(host) {
|
||||
|
||||
NetAddress.prototype.setPort = function setPort(port) {
|
||||
this.port = port;
|
||||
this.hostname = IP.hostname(this.host, port);
|
||||
this.hostname = IP.toHostname(this.host, port);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -216,7 +216,7 @@ NetAddress.prototype.fromHost = function fromHost(host, port, network) {
|
||||
this.services = NetAddress.DEFAULT_SERVICES;
|
||||
this.ts = network.now();
|
||||
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -306,7 +306,7 @@ NetAddress.prototype.fromReader = function fromReader(br, full) {
|
||||
this.raw = br.readBytes(16);
|
||||
this.host = IP.toString(this.raw);
|
||||
this.port = br.readU16BE();
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
|
||||
return this;
|
||||
};
|
||||
@ -414,7 +414,7 @@ NetAddress.prototype.fromJSON = function fromJSON(json) {
|
||||
this.port = json.port;
|
||||
this.services = json.services;
|
||||
this.ts = json.ts;
|
||||
this.hostname = IP.hostname(this.host, this.port);
|
||||
this.hostname = IP.toHostname(this.host, this.port);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
187
lib/utils/base32.js
Normal file
187
lib/utils/base32.js
Normal file
@ -0,0 +1,187 @@
|
||||
/*!
|
||||
* base32.js - base32 for bcoin
|
||||
* Copyright (c) 2014-2016, Christopher Jeffrey (MIT License).
|
||||
* https://github.com/bcoin-org/bcoin
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* Base32
|
||||
*/
|
||||
|
||||
var base32 = 'abcdefghijklmnopqrstuvwxyz234567';
|
||||
var padding = [0, 6, 4, 3, 1];
|
||||
var unbase32 = {};
|
||||
|
||||
for (var i = 0; i < base32.length; i++)
|
||||
unbase32[base32[i]] = i;
|
||||
|
||||
/**
|
||||
* Encode a base32 string.
|
||||
* @param {Buffer} data
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
exports.encode = function(data) {
|
||||
var str = '';
|
||||
var mode = 0;
|
||||
var left = 0;
|
||||
var i, ch;
|
||||
|
||||
for (i = 0; i < data.length; i++) {
|
||||
ch = data[i];
|
||||
switch (mode) {
|
||||
case 0:
|
||||
str += base32[ch >>> 3];
|
||||
left = (ch & 7) << 2;
|
||||
mode = 1;
|
||||
break;
|
||||
case 1:
|
||||
str += base32[left | (ch >>> 6)];
|
||||
str += base32[(ch >>> 1) & 31];
|
||||
left = (ch & 1) << 4;
|
||||
mode = 2;
|
||||
break;
|
||||
case 2:
|
||||
str += base32[left | (ch >>> 4)];
|
||||
left = (ch & 15) << 1;
|
||||
mode = 3;
|
||||
break;
|
||||
case 3:
|
||||
str += base32[left | (ch >>> 7)];
|
||||
str += base32[(ch >>> 2) & 31];
|
||||
left = (ch & 3) << 3;
|
||||
mode = 4;
|
||||
break;
|
||||
case 4:
|
||||
str += base32[left | (ch >>> 5)];
|
||||
str += base32[ch & 31];
|
||||
mode = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode > 0) {
|
||||
str += base32[left];
|
||||
for (i = 0; i < padding[mode]; i++)
|
||||
str += '=';
|
||||
}
|
||||
|
||||
return str;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a base32 string.
|
||||
* @param {String} str
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
exports.decode = function decode(str) {
|
||||
var data = new Buffer(str.length * 5 / 8 | 0);
|
||||
var mode = 0;
|
||||
var left = 0;
|
||||
var j = 0;
|
||||
var i, ch;
|
||||
|
||||
for (i = 0; i < str.length; i++) {
|
||||
ch = unbase32[str[i]];
|
||||
|
||||
if (ch == null)
|
||||
break;
|
||||
|
||||
switch (mode) {
|
||||
case 0:
|
||||
left = ch;
|
||||
mode = 1;
|
||||
break;
|
||||
case 1:
|
||||
data[j++] = (left << 3) | (ch >>> 2);
|
||||
left = ch & 3;
|
||||
mode = 2;
|
||||
break;
|
||||
case 2:
|
||||
left = left << 5 | ch;
|
||||
mode = 3;
|
||||
break;
|
||||
case 3:
|
||||
data[j++] = (left << 1) | (ch >>> 4);
|
||||
left = ch & 15;
|
||||
mode = 4;
|
||||
break;
|
||||
case 4:
|
||||
data[j++] = (left << 4) | (ch >>> 1);
|
||||
left = ch & 1;
|
||||
mode = 5;
|
||||
break;
|
||||
case 5:
|
||||
left = left << 5 | ch;
|
||||
mode = 6;
|
||||
break;
|
||||
case 6:
|
||||
data[j++] = (left << 2) | (ch >>> 3);
|
||||
left = ch & 7;
|
||||
mode = 7;
|
||||
break;
|
||||
case 7:
|
||||
data[j++] = (left << 5) | ch;
|
||||
mode = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
case 3:
|
||||
case 6:
|
||||
throw new Error('Invalid base32 string.');
|
||||
case 2:
|
||||
if (left > 0)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
if (str.slice(i, i + 6) !== '======')
|
||||
throw new Error('Invalid base32 character.');
|
||||
|
||||
if (unbase32[str[i + 6]] != null)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
break;
|
||||
case 4:
|
||||
if (left > 0)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
if (str.slice(i, i + 4) !== '====')
|
||||
throw new Error('Invalid base32 character.');
|
||||
|
||||
if (unbase32[str[i + 4]] != null)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
break;
|
||||
case 5:
|
||||
if (left > 0)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
if (str.slice(i, i + 3) !== '===')
|
||||
throw new Error('Invalid base32 character.');
|
||||
|
||||
if (unbase32[str[i + 3]] != null)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if (left > 0)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
if (str[i] !== '=')
|
||||
throw new Error('Invalid base32 character.');
|
||||
|
||||
if (unbase32[str[i + 1]] != null)
|
||||
throw new Error('Invalid padding.');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return data.slice(0, j);
|
||||
};
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
exports.ASN1 = require('./asn1');
|
||||
exports.AsyncObject = require('./async');
|
||||
exports.base32 = require('./base32');
|
||||
exports.base58 = require('./base58');
|
||||
exports.Bloom = require('./bloom');
|
||||
exports.RollingFilter = exports.Bloom.Rolling;
|
||||
|
||||
146
lib/utils/ip.js
146
lib/utils/ip.js
@ -12,6 +12,7 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
var base32 = require('./base32');
|
||||
var IP = exports;
|
||||
|
||||
/*
|
||||
@ -37,15 +38,27 @@ var IPV6_REGEX =
|
||||
|
||||
IP.ZERO_IP = ZERO_IP;
|
||||
|
||||
/**
|
||||
* Address types.
|
||||
* @enum {Number}
|
||||
*/
|
||||
|
||||
IP.types = {
|
||||
DNS: -1,
|
||||
IPV4: 4,
|
||||
IPV6: 6,
|
||||
TOR: 10
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a hostname.
|
||||
* @param {String} addr
|
||||
* @param {Number?} fallback - Fallback port.
|
||||
* @returns {Object} Contains `host`, `port`, and `version`.
|
||||
* @returns {Object} Contains `host`, `port`, and `type`.
|
||||
*/
|
||||
|
||||
IP.parseHost = function parseHost(addr, fallback) {
|
||||
var parts, host, port, version, hostname, raw;
|
||||
var parts, host, port, type, hostname, raw;
|
||||
|
||||
assert(typeof addr === 'string');
|
||||
|
||||
@ -104,21 +117,21 @@ IP.parseHost = function parseHost(addr, fallback) {
|
||||
port = fallback || 0;
|
||||
}
|
||||
|
||||
version = IP.version(host);
|
||||
type = IP.getType(host);
|
||||
|
||||
if (version !== -1) {
|
||||
if (type !== IP.types.DNS) {
|
||||
raw = IP.toBuffer(host);
|
||||
host = IP.toString(raw);
|
||||
}
|
||||
|
||||
hostname = host;
|
||||
|
||||
if (version === 6)
|
||||
if (type === IP.types.IPV6)
|
||||
hostname = '[' + hostname + ']';
|
||||
|
||||
hostname += ':' + port;
|
||||
|
||||
return new Address(host, port, version, hostname, raw);
|
||||
return new Address(host, port, type, hostname, raw);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -128,8 +141,8 @@ IP.parseHost = function parseHost(addr, fallback) {
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
IP.hostname = function hostname(host, port) {
|
||||
var version;
|
||||
IP.toHostname = function toHostname(host, port) {
|
||||
var type;
|
||||
|
||||
assert(typeof host === 'string');
|
||||
assert(host.length > 0);
|
||||
@ -138,34 +151,37 @@ IP.hostname = function hostname(host, port) {
|
||||
|
||||
assert(!/[\[\]]/.test(host), 'Bad host.');
|
||||
|
||||
version = IP.version(host);
|
||||
type = IP.getType(host);
|
||||
|
||||
if (host.indexOf(':') !== -1)
|
||||
assert(version === 6, 'Bad host.');
|
||||
assert(type === IP.types.IPV6, 'Bad host.');
|
||||
|
||||
if (version !== -1)
|
||||
if (type !== IP.types.DNS)
|
||||
host = IP.normalize(host);
|
||||
|
||||
if (version === 6)
|
||||
if (type === IP.types.IPV6)
|
||||
host = '[' + host + ']';
|
||||
|
||||
return host + ':' + port;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether a string is an IP address.
|
||||
* Get address type (-1=dns, 4=ipv4, 6=ipv6, 10=tor).
|
||||
* @param {String?} str
|
||||
* @returns {Number} IP version (4 or 6).
|
||||
* @returns {Number}
|
||||
*/
|
||||
|
||||
IP.version = function version(str) {
|
||||
IP.getType = function getType(str) {
|
||||
if (IP.isV4Format(str))
|
||||
return 4;
|
||||
return IP.types.IPV4;
|
||||
|
||||
if (IP.isV6Format(str))
|
||||
return 6;
|
||||
return IP.types.IPV6;
|
||||
|
||||
return -1;
|
||||
if (IP.isTorFormat(str))
|
||||
return IP.types.TOR;
|
||||
|
||||
return IP.types.DNS;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -204,6 +220,21 @@ IP.isV6Format = function isV6Format(str) {
|
||||
return IPV6_REGEX.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether a string is an onion address.
|
||||
* @param {String?} str
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
IP.isTorFormat = function isTorFormat(str) {
|
||||
assert(typeof str === 'string');
|
||||
|
||||
if (str.length < 7)
|
||||
return false;
|
||||
|
||||
return str.slice(-6) === '.onion';
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether a buffer is an ipv4-mapped ipv6 address.
|
||||
* @param {Buffer} raw
|
||||
@ -236,6 +267,7 @@ IP.isMapped = function isMapped(raw) {
|
||||
|
||||
IP.toBuffer = function toBuffer(str) {
|
||||
var raw = new Buffer(16);
|
||||
var data;
|
||||
|
||||
assert(typeof str === 'string');
|
||||
|
||||
@ -246,6 +278,15 @@ IP.toBuffer = function toBuffer(str) {
|
||||
return IP.parseV4(str, raw, 12);
|
||||
}
|
||||
|
||||
if (IP.isTorFormat(str)) {
|
||||
data = TOR_ONION;
|
||||
data.copy(raw, 0);
|
||||
data = base32.decode(str.slice(0, -6));
|
||||
assert(data.length === 10, 'Invalid onion address.');
|
||||
data.copy(raw, 6);
|
||||
return raw;
|
||||
}
|
||||
|
||||
return IP.parseV6(str, raw, 0);
|
||||
};
|
||||
|
||||
@ -357,68 +398,49 @@ IP.parseV6 = function parseV6(str, raw, offset) {
|
||||
*/
|
||||
|
||||
IP.toString = function toString(raw) {
|
||||
var str = '';
|
||||
var host = '';
|
||||
var i;
|
||||
|
||||
assert(Buffer.isBuffer(raw));
|
||||
|
||||
if (raw.length === 4) {
|
||||
str += raw[0];
|
||||
str += '.' + raw[1];
|
||||
str += '.' + raw[2];
|
||||
str += '.' + raw[3];
|
||||
return str;
|
||||
host += raw[0];
|
||||
host += '.' + raw[1];
|
||||
host += '.' + raw[2];
|
||||
host += '.' + raw[3];
|
||||
return host;
|
||||
}
|
||||
|
||||
if (raw.length === 16) {
|
||||
if (IP.isMapped(raw)) {
|
||||
str += raw[12];
|
||||
str += '.' + raw[13];
|
||||
str += '.' + raw[14];
|
||||
str += '.' + raw[15];
|
||||
return str;
|
||||
host += raw[12];
|
||||
host += '.' + raw[13];
|
||||
host += '.' + raw[14];
|
||||
host += '.' + raw[15];
|
||||
return host;
|
||||
}
|
||||
|
||||
str += raw.readUInt16BE(0, true).toString(16);
|
||||
if (IP.isTor(raw)) {
|
||||
host = base32.encode(raw.slice(6));
|
||||
return host + '.onion';
|
||||
}
|
||||
|
||||
host += raw.readUInt16BE(0, true).toString(16);
|
||||
|
||||
for (i = 2; i < 16; i += 2) {
|
||||
str += ':';
|
||||
str += raw.readUInt16BE(i, true).toString(16);
|
||||
host += ':';
|
||||
host += raw.readUInt16BE(i, true).toString(16);
|
||||
}
|
||||
|
||||
str = str.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
|
||||
str = str.replace(/:{3,4}/, '::');
|
||||
host = host.replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
|
||||
host = host.replace(/:{3,4}/, '::');
|
||||
|
||||
return str;
|
||||
return host;
|
||||
}
|
||||
|
||||
throw Error('Invalid IP address: ' + raw.toString('hex'));
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a buffer to an ip string or .onion.
|
||||
* @param {Buffer} raw
|
||||
* @returns {String}
|
||||
*/
|
||||
|
||||
IP.toHost = function toHost(raw) {
|
||||
var i, ch, host;
|
||||
|
||||
if (IP.isTor(raw)) {
|
||||
host = '';
|
||||
for (i = 6; i < raw.length; i += 2) {
|
||||
ch = raw[i];
|
||||
ch = ch.toString(32);
|
||||
while (ch.length < 4)
|
||||
ch = '0' + ch;
|
||||
host += ch;
|
||||
}
|
||||
return host + '.onion';
|
||||
}
|
||||
|
||||
return IP.toString(raw);
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize an ip.
|
||||
* @param {String} str
|
||||
@ -836,15 +858,15 @@ IP.isEqual = function isEqual(a, b) {
|
||||
* Represents a parsed address.
|
||||
* @param {String} host
|
||||
* @param {Number} port
|
||||
* @param {Number} version
|
||||
* @param {Number} type
|
||||
* @param {String} hostname
|
||||
* @param {Buffer} raw
|
||||
*/
|
||||
|
||||
function Address(host, port, version, hostname, raw) {
|
||||
function Address(host, port, type, hostname, raw) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.version = version;
|
||||
this.type = type;
|
||||
this.hostname = hostname;
|
||||
this.raw = raw || ZERO_IP;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user