summaryrefslogtreecommitdiff
path: root/js/models/relay.js
blob: 91bde2a73b72c161b599c59c3e529cd782e92a2f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// ~ models/relay ~
define([
  'jquery',
  'underscore',
  'backbone',
  'helpers',
  'fallbackdir'
], function($, _, Backbone){

	var relayModel = Backbone.Model.extend({
        baseurl: 'https://onionoo.torproject.org',
        fingerprint: '',
        parseflags: function(flags, is_bridge) {
            var output = [];
            var model = this;
            _.each(flags, function(flag) {
                if (flag == "Authority") {
                    output.push([flag, "authority", "This relay is a directory authority."]);
                }
                if (flag == "BadExit") {
                    model.set({badexit: true});
                    output.push([flag, "badexit", "This relay is believed to be useless as an exit node (because its ISP censors it, because it is behind a restrictive proxy, or for some similar reason)."]);
                }
                if (flag == "Fast") {
                    output.push([flag, "fast", "This relay is suitable for high-bandwidth circuits."]);
                }
                if (flag == "Guard") {
                    if (!is_bridge) {
                        output.push([flag, "guard", "This relay is suitable for use as an entry guard."]);
                    }
                }
                if (flag == "HSDir") {
                    if (!is_bridge) {
                        output.push([flag, "hsdir", "This relay is considered a v2 hidden service directory."]);
                    }
                }
                if (flag == "NoEdConsensus") {
                    output.push([flag, "noedconensus", "An Ed25519 key in the relay's descriptor or microdesriptor does not reflect authority consensus."]);
                }
                if (flag == "Running") {
                    output.push([flag, "running", "This relay is currently usable."]);
                }
                if (flag == "Stable") {
                    output.push([flag, "stable", "This relay is suitable for long-lived circuits."]);
                }
                if (flag == "V2Dir") {
                    output.push([flag, "v2dir", "This relay implements the v2 directory protocol or higher."]);
                }
                if (flag == "Valid") {
                    output.push([flag, "valid", "This relay has been 'validated'."]);
                }
                if (flag == "Exit") {
                    output.push([flag, "exit", "This relay is more useful for building general-purpose exit circuits than for relay circuits."]);
                }
            });
            return output;
        },
        parseadditionalflags: function(flags) {
            var output = [];
            var model = this;
            _.each(flags, function(flag) {
                if (flag == "Not Recommended") {
                    output.push([flag,"notrecommended", "This relay is running a Tor version that is not recommended by the directory authorities and may contain known issues."]);
                }
                if (flag == "Unmeasured") {
                    output.push([flag,"unmeasured", "This relay has not been measured by at least 3 bandwidth authorities and so its consensus weight is currently capped. This is expected for new relays, but may also be found on established relays at times. This is more likely to be an issue with the bandwidth authorities than an issue with your relay."]);
                }
                if (flag == "FallbackDir") {
                    output.push([flag,"fallbackdir", "Tor clients contact fallback directory mirrors during bootstrap, and download the consensus and authority certificates from them. We include a default list of mirrors in the Tor source code. These default mirrors need to be long-term stable, and on the same IPv4 and IPv6 addresses and ports."]);
                }
                if (flag == "ReachableIPv6") {
                    output.push([flag,"reachableipv6", "This relay claims to accept OR connections using IPv6 and the directory authorities have confirmed it is reachable."]);
                }
                if (flag == "UnreachableIPv6") {
                    output.push([flag,"unreachableipv6", "This relay claims to accept OR connections using IPv6 but the directory authorities failed to confirm it was reachable."]);
                }
                if (flag == "UnreachableIPv4") {
                    output.push([flag,"unreachableipv4", "This relay claims to accept OR connections using IPv4 but the directory authorities failed to confirm it was reachable."]);
                }
                if (flag == "IPv6 Exit") {
                    output.push([flag, "ipv6exit", "This relay allows exit connections using IPv6."]);
                }
                if (flag == "Hibernating") {
                    output.push([flag, "hibernating", "This relay indicated that it is hibernating in its last known server descriptor."]);
                }
            });
            return output;
        },
        parsedate: function(utctime) {
            var hr_magic = [10];
            var t = utctime.split(" ");
            var utcd = t[0].split("-");
            var utct = t[1].split(":");
            var d = new Date(utcd[0], utcd[1]-1, utcd[2], utct[0], utct[1], utct[2]);
            var now = new Date();
            now = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(),  now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds());
            var diff = now-d;
            var secs = Math.round(diff/1000);
            var mins = Math.floor(secs/60);
            var hours = Math.floor(mins/60);
            var days = Math.floor(hours/24);
            var years = Math.floor(days/365);
            // XXX check if this formula is correct.
            secs = secs % 60;
            mins = mins % 60;
            hours = hours % 24;
            days = days % 365;

            var hr_date = "";
            var hr_date_full = "";
            var hr = 0;

            if (years > 0) {
                hr_date += years + "y ";
                hr += 1;
                if (years > 1) {
                    hr_date_full += years + " years ";
                } else {
                    hr_date_full += years + " year ";
                }
            }

            if (days > 0) {
                hr_date += days + "d ";
                hr += 1;
                if (days > 1) {
                    hr_date_full += days + " days ";
                } else {
                    hr_date_full += days + " day ";
                }
            }

            if (hours > 0) {
                hr_date += hours + "h ";
                hr += 1;
                if (hours > 1) {
                    hr_date_full += hours + " hours ";
                } else {
                    hr_date_full += hours + " hour ";
                }
            }


            if (mins > 0) {
                if (hr < 2) {
                    hr_date += mins + "m ";
                    hr += 1;
                }
                if (hours > 1) {
                    hr_date_full += mins + " minutes ";
                } else {
                    hr_date_full += mins + " minute ";
                }
            }

            if (hr < 2) {
                hr_date += secs + "s ";
                hr += 1;
            }
            if (hr > 1) {
                hr_date_full += "and ";
            }
            if (secs > 1) {
                hr_date_full += secs + " seconds";
            } else {
                hr_date_full += secs + " second";
            }
            var output = {hrfull: hr_date_full, hr: hr_date, millisecs: diff};
            return output

        },
        is_new: function(utctime) {
            var hr_magic = [10];
            var t = utctime.split(" ");
            var utcd = t[0].split("-");
            var utct = t[1].split(":");
            var d = new Date(utcd[0], utcd[1]-1, utcd[2], utct[0], utct[1], utct[2]);
            var now = new Date();
            now = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(),  now.getUTCHours(), now.getUTCMinutes(), now.getUTCSeconds());
            var diff = now-d;
            var secs = Math.round(diff/1000);
            var mins = Math.floor(secs/60);
            var hours = Math.floor(mins/60);
            var days = Math.floor(hours/24);
            return days < 15;
        },
        processRelay: function(options, model, relay) {
                    relay.contact = relay.contact ? relay.contact : 'undefined';
                    relay.platform = relay.platform ? relay.platform : null;
                    relay.dir_address = relay.dir_address ? relay.dir_address : null;
                    relay.exit_policy = relay.exit_policy ? relay.exit_policy : null;
                    relay.exit_policy_summary = relay.exit_policy_summary ?  relay.exit_policy_summary : null;
                    relay.exit_policy_v6_summary = relay.exit_policy_v6_summary ?  relay.exit_policy_v6_summary : null;
                    relay.bandwidthr = (typeof relay.bandwidth_rate !== 'undefined') ? hrBandwidth(relay.bandwidth_rate) : null;
                    relay.bandwidthb = (typeof relay.bandwidth_burst !== 'undefined') ? hrBandwidth(relay.bandwidth_burst) : null;
                    relay.obandwidth = (typeof relay.observed_bandwidth !== 'undefined') ? hrBandwidth(relay.observed_bandwidth) : null;
                    relay.bandwidth = (typeof relay.advertised_bandwidth !== 'undefined') ? relay.advertised_bandwidth : null;
                    relay.bandwidth_hr = (typeof relay.advertised_bandwidth !== 'undefined') ? hrBandwidth(relay.advertised_bandwidth) : null;
                    // the filter here is a temporary fix, and can be removed again later if you notice that #25241 is fixed
                    relay.effective_family = relay.effective_family ? relay.effective_family.filter(function(x){return x !== relay.fingerprint}) : null;
                    relay.alleged_family = relay.alleged_family ? relay.alleged_family : null;
                    if (relay.is_bridge) {
                        var new_addresses = [];
                        _.each(relay.or_addresses, function(or_addr) {
                            var addr = or_addr[0] == '[' ? "IPv6" : "IPv4";
                            new_addresses.push(addr);
                        });
                        relay.or_addresses = new_addresses;
                    }
                    relay.or_address = relay.or_addresses ? relay.or_addresses[0].split(":")[0] : null;
                    relay.unreachable_or_addresses = relay.unreachable_or_addresses ? relay.unreachable_or_addresses : [];
                    relay.or_v6_addresses = $.grep(relay.or_addresses, function(n, i) { return n.indexOf("[") == 0; });
                    relay.or_v6_address = (relay.or_v6_addresses.length > 0) ? relay.or_v6_addresses[0].split("]")[0].replace(/\[/, "") : null;
                    relay.unreachable_or_v4_addresses = $.grep(relay.unreachable_or_addresses, function(n, i) { return n.indexOf(".") != -1; });
                    relay.unreachable_or_v6_addresses = $.grep(relay.unreachable_or_addresses, function(n, i) { return n.indexOf("[") == 0; });
                    relay.or_port = relay.or_addresses ? relay.or_addresses[0].split(":")[1] : 0;
                    relay.dir_port = relay.dir_address ? relay.dir_address.split(":")[1] : 0;
                    relay.exit_addresses = relay.exit_addresses ? relay.exit_addresses : null;
                    relay.host_name = relay.host_name ? relay.host_name : null;
                    relay.country = relay.country ? relay.country.toLowerCase() : null;
                    relay.countryname = relay.country ? CountryCodes[relay.country] : null;
                    relay.age = relay.first_seen ? model.parsedate(relay.first_seen).hrfull : null;
                    relay.new_relay = relay.first_seen ? model.is_new(relay.first_seen) : null;
                    relay.uptime = relay.last_restarted ? model.parsedate(relay.last_restarted) : null;
                    relay.uptime_hr = relay.last_restarted ? relay.uptime.hr : null;
                    relay.uptime_hrfull = relay.last_restarted ? relay.uptime.hrfull : null;
                    relay.uptime = relay.last_restarted ? relay.uptime.millisecs : null;
                    relay.last_restarted = relay.last_restarted ? relay.last_restarted : null;
                    relay.downtime = relay.last_seen ? model.parsedate(relay.last_seen).hrfull : null;
                    relay.as_number = relay.as_number ? relay.as_number : null;
                    relay.as_name = relay.as_name ? relay.as_name : null;
                    relay.transports = relay.transports ? relay.transports : null;
                    relay.fingerprint = relay.hashed_fingerprint ? relay.hashed_fingerprint : relay.fingerprint;
                    model.set({badexit: false});
                    relay.flags = model.parseflags(relay.flags, relay.is_bridge);

                    relay.version_consistent = relay.version == relay.platform.split(" ")[1];

                    /* Synthetic Additional Flags */
                    var additional_flags = []
                    if (!((typeof relay.recommended_version !== 'undefined') ? relay.recommended_version : false) && relay.version_consistent) additional_flags.push("Not Recommended");
                    if (!((typeof relay.measured !== 'undefined') ? relay.measured : true)) additional_flags.push("Unmeasured");
                    if (((typeof relay.hibernating !== 'undefined') ? relay.hibernating : false)) additional_flags.push("Hibernating");
                    if (IsFallbackDir(relay.fingerprint)) additional_flags.push("FallbackDir");
                    if (relay.or_v6_addresses.length > 0) additional_flags.push("ReachableIPv6");
                    if (relay.unreachable_or_v4_addresses.length > 0) additional_flags.push("UnreachableIPv4");
                    if (relay.unreachable_or_v6_addresses.length > 0) additional_flags.push("UnreachableIPv6");
                    if (relay.exit_policy_v6_summary !== null) additional_flags.push("IPv6 Exit");

                    relay.additional_flags = model.parseadditionalflags(additional_flags);

                    model.set(relay, options);

        },
        lookup: function(options) {
            var success = options.success;
            var error = options.error;
            var model = this;
            if (model.relay) {
                var relay = model.relay;
                model.processRelay(options, model, relay);
                success(model, relay);
            } else {
                var xhr = $.getJSON(this.baseurl+'/details?lookup='+this.fingerprint, function(data) {
                    checkIfDataIsUpToDate(xhr.getResponseHeader("Last-Modified"));
                    var relay = null;
                    if (data.relays.length >= 1) {
                        relay = data.relays[0];
                        relay.is_bridge = false;
                    } else if (data.bridges.length >= 1) {
                        relay = data.bridges[0];
                        relay.is_bridge = true;
                    }
                    if (relay) {
                        relay.bridgesPublished = data.bridges_published;
                        relay.relaysPublished = data.relays_published;
                        model.processRelay(options, model, relay);
                        success(model, relay);
                    } else {
                        error(model)
                    }
                }).fail(function() {
                    error();
                });
            }
        }

	});

	return relayModel;
});