UNPKG

15.9 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const crypto_1 = require("crypto");
4const ilp_packet_1 = require("ilp-packet");
5const log_1 = require("../common/log");
6const log = log_1.create('route-broadcaster');
7const lodash_1 = require("lodash");
8const routing_table_1 = require("./routing-table");
9const forwarding_routing_table_1 = require("./forwarding-routing-table");
10const accounts_1 = require("./accounts");
11const config_1 = require("./config");
12const peer_1 = require("../routing/peer");
13const dragon_1 = require("../routing/dragon");
14const relation_1 = require("../routing/relation");
15const utils_1 = require("../routing/utils");
16const utils_2 = require("../lib/utils");
17const { BadRequestError } = ilp_packet_1.Errors;
18class RouteBroadcaster {
19 constructor(deps) {
20 this.untrackCallbacks = new Map();
21 this.getAccountRelation = (accountId) => {
22 return accountId ? this.accounts.getInfo(accountId).relation : 'local';
23 };
24 this.deps = deps;
25 this.localRoutingTable = deps(routing_table_1.default);
26 this.forwardingRoutingTable = deps(forwarding_routing_table_1.default);
27 this.accounts = deps(accounts_1.default);
28 this.config = deps(config_1.default);
29 if (this.config.routingSecret) {
30 log.info('loaded routing secret from config.');
31 this.routingSecret = Buffer.from(this.config.routingSecret, 'base64');
32 }
33 else {
34 log.info('generated random routing secret.');
35 this.routingSecret = crypto_1.randomBytes(32);
36 }
37 this.peers = new Map();
38 this.localRoutes = new Map();
39 }
40 start() {
41 this.reloadLocalRoutes();
42 for (const accountId of this.accounts.getAccountIds()) {
43 this.track(accountId);
44 }
45 }
46 stop() {
47 for (const accountId of this.peers.keys()) {
48 this.remove(accountId);
49 }
50 }
51 track(accountId) {
52 if (this.untrackCallbacks.has(accountId)) {
53 return;
54 }
55 const plugin = this.accounts.getPlugin(accountId);
56 const connectHandler = () => {
57 if (!plugin.isConnected()) {
58 log.error('(!!!) plugin emitted connect, but then returned false for isConnected, broken plugin. account=%s', accountId);
59 setImmediate(() => this.add(accountId));
60 }
61 else {
62 this.add(accountId);
63 }
64 };
65 const disconnectHandler = () => {
66 this.remove(accountId);
67 };
68 plugin.on('connect', connectHandler);
69 plugin.on('disconnect', disconnectHandler);
70 this.untrackCallbacks.set(accountId, () => {
71 plugin.removeListener('connect', connectHandler);
72 plugin.removeListener('disconnect', disconnectHandler);
73 });
74 this.add(accountId);
75 }
76 untrack(accountId) {
77 this.remove(accountId);
78 const callback = this.untrackCallbacks.get(accountId);
79 if (callback) {
80 callback();
81 }
82 }
83 add(accountId) {
84 const accountInfo = this.accounts.getInfo(accountId);
85 let sendRoutes;
86 if (typeof accountInfo.sendRoutes === 'boolean') {
87 sendRoutes = accountInfo.sendRoutes;
88 }
89 else if (accountInfo.relation !== 'child') {
90 sendRoutes = true;
91 }
92 else {
93 sendRoutes = false;
94 }
95 let receiveRoutes;
96 if (typeof accountInfo.receiveRoutes === 'boolean') {
97 receiveRoutes = accountInfo.receiveRoutes;
98 }
99 else if (accountInfo.relation !== 'child') {
100 receiveRoutes = true;
101 }
102 else {
103 receiveRoutes = false;
104 }
105 if (!sendRoutes && !receiveRoutes) {
106 log.warn('not sending/receiving routes for peer, set sendRoutes/receiveRoutes to override. accountId=%s', accountId);
107 return;
108 }
109 const existingPeer = this.peers.get(accountId);
110 if (existingPeer) {
111 const receiver = existingPeer.getReceiver();
112 if (receiver) {
113 receiver.sendRouteControl();
114 }
115 else {
116 log.warn('unable to send route control message, receiver object undefined. peer=%s', existingPeer);
117 }
118 return;
119 }
120 const plugin = this.accounts.getPlugin(accountId);
121 if (plugin.isConnected()) {
122 log.trace('add peer. accountId=%s sendRoutes=%s receiveRoutes=%s', accountId, sendRoutes, receiveRoutes);
123 const peer = new peer_1.default({ deps: this.deps, accountId, sendRoutes, receiveRoutes });
124 this.peers.set(accountId, peer);
125 const receiver = peer.getReceiver();
126 if (receiver) {
127 receiver.sendRouteControl();
128 }
129 this.reloadLocalRoutes();
130 }
131 }
132 remove(accountId) {
133 const peer = this.peers.get(accountId);
134 if (!peer) {
135 return;
136 }
137 const sender = peer.getSender();
138 const receiver = peer.getReceiver();
139 log.trace('remove peer. peerId=' + accountId);
140 if (sender) {
141 sender.stop();
142 }
143 this.peers.delete(accountId);
144 if (receiver) {
145 for (let prefix of receiver.getPrefixes()) {
146 this.updatePrefix(prefix);
147 }
148 }
149 if (this.getAccountRelation(accountId) === 'child') {
150 this.updatePrefix(this.accounts.getChildAddress(accountId));
151 }
152 }
153 handleRouteControl(sourceAccount, routeControl) {
154 const peer = this.peers.get(sourceAccount);
155 if (!peer) {
156 log.debug('received route control message from non-peer. sourceAccount=%s', sourceAccount);
157 throw new BadRequestError('cannot process route control messages from non-peers.');
158 }
159 const sender = peer.getSender();
160 if (!sender) {
161 log.debug('received route control message from peer not authorized to receive routes from us (sendRoutes=false). sourceAccount=%s', sourceAccount);
162 throw new BadRequestError('rejecting route control message, we are configured not to send routes to you.');
163 }
164 sender.handleRouteControl(routeControl);
165 }
166 handleRouteUpdate(sourceAccount, routeUpdate) {
167 const peer = this.peers.get(sourceAccount);
168 if (!peer) {
169 log.debug('received route update from non-peer. sourceAccount=%s', sourceAccount);
170 throw new BadRequestError('cannot process route update messages from non-peers.');
171 }
172 const receiver = peer.getReceiver();
173 if (!receiver) {
174 log.debug('received route update from peer not authorized to advertise routes to us (receiveRoutes=false). sourceAccount=%s', sourceAccount);
175 throw new BadRequestError('rejecting route update, we are configured not to receive routes from you.');
176 }
177 routeUpdate.newRoutes = routeUpdate.newRoutes
178 .filter(route => route.prefix.startsWith(this.getGlobalPrefix()) &&
179 route.prefix.length > this.getGlobalPrefix().length)
180 .filter(route => !route.path.includes(this.accounts.getOwnAddress()));
181 const changedPrefixes = receiver.handleRouteUpdate(routeUpdate);
182 let haveRoutesChanged;
183 for (const prefix of changedPrefixes) {
184 haveRoutesChanged = this.updatePrefix(prefix) || haveRoutesChanged;
185 }
186 if (haveRoutesChanged && this.config.routeBroadcastEnabled) {
187 for (const peer of this.peers.values()) {
188 const sender = peer.getSender();
189 if (sender) {
190 sender.scheduleRouteUpdate();
191 }
192 }
193 }
194 }
195 reloadLocalRoutes() {
196 log.trace('reload local and configured routes.');
197 this.localRoutes = new Map();
198 const localAccounts = this.accounts.getAccountIds();
199 const ownAddress = this.accounts.getOwnAddress();
200 this.localRoutes.set(ownAddress, {
201 nextHop: '',
202 path: [],
203 auth: utils_2.hmac(this.routingSecret, ownAddress)
204 });
205 let defaultRoute = this.config.defaultRoute;
206 if (defaultRoute === 'auto') {
207 defaultRoute = localAccounts.filter(id => this.accounts.getInfo(id).relation === 'parent')[0];
208 }
209 if (defaultRoute) {
210 const globalPrefix = this.getGlobalPrefix();
211 this.localRoutes.set(globalPrefix, {
212 nextHop: defaultRoute,
213 path: [],
214 auth: utils_2.hmac(this.routingSecret, globalPrefix)
215 });
216 }
217 for (let accountId of localAccounts) {
218 if (this.getAccountRelation(accountId) === 'child') {
219 const childAddress = this.accounts.getChildAddress(accountId);
220 this.localRoutes.set(childAddress, {
221 nextHop: accountId,
222 path: [],
223 auth: utils_2.hmac(this.routingSecret, childAddress)
224 });
225 }
226 }
227 const localPrefixes = Array.from(this.localRoutes.keys());
228 const configuredPrefixes = this.config.routes
229 ? this.config.routes.map(r => r.targetPrefix)
230 : [];
231 for (let prefix of localPrefixes.concat(configuredPrefixes)) {
232 this.updatePrefix(prefix);
233 }
234 }
235 updatePrefix(prefix) {
236 const newBest = this.getBestPeerForPrefix(prefix);
237 return this.updateLocalRoute(prefix, newBest);
238 }
239 getBestPeerForPrefix(prefix) {
240 const configuredRoute = lodash_1.find(this.config.routes, { targetPrefix: prefix });
241 if (configuredRoute) {
242 if (this.accounts.exists(configuredRoute.peerId)) {
243 return {
244 nextHop: configuredRoute.peerId,
245 path: [],
246 auth: utils_2.hmac(this.routingSecret, prefix)
247 };
248 }
249 else {
250 log.warn('ignoring configured route, account does not exist. prefix=%s accountId=%s', configuredRoute.targetPrefix, configuredRoute.peerId);
251 }
252 }
253 const localRoute = this.localRoutes.get(prefix);
254 if (localRoute) {
255 return localRoute;
256 }
257 const weight = (route) => {
258 const relation = this.getAccountRelation(route.peer);
259 return relation_1.getRelationPriority(relation);
260 };
261 const bestRoute = Array.from(this.peers.values())
262 .map(peer => peer.getReceiver())
263 .map(receiver => receiver && receiver.getPrefix(prefix))
264 .filter((a) => !!a)
265 .sort((a, b) => {
266 if (!a && !b) {
267 return 0;
268 }
269 else if (!a) {
270 return 1;
271 }
272 else if (!b) {
273 return -1;
274 }
275 const weightA = weight(a);
276 const weightB = weight(b);
277 if (weightA !== weightB) {
278 return weightB - weightA;
279 }
280 const pathA = a.path.length;
281 const pathB = b.path.length;
282 if (pathA !== pathB) {
283 return pathA - pathB;
284 }
285 if (a.peer > b.peer) {
286 return 1;
287 }
288 else if (b.peer > a.peer) {
289 return -1;
290 }
291 else {
292 return 0;
293 }
294 })[0];
295 return bestRoute && {
296 nextHop: bestRoute.peer,
297 path: bestRoute.path,
298 auth: bestRoute.auth
299 };
300 }
301 getGlobalPrefix() {
302 switch (this.config.env) {
303 case 'production':
304 return 'g';
305 case 'test':
306 return 'test';
307 default:
308 throw new Error('invalid value for `env` config. env=' + this.config.env);
309 }
310 }
311 getStatus() {
312 return {
313 routingTableId: this.forwardingRoutingTable.routingTableId,
314 currentEpoch: this.forwardingRoutingTable.currentEpoch,
315 localRoutingTable: utils_1.formatRoutingTableAsJson(this.localRoutingTable),
316 forwardingRoutingTable: utils_1.formatForwardingRoutingTableAsJson(this.forwardingRoutingTable),
317 routingLog: this.forwardingRoutingTable.log
318 .filter(Boolean)
319 .map(entry => (Object.assign({}, entry, { route: entry && entry.route && utils_1.formatRouteAsJson(entry.route) }))),
320 peers: Array.from(this.peers.values()).reduce((acc, peer) => {
321 const sender = peer.getSender();
322 const receiver = peer.getReceiver();
323 acc[peer.getAccountId()] = {
324 send: sender && sender.getStatus(),
325 receive: receiver && receiver.getStatus()
326 };
327 return acc;
328 }, {})
329 };
330 }
331 updateLocalRoute(prefix, route) {
332 const currentBest = this.localRoutingTable.get(prefix);
333 const currentNextHop = currentBest && currentBest.nextHop;
334 const newNextHop = route && route.nextHop;
335 if (newNextHop !== currentNextHop) {
336 if (route) {
337 log.trace('new best route for prefix. prefix=%s oldBest=%s newBest=%s', prefix, currentNextHop, newNextHop);
338 this.localRoutingTable.insert(prefix, route);
339 }
340 else {
341 log.trace('no more route available for prefix. prefix=%s', prefix);
342 this.localRoutingTable.delete(prefix);
343 }
344 this.updateForwardingRoute(prefix, route);
345 return true;
346 }
347 return false;
348 }
349 updateForwardingRoute(prefix, route) {
350 if (route) {
351 route = Object.assign({}, route, { path: [this.accounts.getOwnAddress(), ...route.path], auth: utils_2.sha256(route.auth) });
352 if (!prefix.startsWith(this.getGlobalPrefix()) ||
353 prefix === this.getGlobalPrefix() ||
354 (prefix.startsWith(this.accounts.getOwnAddress() + '.') &&
355 route.path.length === 1) ||
356 dragon_1.canDragonFilter(this.forwardingRoutingTable, this.getAccountRelation, prefix, route)) {
357 route = undefined;
358 }
359 }
360 const currentBest = this.forwardingRoutingTable.get(prefix);
361 const currentNextHop = currentBest && currentBest.route && currentBest.route.nextHop;
362 const newNextHop = route && route.nextHop;
363 if (currentNextHop !== newNextHop) {
364 const epoch = this.forwardingRoutingTable.currentEpoch++;
365 const routeUpdate = {
366 prefix,
367 route,
368 epoch
369 };
370 this.forwardingRoutingTable.insert(prefix, routeUpdate);
371 log.trace('logging route update. update=%j', routeUpdate);
372 if (currentBest) {
373 this.forwardingRoutingTable.log[currentBest.epoch] = null;
374 }
375 this.forwardingRoutingTable.log[epoch] = routeUpdate;
376 if (route) {
377 const subPrefixes = this.forwardingRoutingTable.getKeysStartingWith(prefix);
378 for (const subPrefix of subPrefixes) {
379 if (subPrefix === prefix)
380 continue;
381 const routeUpdate = this.forwardingRoutingTable.get(subPrefix);
382 if (!routeUpdate || !routeUpdate.route)
383 continue;
384 this.updateForwardingRoute(subPrefix, routeUpdate.route);
385 }
386 }
387 }
388 }
389}
390exports.default = RouteBroadcaster;
391//# sourceMappingURL=route-broadcaster.js.map
\No newline at end of file