ip: better tor support.

This commit is contained in:
Christopher Jeffrey 2017-01-23 22:31:45 -08:00
parent 2c0f8ce226
commit 07c3177227
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
8 changed files with 289 additions and 78 deletions

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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);

View File

@ -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.');

View File

@ -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
View 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);
};

View File

@ -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;

View File

@ -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;
}