parent
84ad9eb2c2
commit
d68e5adf0b
81
lib/_util.js
81
lib/_util.js
|
@ -94,3 +94,84 @@ exports.popkey = function(obj, key) {
|
||||||
delete obj[key];
|
delete obj[key];
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// See https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
||||||
|
var IPv4_MAPPING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
|
||||||
|
exports.IPv4_MAPPING = IPv4_MAPPING;
|
||||||
|
|
||||||
|
// Very simple inet_pton(3) equivalent.
|
||||||
|
exports.inet_pton = function(str) {
|
||||||
|
var buf = new Buffer(16);
|
||||||
|
buf.fill(0);
|
||||||
|
// IPv4-mapped IPv6.
|
||||||
|
if (str.slice(0, 7) === "::ffff:") {
|
||||||
|
str = str.slice(7);
|
||||||
|
}
|
||||||
|
// IPv4.
|
||||||
|
if (str.indexOf(":") === -1) {
|
||||||
|
IPv4_MAPPING.copy(buf);
|
||||||
|
var octets = str.split(/\./g).map(function(o) {
|
||||||
|
assert(/^\d+$/.test(o), "Bad octet");
|
||||||
|
return parseInt(o, 10);
|
||||||
|
});
|
||||||
|
// Support short form from inet_aton(3) man page.
|
||||||
|
if (octets.length === 1) {
|
||||||
|
buf.writeUInt32BE(octets[0], 12);
|
||||||
|
} else {
|
||||||
|
// Check against 1000.bad.addr
|
||||||
|
octets.forEach(function(octet) {
|
||||||
|
assert(octet >= 0, "Bad IPv4 address");
|
||||||
|
assert(octet <= 255, "Bad IPv4 address");
|
||||||
|
});
|
||||||
|
if (octets.length === 2) {
|
||||||
|
buf[12] = octets[0];
|
||||||
|
buf[15] = octets[1];
|
||||||
|
} else if (octets.length === 3) {
|
||||||
|
buf[12] = octets[0];
|
||||||
|
buf[13] = octets[1];
|
||||||
|
buf[15] = octets[2];
|
||||||
|
} else if (octets.length === 4) {
|
||||||
|
buf[12] = octets[0];
|
||||||
|
buf[13] = octets[1];
|
||||||
|
buf[14] = octets[2];
|
||||||
|
buf[15] = octets[3];
|
||||||
|
} else {
|
||||||
|
throw new Error("Bad IPv4 address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IPv6.
|
||||||
|
} else {
|
||||||
|
var dgroups = str.split(/::/g);
|
||||||
|
// Check against 1::1::1
|
||||||
|
assert(dgroups.length <= 2, "Bad IPv6 address");
|
||||||
|
var groups = [];
|
||||||
|
var i;
|
||||||
|
if (dgroups[0]) {
|
||||||
|
groups.push.apply(groups, dgroups[0].split(/:/g));
|
||||||
|
}
|
||||||
|
if (dgroups.length === 2) {
|
||||||
|
if (dgroups[1]) {
|
||||||
|
var splitted = dgroups[1].split(/:/g);
|
||||||
|
var fill = 8 - (groups.length + splitted.length);
|
||||||
|
// Check against 1:1:1:1::1:1:1:1
|
||||||
|
assert(fill > 0, "Bad IPv6 address");
|
||||||
|
for (i = 0; i < fill; i++) {
|
||||||
|
groups.push(0);
|
||||||
|
}
|
||||||
|
groups.push.apply(groups, splitted);
|
||||||
|
} else {
|
||||||
|
// Check against 1:1:1:1:1:1:1:1::
|
||||||
|
assert(groups.length <= 7, "Bad IPv6 address");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Check against 1:1:1
|
||||||
|
assert(groups.length === 8, "Bad IPv6 address");
|
||||||
|
}
|
||||||
|
for (i = 0; i < Math.min(groups.length, 8); i++) {
|
||||||
|
// Check against parseInt("127.0.0.1", 16) -> 295
|
||||||
|
assert(/^[0-9a-f]+$/.test(groups[i]), "Bad group");
|
||||||
|
buf.writeUInt16BE(parseInt(groups[i], 16), i * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
};
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
|
var bufferEqual = require("buffer-equal");
|
||||||
var assert = require("./_util").assert;
|
var assert = require("./_util").assert;
|
||||||
var structs = require("./structs");
|
var structs = require("./structs");
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
|
@ -41,6 +42,8 @@ var util = require("./_util");
|
||||||
|
|
||||||
var message = structs.message;
|
var message = structs.message;
|
||||||
var ServicesBitfield = structs.ServicesBitfield;
|
var ServicesBitfield = structs.ServicesBitfield;
|
||||||
|
var IPv4_MAPPING = util.IPv4_MAPPING;
|
||||||
|
var inet_pton = util.inet_pton;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try to get command of the given encoded message.
|
* Try to get command of the given encoded message.
|
||||||
|
@ -226,6 +229,54 @@ var version = exports.version = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var IPv6_LOOPBACK = new Buffer(
|
||||||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check whether given encoded IPv6 or IPv4-mapped IPv6 is in private
|
||||||
|
// network range. See
|
||||||
|
// <https://en.wikipedia.org/wiki/Reserved_IP_addresses> for details.
|
||||||
|
// TODO(Kagami): Do we also need to filter multicasts and other reserved
|
||||||
|
// ranges?
|
||||||
|
function isPrivateIp(buf) {
|
||||||
|
// IPv4.
|
||||||
|
if (bufferEqual(buf.slice(0, 12), IPv4_MAPPING)) {
|
||||||
|
buf = buf.slice(12);
|
||||||
|
if (buf[0] === 127) {
|
||||||
|
return true;
|
||||||
|
} else if (buf[0] === 10) {
|
||||||
|
return true;
|
||||||
|
} else if (buf[0] === 192 && buf[1] === 168) {
|
||||||
|
return true;
|
||||||
|
// XXX(Kagami): ignore:start and ignore:end doesn't ignore this for
|
||||||
|
// some reason. Probably related:
|
||||||
|
// <https://github.com/jshint/jshint/issues/1465>.
|
||||||
|
} else if (buf[0] === 172 && (buf[1] & 0xf0) === 0x10) {//jshint ignore:line
|
||||||
|
return true;
|
||||||
|
} else if (buf[0] === 169 && buf[1] === 254) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// IPv6.
|
||||||
|
} else {
|
||||||
|
if (bufferEqual(buf, IPv6_LOOPBACK)) {
|
||||||
|
return true;
|
||||||
|
} else if (buf[0] === 0xfe && (buf[1] & 0xc0) === 0x80) {//jshint ignore:line
|
||||||
|
return true;
|
||||||
|
} else if ((buf[0] & 0xfe) === 0xfc) { // jshint ignore:line
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to make it easier to filter out private IPs.
|
||||||
|
function checkAddrOpts(opts) {
|
||||||
|
return !isPrivateIp(inet_pton(opts.host));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `addr` message. Provide information on known nodes of the network.
|
* `addr` message. Provide information on known nodes of the network.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#addr}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#addr}
|
||||||
|
@ -260,13 +311,19 @@ var addr = exports.addr = {
|
||||||
decodePayload: function(buf) {
|
decodePayload: function(buf) {
|
||||||
var decoded = structs.var_int.decode(buf);
|
var decoded = structs.var_int.decode(buf);
|
||||||
var listLength = decoded.value;
|
var listLength = decoded.value;
|
||||||
|
// NOTE(Kagami): Check length before filtering private IPs because
|
||||||
|
// we shouldn't even receive them.
|
||||||
assert(listLength <= 1000, "Too many address entires");
|
assert(listLength <= 1000, "Too many address entires");
|
||||||
var length = decoded.length + listLength * 38;
|
var length = decoded.length + listLength * 38;
|
||||||
assert(buf.length >= length, "Buffer is too small");
|
assert(buf.length >= length, "Buffer is too small");
|
||||||
var rest = decoded.rest;
|
var rest = decoded.rest;
|
||||||
var addrs = new Array(listLength);
|
var addrs = [];
|
||||||
|
var addrBuf;
|
||||||
for (var i = 0; i < listLength; i++) {
|
for (var i = 0; i < listLength; i++) {
|
||||||
addrs[i] = structs.net_addr.decode(rest.slice(i*38, (i+1)*38));
|
addrBuf = rest.slice(i*38, (i+1)*38);
|
||||||
|
if (!isPrivateIp(addrBuf.slice(20, 36))) {
|
||||||
|
addrs.push(structs.net_addr.decode(addrBuf));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
addrs: addrs,
|
addrs: addrs,
|
||||||
|
@ -291,6 +348,7 @@ var addr = exports.addr = {
|
||||||
* The same as [encode]{@link module:bitmessage/messages.addr.encode}.
|
* The same as [encode]{@link module:bitmessage/messages.addr.encode}.
|
||||||
*/
|
*/
|
||||||
encodePayload: function(addrs) {
|
encodePayload: function(addrs) {
|
||||||
|
addrs = addrs.filter(checkAddrOpts);
|
||||||
assert(addrs.length <= 1000, "Too many address entires");
|
assert(addrs.length <= 1000, "Too many address entires");
|
||||||
var addrBufs = addrs.map(structs.net_addr.encode);
|
var addrBufs = addrs.map(structs.net_addr.encode);
|
||||||
var bufs = [structs.var_int.encode(addrs.length)].concat(addrBufs);
|
var bufs = [structs.var_int.encode(addrs.length)].concat(addrBufs);
|
||||||
|
|
|
@ -27,11 +27,14 @@
|
||||||
|
|
||||||
var objectAssign = Object.assign || require("object-assign");
|
var objectAssign = Object.assign || require("object-assign");
|
||||||
var bufferEqual = require("buffer-equal");
|
var bufferEqual = require("buffer-equal");
|
||||||
var assert = require("./_util").assert;
|
|
||||||
var bmcrypto = require("./crypto");
|
var bmcrypto = require("./crypto");
|
||||||
var POW = require("./pow");
|
var POW = require("./pow");
|
||||||
var util = require("./_util");
|
var util = require("./_util");
|
||||||
|
|
||||||
|
var assert = util.assert;
|
||||||
|
var IPv4_MAPPING = util.IPv4_MAPPING;
|
||||||
|
var inet_pton = util.inet_pton;
|
||||||
|
|
||||||
function isAscii(str) {
|
function isAscii(str) {
|
||||||
for (var i = 0; i < str.length; i++) {
|
for (var i = 0; i < str.length; i++) {
|
||||||
if (str.charCodeAt(i) > 127) {
|
if (str.charCodeAt(i) > 127) {
|
||||||
|
@ -678,9 +681,6 @@ exports.var_int_list = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// See https://en.wikipedia.org/wiki/IPv6#IPv4-mapped_IPv6_addresses
|
|
||||||
var IPv4_MAPPING = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255]);
|
|
||||||
|
|
||||||
// Very simple inet_ntop(3) equivalent.
|
// Very simple inet_ntop(3) equivalent.
|
||||||
function inet_ntop(buf) {
|
function inet_ntop(buf) {
|
||||||
assert(buf.length === 16, "Bad buffer size");
|
assert(buf.length === 16, "Bad buffer size");
|
||||||
|
@ -689,6 +689,7 @@ function inet_ntop(buf) {
|
||||||
return Array.prototype.join.call(buf.slice(12), ".");
|
return Array.prototype.join.call(buf.slice(12), ".");
|
||||||
// IPv6.
|
// IPv6.
|
||||||
} else {
|
} else {
|
||||||
|
// TODO(Kagami): Join empty groups to make address looks nicer.
|
||||||
var groups = [];
|
var groups = [];
|
||||||
for (var i = 0; i < 8; i++) {
|
for (var i = 0; i < 8; i++) {
|
||||||
groups.push(buf.readUInt16BE(i * 2, true).toString(16));
|
groups.push(buf.readUInt16BE(i * 2, true).toString(16));
|
||||||
|
@ -697,83 +698,6 @@ function inet_ntop(buf) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Very simple inet_pton(3) equivalent.
|
|
||||||
function inet_pton(str) {
|
|
||||||
var buf = new Buffer(16);
|
|
||||||
buf.fill(0);
|
|
||||||
// IPv4-mapped IPv6.
|
|
||||||
if (str.slice(0, 7) === "::ffff:") {
|
|
||||||
str = str.slice(7);
|
|
||||||
}
|
|
||||||
// IPv4.
|
|
||||||
if (str.indexOf(":") === -1) {
|
|
||||||
IPv4_MAPPING.copy(buf);
|
|
||||||
var octets = str.split(/\./g).map(function(o) {
|
|
||||||
assert(/^\d+$/.test(o), "Bad octet");
|
|
||||||
return parseInt(o, 10);
|
|
||||||
});
|
|
||||||
// Support short form from inet_aton(3) man page.
|
|
||||||
if (octets.length === 1) {
|
|
||||||
buf.writeUInt32BE(octets[0], 12);
|
|
||||||
} else {
|
|
||||||
// Check against 1000.bad.addr
|
|
||||||
octets.forEach(function(octet) {
|
|
||||||
assert(octet >= 0, "Bad IPv4 address");
|
|
||||||
assert(octet <= 255, "Bad IPv4 address");
|
|
||||||
});
|
|
||||||
if (octets.length === 2) {
|
|
||||||
buf[12] = octets[0];
|
|
||||||
buf[15] = octets[1];
|
|
||||||
} else if (octets.length === 3) {
|
|
||||||
buf[12] = octets[0];
|
|
||||||
buf[13] = octets[1];
|
|
||||||
buf[15] = octets[2];
|
|
||||||
} else if (octets.length === 4) {
|
|
||||||
buf[12] = octets[0];
|
|
||||||
buf[13] = octets[1];
|
|
||||||
buf[14] = octets[2];
|
|
||||||
buf[15] = octets[3];
|
|
||||||
} else {
|
|
||||||
throw new Error("Bad IPv4 address");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// IPv6.
|
|
||||||
} else {
|
|
||||||
var dgroups = str.split(/::/g);
|
|
||||||
// Check against 1::1::1
|
|
||||||
assert(dgroups.length <= 2, "Bad IPv6 address");
|
|
||||||
var groups = [];
|
|
||||||
var i;
|
|
||||||
if (dgroups[0]) {
|
|
||||||
groups.push.apply(groups, dgroups[0].split(/:/g));
|
|
||||||
}
|
|
||||||
if (dgroups.length === 2) {
|
|
||||||
if (dgroups[1]) {
|
|
||||||
var splitted = dgroups[1].split(/:/g);
|
|
||||||
var fill = 8 - (groups.length + splitted.length);
|
|
||||||
// Check against 1:1:1:1::1:1:1:1
|
|
||||||
assert(fill > 0, "Bad IPv6 address");
|
|
||||||
for (i = 0; i < fill; i++) {
|
|
||||||
groups.push(0);
|
|
||||||
}
|
|
||||||
groups.push.apply(groups, splitted);
|
|
||||||
} else {
|
|
||||||
// Check against 1:1:1:1:1:1:1:1::
|
|
||||||
assert(groups.length <= 7, "Bad IPv6 address");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check against 1:1:1
|
|
||||||
assert(groups.length === 8, "Bad IPv6 address");
|
|
||||||
}
|
|
||||||
for (i = 0; i < Math.min(groups.length, 8); i++) {
|
|
||||||
// Check against parseInt("127.0.0.1", 16) -> 295
|
|
||||||
assert(/^[0-9a-f]+$/.test(groups[i]), "Bad group");
|
|
||||||
buf.writeUInt16BE(parseInt(groups[i], 16), i * 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network address.
|
* Network address.
|
||||||
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Network_address}
|
* @see {@link https://bitmessage.org/wiki/Protocol_specification#Network_address}
|
||||||
|
|
26
test.js
26
test.js
|
@ -556,9 +556,33 @@ describe("Message types", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shouldn't encode/decode more than 1000 entires", function() {
|
it("shouldn't encode/decode more than 1000 entires", function() {
|
||||||
expect(addr.encode.bind(null, Array(2000))).to.throw(/too many/i);
|
var addrs = new Array(1001);
|
||||||
|
var ip = {host: "1.2.3.4"};
|
||||||
|
for (var i = 0; i < 1001; i++) {
|
||||||
|
addrs[i] = ip;
|
||||||
|
}
|
||||||
|
expect(addr.encode.bind(null, addrs)).to.throw(/too many/i);
|
||||||
expect(addr.decodePayload.bind(null, var_int.encode(2000))).to.throw(/too many/i);
|
expect(addr.decodePayload.bind(null, var_int.encode(2000))).to.throw(/too many/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should filter out private IP ranges", function() {
|
||||||
|
expect(addr.encodePayload([
|
||||||
|
{host: "127.0.0.1", port: 1},
|
||||||
|
{host: "127.5.3.1", port: 2},
|
||||||
|
{host: "1.2.3.4", port: 3, time: new Date(1425034238202)},
|
||||||
|
{host: "192.168.15.20", port: 4},
|
||||||
|
{host: "10.10.10.10", port: 5},
|
||||||
|
{host: "172.17.42.1", port: 6},
|
||||||
|
{host: "::1", port: 7},
|
||||||
|
{host: "fe80::1:2:3", port: 8},
|
||||||
|
{host: "fc00::3:2:1", port: 9},
|
||||||
|
]).toString("hex")).to.equal("010000000054f04bfe00000001000000000000000100000000000000000000ffff010203040003");
|
||||||
|
|
||||||
|
var res = addr.decodePayload(Buffer("090000000054f04b9b00000001000000000000000100000000000000000000ffff7f00000100010000000054f04b9b00000001000000000000000100000000000000000000ffff7f05030100020000000054f04b9b00000001000000000000000100000000000000000000ffff0102030400030000000054f04b9b00000001000000000000000100000000000000000000ffffc0a80f1400040000000054f04b9b00000001000000000000000100000000000000000000ffff0a0a0a0a00050000000054f04b9b00000001000000000000000100000000000000000000ffffac112a0100060000000054f04b9b0000000100000000000000010000000000000000000000000000000100070000000054f04b9b000000010000000000000001fe80000000000000000000010002000300080000000054f04b9b000000010000000000000001fc0000000000000000000003000200010009", "hex"));
|
||||||
|
expect(res.addrs).to.have.length(1);
|
||||||
|
expect(res.addrs[0].host).to.equal("1.2.3.4");
|
||||||
|
expect(res.addrs[0].port).to.equal(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("inv", function() {
|
describe("inv", function() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user