Vydané zdroje
Stáhnout zdroje ZIP
CH-J SM Mobile
Procházejte adresáře a soubory konkrétního vydání aplikace.
CH-J / SM Mobile
175 souborů
sm-mobile-20261016T185235Z-8e93ecc1b59a
Přidáno: · Sestaveno:
1
import 'dart:convert';2
import 'dart:io';3
import 'icmp_probe.dart';4
import 'measure.dart';6
/// The usual traceroute notation for ICMP destination unreachable codes:7
/// !N network, !H host, !P protocol, !F fragmentation needed, !X8
/// administratively prohibited; '' for port unreachable (expected for UDP).9
String unreachableMarker(int? code, {required bool v6}) {10
if (code == null) return '!';11
if (v6) {12
return switch (code) {13
0 => '!N',14
1 || 5 || 6 => '!X',15
3 => '!H',16
4 => '',17
_ => '!<$code>',18
};19
}20
return switch (code) {21
0 || 6 => '!N',22
1 || 7 => '!H',23
2 => '!P',24
3 => '',25
4 => '!F',26
9 || 10 || 13 => '!X',27
_ => '!<$code>',28
};29
}31
/// One hop over all repetitions of a traceroute.32
class HopAggregate {33
final int ttl;35
/// Responding addresses with how often each answered.36
final addresses = <String, int>{};37
final stats = StatsAccumulator();39
/// Probes of the latest repetition, in order.40
List<ProbeEvent> latest = [];41
bool reached = false;42
final markers = <String>{};43
HopAggregate(this.ttl);45
void add(ProbeEvent e, Family family, int cycle) {46
if (e.from != null) {47
addresses.update(e.from!.address, (n) => n + 1, ifAbsent: () => 1);48
}49
final answered = e.rttMs != null && e.status != ProbeStatus.timeout;50
stats.add(51
Sample(52
seq: e.seq,53
time: e.time ?? DateTime.now(),54
family: family,55
address: e.from?.address ?? '*',56
rttMs: answered ? e.rttMs : null,57
outcome: answered58
? Outcome.ok59
: e.status == ProbeStatus.sendFailed60
? Outcome.error61
: Outcome.timeout,62
),63
);64
if (e.status == ProbeStatus.reply) reached = true;65
if (e.status == ProbeStatus.unreachable) {66
final m = unreachableMarker(e.icmpCode, v6: family == Family.v6);67
if (m.isNotEmpty) markers.add(m);68
}69
}70
}72
/// A traceroute of one IP version, possibly repeated.73
class TraceLane {74
final Family family;75
final InternetAddress target;76
final hops = <int, HopAggregate>{};77
int cycles = 0;78
TraceLane(this.family, this.target);80
/// Starts a new repetition: the latest probes of every hop are cleared.81
void nextCycle() {82
cycles++;83
for (final h in hops.values) {84
h.latest = [];85
}86
}88
void add(ProbeEvent e) {89
final hop = hops.putIfAbsent(e.ttl, () => HopAggregate(e.ttl));90
hop.latest = [...hop.latest, e];91
hop.add(e, family, cycles);92
}94
List<HopAggregate> get ordered =>95
(hops.values.toList()..sort((a, b) => a.ttl.compareTo(b.ttl)));97
bool get reached => hops.values.any((h) => h.reached);99
/// A compact snapshot for comparing two measurements.100
Map<int, HopSnapshot> snapshot() => {101
for (final h in ordered)102
h.ttl: HopSnapshot(103
h.addresses.keys.toSet(),104
h.stats.stats().mean,105
h.stats.stats().lossPercent,106
),107
};108
}110
class HopSnapshot {111
final Set<String> addresses;112
final double? meanMs;113
final double lossPercent;114
const HopSnapshot(this.addresses, this.meanMs, this.lossPercent);115
}117
/// The difference of one hop between an earlier and a later measurement.118
class HopChange {119
final int ttl;120
final HopSnapshot? before, after;121
const HopChange(this.ttl, this.before, this.after);123
/// The responding routers differ (or a hop appeared / disappeared).124
bool get pathChanged =>125
before == null ||126
after == null ||127
(before!.addresses.isNotEmpty &&128
after!.addresses.isNotEmpty &&129
before!.addresses.intersection(after!.addresses).isEmpty);131
double? get meanDelta => before?.meanMs == null || after?.meanMs == null132
? null133
: after!.meanMs! - before!.meanMs!;134
}136
List<HopChange> compareTraces(137
Map<int, HopSnapshot> before,138
Map<int, HopSnapshot> after,139
) {140
final ttls = {...before.keys, ...after.keys}.toList()..sort();141
return [for (final t in ttls) HopChange(t, before[t], after[t])];142
}144
String _csv(Object? v) {145
final s = v?.toString() ?? '';146
return s.contains(RegExp(r'[",\n\r]')) ? '"${s.replaceAll('"', '""')}"' : s;147
}149
String _n(double? v) => v == null ? '' : v.toStringAsFixed(3);151
String _probeText(ProbeEvent p, bool v6) {152
if (p.status == ProbeStatus.timeout) return '*';153
if (p.status == ProbeStatus.sendFailed) return '! (${p.note ?? 'send'})';154
final marker = p.status == ProbeStatus.unreachable155
? unreachableMarker(p.icmpCode, v6: v6)156
: '';157
return '${_n(p.rttMs)} ms${marker.isEmpty ? '' : ' $marker'}';158
}160
/// A traceroute report of [lanes]. [names] maps addresses to reverse DNS161
/// names where known.162
String traceReport(163
ReportFormat format, {164
required String host,165
required IpMode mode,166
required Map<String, Object?> settings,167
required List<TraceLane> lanes,168
Map<String, String> names = const {},169
}) {170
switch (format) {171
case ReportFormat.json:172
return const JsonEncoder.withIndent(' ').convert({173
'tool': 'SM Mobile',174
'method': 'traceroute',175
'host': host,176
'ipMode': mode.name,177
'settings': settings,178
'routes': [179
for (final lane in lanes)180
{181
'ipVersion': lane.family.label,182
'target': lane.target.address,183
'repetitions': lane.cycles,184
'reached': lane.reached,185
'hops': [186
for (final h in lane.ordered)187
{188
'hop': h.ttl,189
'addresses': [190
for (final a in h.addresses.keys)191
{'address': a, 'hostname': ?names[a]},192
],193
'statistics': h.stats.stats().toJson(),194
'markers': h.markers.toList(),195
'reached': h.reached,196
'latestProbes': [197
for (final p in h.latest)198
{199
'probe': p.seq,200
'status': p.status.name,201
'from': p.from?.address,202
'rttMs': p.status == ProbeStatus.timeout203
? null204
: p.rttMs,205
'icmpCode': p.icmpCode,206
'note': p.note,207
},208
],209
},210
],211
},212
],213
});214
case ReportFormat.csv:215
final b = StringBuffer(216
'ip_version,hop,probe,status,address,hostname,rtt_ms,icmp_code,note\n',217
);218
for (final lane in lanes) {219
for (final h in lane.ordered) {220
for (final p in h.latest) {221
final a = p.from?.address;222
b.writeln(223
[224
lane.family.label,225
h.ttl,226
p.seq,227
p.status.name,228
a ?? '',229
a == null ? '' : names[a] ?? '',230
p.status == ProbeStatus.timeout ? '' : _n(p.rttMs),231
p.icmpCode ?? '',232
p.note ?? '',233
].map(_csv).join(','),234
);235
}236
}237
}238
return b.toString();239
case ReportFormat.txt:240
final b = StringBuffer('SM Mobile · traceroute · $host (${mode.name})\n');241
settings.forEach((k, v) => b.writeln('$k: $v'));242
for (final lane in lanes) {243
b244
..writeln()245
..writeln(246
'${lane.family.label} → ${lane.target.address}'247
'${lane.cycles > 1 ? ' (${lane.cycles}×)' : ''}',248
);249
for (final h in lane.ordered) {250
final who = h.addresses.keys251
.map((a) => names[a] == null ? a : '${names[a]} ($a)')252
.join(', ');253
final st = h.stats.stats();254
b.writeln(255
'${h.ttl.toString().padLeft(2)} ${who.isEmpty ? '*' : who} '256
'${h.latest.map((p) => _probeText(p, lane.family == Family.v6)).join(' ')}'257
'${st.successes > 0 ? ' [min/avg/max ${_n(st.min)}/${_n(st.mean)}/${_n(st.max)} ms, loss ${st.lossPercent.toStringAsFixed(0)} %]' : ''}',258
);259
}260
b.writeln(261
lane.reached ? 'destination reached' : 'destination not reached',262
);263
}264
return b.toString();265
}266
}SHA-256
022f2f5c039e0acf804e968dc9c182e956f0323c9e16d02799c0f337944eb8aeCH-J Proprietary Software License 1.14 · Zdroje jsou zveřejněny pod CH-J Proprietary Software License 1.14. Jejich dostupnost nemění licenční podmínky ani neposkytuje další oprávnění.
SHA-256 archivu
46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655