CH-J SM Mobilespráva serverů přes SSH
Menu
Vydané zdroje

CH-J SM Mobile

Procházejte adresáře a soubory konkrétního vydání aplikace.

Stáhnout zdroje ZIP
CH-J / SM Mobile 175 souborů
sm-mobile-20261016T185235Z-8e93ecc1b59a Přidáno: · Sestaveno:
trace_model.dart Přidáno: Datum není evidováno 8,1 KB · 266 řádků
Kód Stáhnout soubor
1import 'dart:convert';
2import 'dart:io';
3import 'icmp_probe.dart';
4import 'measure.dart';
6/// The usual traceroute notation for ICMP destination unreachable codes:
7/// !N network, !H host, !P protocol, !F fragmentation needed, !X
8/// administratively prohibited; '' for port unreachable (expected for UDP).
9String 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 };
31/// One hop over all repetitions of a traceroute.
32class 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: answered
58 ? Outcome.ok
59 : e.status == ProbeStatus.sendFailed
60 ? Outcome.error
61 : 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 }
72/// A traceroute of one IP version, possibly repeated.
73class 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 };
110class HopSnapshot {
111 final Set<String> addresses;
112 final double? meanMs;
113 final double lossPercent;
114 const HopSnapshot(this.addresses, this.meanMs, this.lossPercent);
117/// The difference of one hop between an earlier and a later measurement.
118class 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 == null
132 ? null
133 : after!.meanMs! - before!.meanMs!;
136List<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])];
144String _csv(Object? v) {
145 final s = v?.toString() ?? '';
146 return s.contains(RegExp(r'[",\n\r]')) ? '"${s.replaceAll('"', '""')}"' : s;
149String _n(double? v) => v == null ? '' : v.toStringAsFixed(3);
151String _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.unreachable
155 ? unreachableMarker(p.icmpCode, v6: v6)
156 : '';
157 return '${_n(p.rttMs)} ms${marker.isEmpty ? '' : ' $marker'}';
160/// A traceroute report of [lanes]. [names] maps addresses to reverse DNS
161/// names where known.
162String 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.timeout
203 ? null
204 : 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 b
244 ..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.keys
251 .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 }
SHA-256022f2f5c039e0acf804e968dc9c182e956f0323c9e16d02799c0f337944eb8ae

CH-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 archivu46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
Soubory