CH-J SM Mobilepamamahala ng server sa pamamagitan ng SSH
Menu
Nailathalang source

CH-J SM Mobile

I-browse ang mga direktoryo at file ng isang partikular na release ng application.

I-download ang source ZIP
CH-J / SM Mobile 175 file
sm-mobile-20261016T185235Z-8e93ecc1b59a Idinagdag: · Binuo:
icmp_probe.dart Idinagdag: Hindi naitala ang petsa 31.9 KB · 1096 linya
Code I-download ang file
1import 'dart:async';
2import 'dart:ffi';
3import 'dart:io';
4import 'dart:isolate';
5import 'dart:math' as math;
6import 'dart:typed_data';
7import 'package:ffi/ffi.dart';
8import 'icmp_packet.dart';
9import 'posix.dart';
11/// Settings of an ICMP echo measurement. [count] 0 means continuous until
12/// stopped. [ttl] null keeps the system default TTL / hop limit.
13class PingSettings {
14 final int count, size;
15 final int? ttl;
16 final bool dontFragment;
17 final Duration interval, timeout;
18 const PingSettings({
19 this.count = 4,
20 this.interval = const Duration(seconds: 1),
21 this.timeout = const Duration(seconds: 2),
22 this.size = 56,
23 this.ttl,
24 this.dontFragment = false,
25 });
26 Map<String, Object?> toJson() => {
27 'count': count == 0 ? 'continuous' : count,
28 'intervalMs': interval.inMilliseconds,
29 'timeoutMs': timeout.inMilliseconds,
30 'payloadBytes': size,
31 'ttl': ttl ?? 'system default',
32 'dontFragment': dontFragment,
33 };
36/// How traceroute probes are sent.
37enum TraceMethod {
38 /// ICMP echo requests with increasing TTL.
39 icmp,
41 /// UDP datagrams to high ports; the destination answers with ICMP port
42 /// unreachable.
43 udp,
45 /// TCP SYN to a port; the destination answers with SYN-ACK or reset.
46 tcp,
49/// Settings of a traceroute.
50class TraceSettings {
51 final int maxHops, probes, size, firstTtl;
52 final Duration timeout, interval;
53 final TraceMethod method;
55 /// UDP: first destination port (incremented per probe); TCP: the port.
56 final int? port;
57 const TraceSettings({
58 this.maxHops = 30,
59 this.probes = 3,
60 this.timeout = const Duration(seconds: 2),
61 this.size = 32,
62 this.firstTtl = 1,
63 this.interval = Duration.zero,
64 this.method = TraceMethod.icmp,
65 this.port,
66 });
67 int get effectivePort =>
68 port ?? (method == TraceMethod.tcp ? 80 : defaultUdpPort);
69 static const defaultUdpPort = 33434;
70 Map<String, Object?> toJson() => {
71 'method': method.name,
72 'maxHops': maxHops,
73 'probesPerHop': probes,
74 'firstTtl': firstTtl,
75 'timeoutMs': timeout.inMilliseconds,
76 'intervalMs': interval.inMilliseconds,
77 if (method != TraceMethod.icmp) 'port': effectivePort,
78 };
81/// One echo request outcome.
82enum ProbeStatus { reply, timeout, timeExceeded, unreachable, sendFailed }
84class ProbeEvent {
85 /// Sequence number for ping; probe number within the hop for traceroute.
86 final int seq;
88 /// Hop number (TTL / hop limit) for traceroute; 0 for ping.
89 final int ttl;
90 final ProbeStatus status;
91 final InternetAddress? from;
92 final double? rttMs;
93 final int? icmpCode;
95 /// When the probe was sent.
96 final DateTime? time;
98 /// Additional detail: 'portUnreachable' (UDP destination reached),
99 /// 'tcpOpen' / 'tcpRefused' (TCP destination reached), 'messageTooLong'
100 /// or 'errno N'.
101 final String? note;
102 const ProbeEvent(
103 this.seq,
104 this.status, {
105 this.ttl = 0,
106 this.from,
107 this.rttMs,
108 this.icmpCode,
109 this.time,
110 this.note,
111 });
114/// Ping statistics in the usual min/avg/max/loss form.
115class PingStats {
116 final int sent, received;
117 final double? min, avg, max;
118 const PingStats(this.sent, this.received, this.min, this.avg, this.max);
119 double get lossPercent => sent == 0 ? 0 : 100 * (sent - received) / sent;
121 factory PingStats.of(Iterable<ProbeEvent> events) {
122 final rtts = [
123 for (final e in events)
124 if (e.status == ProbeStatus.reply && e.rttMs != null) e.rttMs!,
125 ];
126 return PingStats(
127 events.length,
128 rtts.length,
129 rtts.isEmpty ? null : rtts.reduce(math.min),
130 rtts.isEmpty ? null : rtts.reduce((a, b) => a + b) / rtts.length,
131 rtts.isEmpty ? null : rtts.reduce(math.max),
132 );
133 }
136/// Raised when the platform does not allow an unprivileged ICMP socket or
137/// a socket call fails before any packet could be sent.
138class IcmpUnavailable implements Exception {
139 /// 'icmpUnsupported', 'icmpNotPermitted', 'networkError',
140 /// 'tcpTraceUnsupported', 'ttlUnsupported', 'dontFragmentUnsupported'.
141 final String code;
142 final int? errno;
143 const IcmpUnavailable(this.code, [this.errno]);
144 @override
145 String toString() => 'IcmpUnavailable($code, errno $errno)';
148/// A running measurement: [events] streams results; [cancel] stops it at
149/// once and closes the socket.
150class IcmpRun {
151 final Stream<ProbeEvent> events;
152 final Future<void> Function() cancel;
153 IcmpRun(this.events, this.cancel);
156class IcmpProbe {
157 /// Null when [method] can run on this platform without privileges,
158 /// otherwise the localization key explaining why not.
159 ///
160 /// iOS/macOS deliver ICMP errors for UDP and TCP probes to an unprivileged
161 /// ICMP datagram socket, so all methods work. Android/Linux report ICMP
162 /// errors for UDP through the socket error queue; for TCP connections the
163 /// kernel reports only an error code without the router's address, so TCP
164 /// traceroute is not available there.
165 static String? unsupported(TraceMethod method) {
166 final p = Posix.current;
167 if (p == null) return 'icmpUnsupported';
168 if (method == TraceMethod.tcp && !p.darwin) return 'tcpTraceUnsupported';
169 return null;
170 }
172 /// Sends [settings].count echo requests to [target].
173 static IcmpRun ping(InternetAddress target, PingSettings settings) => _start({
174 'mode': 'ping',
175 'address': target.rawAddress,
176 'count': settings.count,
177 'interval': settings.interval.inMicroseconds,
178 'timeout': settings.timeout.inMicroseconds,
179 'size': settings.size,
180 'ttl': ?settings.ttl,
181 'df': settings.dontFragment,
182 });
184 /// Probes each hop to [target] with increasing TTL / hop limit.
185 static IcmpRun traceroute(InternetAddress target, TraceSettings settings) =>
186 _start({
187 'mode': 'trace',
188 'method': settings.method.name,
189 'address': target.rawAddress,
190 'maxHops': settings.maxHops,
191 'probes': settings.probes,
192 'timeout': settings.timeout.inMicroseconds,
193 'interval': settings.interval.inMicroseconds,
194 'size': settings.size,
195 'firstTtl': settings.firstTtl,
196 'port': settings.effectivePort,
197 });
199 static IcmpRun _start(Map<String, Object> config) {
200 final controller = StreamController<ProbeEvent>();
201 final fromWorker = ReceivePort();
202 SendPort? toWorker;
203 Isolate? isolate;
204 var done = false;
205 void finish() {
206 if (done) return;
207 done = true;
208 fromWorker.close();
209 controller.close();
210 }
212 fromWorker.listen((message) {
213 if (message is SendPort) {
214 toWorker = message;
215 } else if (message is Map) {
216 if (message['error'] != null) {
217 controller.addError(
218 IcmpUnavailable(
219 message['error'] as String,
220 message['errno'] as int?,
221 ),
222 );
223 } else {
224 controller.add(_decode(message));
225 }
226 } else if (message == null) {
227 finish();
228 }
229 });
230 Isolate.spawn(
231 _worker,
232 [fromWorker.sendPort, config],
233 onExit: fromWorker.sendPort,
234 onError: fromWorker.sendPort,
235 ).then(
236 (i) {
237 isolate = i;
238 if (done) i.kill(priority: Isolate.immediate);
239 },
240 onError: (Object e) {
241 controller.addError(const IcmpUnavailable('icmpUnsupported'));
242 finish();
243 },
244 );
245 Future<void> cancel() async {
246 // The worker closes its socket when told; kill it if it does not exit.
247 toWorker?.send('cancel');
248 final pending = isolate;
249 if (pending != null) {
250 Timer(const Duration(milliseconds: 500), () {
251 pending.kill(priority: Isolate.immediate);
252 });
253 }
254 finish();
255 }
257 controller.onCancel = cancel;
258 return IcmpRun(controller.stream, cancel);
259 }
261 static ProbeEvent _decode(Map m) => ProbeEvent(
262 m['seq'] as int,
263 ProbeStatus.values[m['status'] as int],
264 ttl: m['ttl'] as int,
265 from: m['from'] == null
266 ? null
267 : InternetAddress.fromRawAddress(m['from'] as Uint8List),
268 rttMs: m['rtt'] as double?,
269 icmpCode: m['code'] as int?,
270 time: m['time'] == null
271 ? null
272 : DateTime.fromMicrosecondsSinceEpoch(m['time'] as int),
273 note: m['note'] as String?,
274 );
277// ---------------------------------------------------------------------------
278// Worker isolate: owns the sockets and all blocking calls.
280Future<void> _worker(List<Object> args) async {
281 final out = args[0] as SendPort, config = args[1] as Map<String, Object>;
282 final inbox = ReceivePort();
283 out.send(inbox.sendPort);
284 var cancelled = false;
285 inbox.listen((_) => cancelled = true);
286 final posix = Posix.current;
287 if (posix == null) {
288 out.send({'error': 'icmpUnsupported'});
289 inbox.close();
290 return;
291 }
292 final target = InternetAddress.fromRawAddress(config['address'] as Uint8List);
293 final session = _Session(posix, target);
294 try {
295 if (config['mode'] == 'ping') {
296 session.open();
297 await _ping(session, config, out, () => cancelled);
298 } else {
299 final method = TraceMethod.values.byName(config['method'] as String);
300 if (method == TraceMethod.tcp && !posix.darwin) {
301 throw const IcmpUnavailable('tcpTraceUnsupported');
302 }
303 // ICMP errors for UDP/TCP probes: Darwin delivers them to the ICMP
304 // socket; Linux to the UDP socket's own error queue.
305 if (method == TraceMethod.icmp || posix.darwin) session.open();
306 await _trace(session, method, config, out, () => cancelled);
307 }
308 } on IcmpUnavailable catch (e) {
309 out.send({'error': e.code, 'errno': e.errno});
310 } finally {
311 session.close();
312 inbox.close();
313 }
316void _send(SendPort out, ProbeEvent e) => out.send({
317 'seq': e.seq,
318 'ttl': e.ttl,
319 'status': e.status.index,
320 'from': e.from?.rawAddress,
321 'rtt': e.rttMs,
322 'code': e.icmpCode,
323 'time': e.time?.microsecondsSinceEpoch,
324 'note': e.note,
325});
327Future<void> _ping(
328 _Session s,
329 Map<String, Object> c,
330 SendPort out,
331 bool Function() cancelled,
332) async {
333 final count = c['count'] as int, size = c['size'] as int;
334 final interval = c['interval'] as int, timeout = c['timeout'] as int;
335 final ttl = c['ttl'] as int?;
336 if (ttl != null && !s.hopLimit(ttl)) {
337 throw IcmpUnavailable('ttlUnsupported', s.p.errno);
338 }
339 if (c['df'] == true && !s.dontFragment()) {
340 throw IcmpUnavailable('dontFragmentUnsupported', s.p.errno);
341 }
342 final clock = s.clock;
343 final pending = <int, int>{}; // seq -> send time (µs)
344 final sentAt = <int, DateTime>{};
345 var nextSend = 0, sent = 0, seq = 0;
346 bool more() => count == 0 || sent < count;
347 while (!cancelled()) {
348 final now = clock.elapsedMicroseconds;
349 if (more() && now >= nextSend) {
350 sent++;
351 // Sequence numbers are 16 bits; a continuous ping wraps around.
352 seq = seq % 0xffff + 1;
353 final time = DateTime.now();
354 if (s.send(seq, size)) {
355 pending[seq] = clock.elapsedMicroseconds;
356 sentAt[seq] = time;
357 } else {
358 _send(
359 out,
360 ProbeEvent(
361 seq,
362 ProbeStatus.sendFailed,
363 time: time,
364 note: _errnoNote(s.p, s.p.errno),
365 ),
366 );
367 }
368 nextSend += interval;
369 }
370 for (final entry in pending.entries.toList()) {
371 if (clock.elapsedMicroseconds - entry.value > timeout) {
372 pending.remove(entry.key);
373 _send(
374 out,
375 ProbeEvent(
376 entry.key,
377 ProbeStatus.timeout,
378 time: sentAt.remove(entry.key),
379 ),
380 );
381 }
382 }
383 if (!more() && pending.isEmpty) break;
384 final wait = more()
385 ? nextSend - clock.elapsedMicroseconds
386 : timeout -
387 (clock.elapsedMicroseconds - pending.values.reduce(math.min));
388 for (final r in s.receiveEcho(math.max(0, math.min(wait, 50000)))) {
389 final start = pending.remove(r.seq);
390 if (start == null) continue;
391 _send(
392 out,
393 ProbeEvent(
394 r.seq,
395 r.status,
396 from: r.from,
397 rttMs: (r.receivedAt - start) / 1000,
398 icmpCode: r.code,
399 time: sentAt.remove(r.seq),
400 ),
401 );
402 }
403 // Lets the isolate process a cancel message between short polls.
404 await Future<void>.delayed(Duration.zero);
405 }
408String _errnoNote(Posix p, int errno) =>
409 errno == p.eMsgSize ? 'messageTooLong' : 'errno $errno';
411/// A probe sender for one traceroute method.
412abstract class _Prober {
413 /// Sends probe [n] with [ttl]; returns the send time on the session clock,
414 /// or null (with errno set) when sending failed.
415 int? send(int n, int ttl);
417 /// Waits up to [micros] for the answer to the probe sent last.
418 _Received? poll(int micros);
419 void close();
422Future<void> _trace(
423 _Session s,
424 TraceMethod method,
425 Map<String, Object> c,
426 SendPort out,
427 bool Function() cancelled,
428) async {
429 final maxHops = c['maxHops'] as int, probes = c['probes'] as int;
430 final timeout = c['timeout'] as int, size = c['size'] as int;
431 final interval = c['interval'] as int, firstTtl = c['firstTtl'] as int;
432 final port = c['port'] as int;
433 final _Prober prober = switch (method) {
434 TraceMethod.icmp => _IcmpProber(s, size),
435 TraceMethod.udp =>
436 s.p.darwin
437 ? _DarwinUdpProber(s, port, size)
438 : _LinuxUdpProber(s, port, size),
439 TraceMethod.tcp => _DarwinTcpProber(s, port),
440 };
441 final clock = s.clock;
442 var n = 0;
443 try {
444 for (var ttl = firstTtl; ttl <= maxHops && !cancelled(); ttl++) {
445 var reached = false, unreachable = false;
446 for (var probe = 1; probe <= probes && !cancelled(); probe++) {
447 if (n > 0 && interval > 0) {
448 final until = clock.elapsedMicroseconds + interval;
449 while (!cancelled() && clock.elapsedMicroseconds < until) {
450 await Future<void>.delayed(const Duration(milliseconds: 10));
451 }
452 if (cancelled()) return;
453 }
454 n++;
455 final time = DateTime.now();
456 final sent = prober.send(n, ttl);
457 if (sent == null) {
458 _send(
459 out,
460 ProbeEvent(
461 probe,
462 ProbeStatus.sendFailed,
463 ttl: ttl,
464 time: time,
465 note: _errnoNote(s.p, s.p.errno),
466 ),
467 );
468 continue;
469 }
470 _Received? answer;
471 while (!cancelled()) {
472 final left = timeout - (clock.elapsedMicroseconds - sent);
473 if (left <= 0) break;
474 answer = prober.poll(math.min(left, 50000));
475 if (answer != null) break;
476 await Future<void>.delayed(Duration.zero);
477 }
478 if (cancelled()) return;
479 if (answer == null) {
480 _send(
481 out,
482 ProbeEvent(probe, ProbeStatus.timeout, ttl: ttl, time: time),
483 );
484 continue;
485 }
486 reached |= answer.status == ProbeStatus.reply;
487 unreachable |= answer.status == ProbeStatus.unreachable;
488 _send(
489 out,
490 ProbeEvent(
491 probe,
492 answer.status,
493 ttl: ttl,
494 from: answer.from,
495 rttMs: (answer.receivedAt - sent) / 1000,
496 icmpCode: answer.code,
497 time: time,
498 note: answer.note,
499 ),
500 );
501 }
502 if (reached || unreachable) break;
503 }
504 } finally {
505 prober.close();
506 }
509class _Received {
510 final int seq;
511 final ProbeStatus status;
512 final InternetAddress? from;
513 final int? code;
514 final int receivedAt; // µs on the session clock
515 final String? note;
516 _Received(
517 this.seq,
518 this.status,
519 this.from,
520 this.code,
521 this.receivedAt, [
522 this.note,
523 ]);
526/// A received ICMP message with where and when it arrived.
527class _Inbound {
528 final IcmpMessage message;
529 final InternetAddress? from;
530 final int at;
531 _Inbound(this.message, this.from, this.at);
534ProbeStatus? _errorStatus(IcmpKind kind) => switch (kind) {
535 IcmpKind.timeExceeded => ProbeStatus.timeExceeded,
536 IcmpKind.unreachable => ProbeStatus.unreachable,
537 _ => null,
538};
540class _IcmpProber implements _Prober {
541 final _Session s;
542 final int size;
543 int _seq = 0;
544 _IcmpProber(this.s, this.size);
545 @override
546 int? send(int n, int ttl) {
547 s.hopLimit(ttl);
548 _seq = n & 0xffff;
549 final at = s.clock.elapsedMicroseconds;
550 return s.send(_seq, size) ? at : null;
551 }
553 @override
554 _Received? poll(int micros) {
555 for (final r in s.receiveEcho(micros)) {
556 if (r.seq == _seq) return r;
557 }
558 return null;
559 }
561 @override
562 void close() {}
565/// Classic traceroute ports: one destination port per probe.
566int _probePort(int base, int n) => base + (n - 1) % math.max(1, 65535 - base);
568/// Darwin: what an ICMP message about one of our UDP/TCP probes means.
569/// Port unreachable ends the trace like in the classic tool; when it comes
570/// from another address than the target (anycast, a host firewall), the
571/// note says so.
572_Received? _transportAnswer(_Session s, _Inbound m) {
573 final msg = m.message;
574 if (msg.portUnreachable(s.v6)) {
575 return _Received(
576 0,
577 ProbeStatus.reply,
578 m.from,
579 msg.code,
580 m.at,
581 m.from == s.target ? 'portUnreachable' : 'portUnreachableOther',
582 );
583 }
584 final status = _errorStatus(msg.kind);
585 if (status == null) return null;
586 return _Received(0, status, m.from, msg.code, m.at);
589/// Darwin: UDP probes from an ordinary UDP socket; the ICMP errors they
590/// cause arrive on the session's ICMP socket, matched by the quoted ports.
591class _DarwinUdpProber implements _Prober {
592 final _Session s;
593 final int basePort, size;
594 late final int fd;
595 int _port = 0, _source = 0;
596 _DarwinUdpProber(this.s, this.basePort, this.size) {
597 fd = s.p.socket(
598 s.v6 ? s.p.afInet6 : s.p.afInet,
599 s.p.sockDgram,
600 s.p.ipprotoUdp,
601 );
602 if (fd < 0) throw IcmpUnavailable('networkError', s.p.errno);
603 }
605 @override
606 int? send(int n, int ttl) {
607 s.setTtl(fd, ttl);
608 _port = _probePort(basePort, n);
609 final at = s.clock.elapsedMicroseconds;
610 if (!s.sendDatagram(fd, _port, size)) return null;
611 if (_source == 0) _source = s.localPort(fd);
612 return at;
613 }
615 @override
616 _Received? poll(int micros) {
617 for (final m in s.receiveRaw(micros)) {
618 final msg = m.message;
619 if (msg.protocol != 17 ||
620 msg.destinationPort != _port ||
621 msg.sourcePort != _source) {
622 continue;
623 }
624 final answer = _transportAnswer(s, m);
625 if (answer != null) return answer;
626 }
627 return null;
628 }
630 @override
631 void close() => s.p.close(fd);
634/// Darwin: a non-blocking TCP connect with the TTL set; routers answer the
635/// SYN with time exceeded (seen on the ICMP socket), the destination with
636/// SYN-ACK (connected) or a reset (refused).
637class _DarwinTcpProber implements _Prober {
638 final _Session s;
639 final int port;
640 int fd = -1, _source = 0;
641 _DarwinTcpProber(this.s, this.port);
643 @override
644 int? send(int n, int ttl) {
645 _closeSocket();
646 final p = s.p;
647 fd = p.socket(s.v6 ? p.afInet6 : p.afInet, p.sockStream, p.ipprotoTcp);
648 if (fd < 0) return null;
649 s.setTtl(fd, ttl);
650 final flags = p.fcntl(fd, p.fGetFl, 0);
651 p.fcntl(fd, p.fSetFl, flags | p.oNonBlock);
652 final address = p.sockaddr(s.target, port);
653 final buffer = calloc<Uint8>(address.length);
654 try {
655 buffer.asTypedList(address.length).setAll(0, address);
656 final at = s.clock.elapsedMicroseconds;
657 final result = p.connect(fd, buffer, address.length);
658 // EINPROGRESS is the normal case; a refusal may be immediate on
659 // loopback and is then reported by the next poll.
660 if (result != 0) {
661 final errno = p.errno;
662 if (errno != p.eInProgress && errno != p.eConnRefused) return null;
663 }
664 _source = s.localPort(fd);
665 return at;
666 } finally {
667 calloc.free(buffer);
668 }
669 }
671 _Received? _matching(int micros) {
672 for (final m in s.receiveRaw(micros)) {
673 final msg = m.message;
674 if (msg.protocol == 6 &&
675 msg.destinationPort == port &&
676 msg.sourcePort == _source) {
677 final answer = _transportAnswer(s, m);
678 if (answer != null) return answer;
679 }
680 }
681 return null;
682 }
684 @override
685 _Received? poll(int micros) {
686 final p = s.p;
687 final early = _matching(0);
688 if (early != null || fd < 0) return early;
689 final fds = calloc<PollFd>(2);
690 try {
691 fds[0]
692 ..fd = s.fd
693 ..events = Posix.pollIn
694 ..revents = 0;
695 fds[1]
696 ..fd = fd
697 ..events = Posix.pollOut
698 ..revents = 0;
699 final ready = p.poll(fds, 2, (micros / 1000).ceil());
700 if (ready <= 0) return null;
701 if (fds[0].revents & Posix.pollIn != 0) {
702 final answer = _matching(0);
703 if (answer != null) return answer;
704 }
705 if (fds[1].revents != 0) {
706 final at = s.clock.elapsedMicroseconds;
707 final error = s.socketError(fd);
708 if (error == 0 || error == p.eConnRefused) {
709 _closeSocket();
710 return _Received(
711 0,
712 ProbeStatus.reply,
713 s.target,
714 null,
715 at,
716 error == 0 ? 'tcpOpen' : 'tcpRefused',
717 );
718 }
719 // Another error (no route after a time exceeded): the ICMP message
720 // names the router and is normally already readable.
721 final answer = _matching(20000);
722 _closeSocket();
723 return answer ??
724 _Received(
725 0,
726 ProbeStatus.unreachable,
727 null,
728 null,
729 at,
730 'errno $error',
731 );
732 }
733 } finally {
734 calloc.free(fds);
735 }
736 return null;
737 }
739 void _closeSocket() {
740 if (fd >= 0) s.p.close(fd);
741 fd = -1;
742 }
744 @override
745 void close() => _closeSocket();
748/// Linux/Android: UDP probes on a socket with IP_RECVERR; ICMP errors come
749/// back on its error queue together with the original destination port.
750class _LinuxUdpProber implements _Prober {
751 final _Session s;
752 final int basePort, size;
753 late final int fd;
754 int _port = 0;
755 _LinuxUdpProber(this.s, this.basePort, this.size) {
756 final p = s.p;
757 fd = p.socket(s.v6 ? p.afInet6 : p.afInet, p.sockDgram, p.ipprotoUdp);
758 if (fd < 0) throw IcmpUnavailable('networkError', p.errno);
759 s.setInt(
760 fd,
761 s.v6 ? p.ipprotoIpv6 : p.ipprotoIp,
762 s.v6 ? p.ipv6RecvErr : p.ipRecvErr,
763 1,
764 );
765 }
767 @override
768 int? send(int n, int ttl) {
769 s.setTtl(fd, ttl);
770 _port = _probePort(basePort, n);
771 final at = s.clock.elapsedMicroseconds;
772 return s.sendDatagram(fd, _port, size) ? at : null;
773 }
775 @override
776 _Received? poll(int micros) {
777 for (final e in s.readErrorQueue(fd, micros)) {
778 if (e.port != _port) continue;
779 final (type, code, offender) = e.error;
780 final unreachable = s.v6 ? IcmpType.unreachable6 : IcmpType.unreachable4;
781 final exceeded = s.v6 ? IcmpType.timeExceeded6 : IcmpType.timeExceeded4;
782 if (type == unreachable && code == (s.v6 ? 4 : 3)) {
783 return _Received(
784 0,
785 ProbeStatus.reply,
786 offender,
787 code,
788 e.at,
789 offender == s.target ? 'portUnreachable' : 'portUnreachableOther',
790 );
791 }
792 if (type == exceeded) {
793 return _Received(0, ProbeStatus.timeExceeded, offender, code, e.at);
794 }
795 if (type == unreachable) {
796 return _Received(0, ProbeStatus.unreachable, offender, code, e.at);
797 }
798 }
799 return null;
800 }
802 @override
803 void close() => s.p.close(fd);
806typedef _QueuedError = ({
807 Uint8List data,
808 int port,
809 (int, int, InternetAddress?) error,
810 int at,
811});
813/// One ICMP socket and the buffers used with it.
814class _Session {
815 final Posix p;
816 final InternetAddress target;
817 final bool v6;
818 // Darwin keeps the id we choose; Linux replaces it with the socket's own
819 // identifier, so on Linux replies are matched by sequence only (the kernel
820 // already delivers only this socket's replies).
821 final int id = math.Random.secure().nextInt(0xffff);
822 int fd = -1;
824 /// Send and receive times are both taken from this clock.
825 final Stopwatch clock = Stopwatch()..start();
826 final Pointer<Uint8> _buffer = calloc<Uint8>(_bufferSize);
827 final Pointer<Uint8> _address = calloc<Uint8>(128);
828 final Pointer<Uint32> _addressLength = calloc<Uint32>();
829 final Pointer<PollFd> _poll = calloc<PollFd>();
830 static const _bufferSize = 65536;
832 _Session(this.p, this.target) : v6 = target.type == InternetAddressType.IPv6;
834 void open() {
835 fd = p.socket(
836 v6 ? p.afInet6 : p.afInet,
837 p.sockDgram,
838 v6 ? p.ipprotoIcmpv6 : p.ipprotoIcmp,
839 );
840 if (fd < 0) {
841 final errno = p.errno;
842 throw IcmpUnavailable(
843 errno == p.eAccess || errno == p.ePerm
844 ? 'icmpNotPermitted'
845 : 'icmpUnsupported',
846 errno,
847 );
848 }
849 if (!p.darwin) {
850 // Deliver ICMP errors (time exceeded, unreachable) to the error queue.
851 setInt(
852 fd,
853 v6 ? p.ipprotoIpv6 : p.ipprotoIp,
854 v6 ? p.ipv6RecvErr : p.ipRecvErr,
855 1,
856 );
857 }
858 }
860 bool setInt(int socket, int level, int name, int value) {
861 final v = calloc<Int32>()..value = value;
862 try {
863 return p.setsockopt(socket, level, name, v.cast(), 4) == 0;
864 } finally {
865 calloc.free(v);
866 }
867 }
869 bool setTtl(int socket, int ttl) => setInt(
870 socket,
871 v6 ? p.ipprotoIpv6 : p.ipprotoIp,
872 v6 ? p.ipv6UnicastHops : p.ipTtl,
873 ttl,
874 );
876 bool hopLimit(int ttl) => setTtl(fd, ttl);
878 bool dontFragment() => setInt(
879 fd,
880 v6 ? p.ipprotoIpv6 : p.ipprotoIp,
881 v6 ? p.ipv6DontFrag : p.ipDontFrag,
882 p.dontFragValue,
883 );
885 int socketError(int socket) {
886 final value = calloc<Int32>(), length = calloc<Uint32>()..value = 4;
887 try {
888 if (p.getsockopt(socket, p.solSocket, p.soError, value.cast(), length) !=
889 0) {
890 return p.errno;
891 }
892 return value.value;
893 } finally {
894 calloc.free(value);
895 calloc.free(length);
896 }
897 }
899 int localPort(int socket) {
900 _addressLength.value = 128;
901 if (p.getsockname(socket, _address, _addressLength) != 0) return 0;
902 return p.port(_address.asTypedList(4));
903 }
905 bool _sendTo(int socket, int length, int port) {
906 final address = p.sockaddr(target, port);
907 final to = calloc<Uint8>(address.length);
908 try {
909 to.asTypedList(address.length).setAll(0, address);
910 return p.sendto(socket, _buffer, length, 0, to, address.length) == length;
911 } finally {
912 calloc.free(to);
913 }
914 }
916 bool send(int seq, int size) {
917 final packet = buildEchoRequest(v6: v6, id: id, seq: seq, size: size);
918 _buffer.asTypedList(packet.length).setAll(0, packet);
919 return _sendTo(fd, packet.length, 0);
920 }
922 bool sendDatagram(int socket, int port, int size) {
923 final payload = _buffer.asTypedList(size);
924 for (var i = 0; i < size; i++) {
925 payload[i] = 0x40 + i % 32;
926 }
927 return _sendTo(socket, size, port);
928 }
930 /// Echo replies and errors for this session's echo requests.
931 List<_Received> receiveEcho(int micros) {
932 final out = <_Received>[];
933 for (final m in receiveRaw(micros)) {
934 final message = m.message;
935 if (message.seq == null) continue;
936 if (p.darwin && message.id != id) continue; // another process's echo
937 final status = message.kind == IcmpKind.echoReply
938 ? ProbeStatus.reply
939 : _errorStatus(message.kind);
940 if (status == null) continue;
941 if (status == ProbeStatus.reply && m.from != null && m.from != target) {
942 continue;
943 }
944 out.add(_Received(message.seq!, status, m.from, message.code, m.at));
945 }
946 return out;
947 }
949 /// All ICMP messages that arrive within [micros] on the ICMP socket. On
950 /// Linux, errors for echo requests come from the error queue.
951 List<_Inbound> receiveRaw(int micros) {
952 final out = <_Inbound>[];
953 if (fd < 0) return out;
954 _poll.ref
955 ..fd = fd
956 ..events = Posix.pollIn
957 ..revents = 0;
958 final ready = p.poll(_poll, 1, (micros / 1000).ceil());
959 if (ready <= 0) return out;
960 final revents = _poll.ref.revents;
961 if (revents & Posix.pollIn != 0) _readMessages(out);
962 if (!p.darwin && revents & Posix.pollErr != 0) {
963 for (final e in readErrorQueue(fd, 0)) {
964 final sent = sentEcho(e.data, v6: v6);
965 if (sent == null) continue;
966 final (type, code, offender) = e.error;
967 final kind =
968 type == (v6 ? IcmpType.timeExceeded6 : IcmpType.timeExceeded4)
969 ? IcmpKind.timeExceeded
970 : type == (v6 ? IcmpType.unreachable6 : IcmpType.unreachable4)
971 ? IcmpKind.unreachable
972 : IcmpKind.other;
973 out.add(
974 _Inbound(
975 IcmpMessage(kind, type, code, id: sent.id, seq: sent.seq),
976 offender,
977 e.at,
978 ),
979 );
980 }
981 }
982 return out;
983 }
985 void _readMessages(List<_Inbound> out) {
986 while (true) {
987 _addressLength.value = 128;
988 final n = p.recvfrom(
989 fd,
990 _buffer,
991 _bufferSize,
992 p.msgDontWait,
993 _address,
994 _addressLength,
995 );
996 if (n <= 0) return;
997 final at = clock.elapsedMicroseconds;
998 final data = Uint8List.fromList(_buffer.asTypedList(n));
999 final message = parseIcmp(data, v6: v6, ipHeader: p.darwin && !v6);
1000 if (message == null) continue;
1001 final from = p.address(_address.asTypedList(128), _addressLength.value);
1002 out.add(_Inbound(message, from, at));
1006 /// Linux: reads the error queue of [socket], waiting up to [micros] for
1007 /// an entry. Each entry carries the offending packet's payload, its
1008 /// original destination port and the ICMP type, code and sender.
1009 List<_QueuedError> readErrorQueue(int socket, int micros) {
1010 final out = <_QueuedError>[];
1011 if (micros > 0) {
1012 _poll.ref
1013 ..fd = socket
1014 ..events = Posix.pollIn
1015 ..revents = 0;
1016 final ready = p.poll(_poll, 1, (micros / 1000).ceil());
1017 if (ready <= 0 || _poll.ref.revents & Posix.pollErr == 0) return out;
1019 final msg = calloc<MsgHdr>();
1020 final iov = calloc<IoVec>();
1021 const controlSize = 512;
1022 final control = calloc<Uint8>(controlSize);
1023 final name = calloc<Uint8>(128);
1024 try {
1025 while (true) {
1026 iov.ref
1027 ..base = _buffer
1028 ..length = _bufferSize;
1029 msg.ref
1030 ..name = name
1031 ..nameLength = 128
1032 ..iov = iov
1033 ..iovLength = 1
1034 ..control = control
1035 ..controlLength = controlSize
1036 ..flags = 0;
1037 final n = p.recvmsg(socket, msg, p.msgErrQueue | p.msgDontWait);
1038 if (n < 0) return out;
1039 final at = clock.elapsedMicroseconds;
1040 final error = _extendedError(msg.ref);
1041 if (error == null) continue;
1042 out.add((
1043 data: Uint8List.fromList(_buffer.asTypedList(n)),
1044 port: p.port(name.asTypedList(4)),
1045 error: error,
1046 at: at,
1047 ));
1049 } finally {
1050 calloc.free(name);
1051 calloc.free(control);
1052 calloc.free(iov);
1053 calloc.free(msg);
1057 (int, int, InternetAddress?)? _extendedError(MsgHdr msg) {
1058 final word = sizeOf<IntPtr>();
1059 final header = sizeOf<CmsgHdr>();
1060 var offset = 0;
1061 final base = msg.control;
1062 while (offset + header <= msg.controlLength) {
1063 final cmsg = Pointer<CmsgHdr>.fromAddress(base.address + offset).ref;
1064 if (cmsg.length < header) return null;
1065 final level = v6 ? p.ipprotoIpv6 : p.ipprotoIp;
1066 final type = v6 ? p.ipv6RecvErr : p.ipRecvErr;
1067 final dataOffset = offset + ((header + word - 1) & ~(word - 1));
1068 if (cmsg.level == level && cmsg.type == type) {
1069 // struct sock_extended_err: u32 errno; u8 origin, type, code, pad;
1070 // u32 info, data; followed by the offender's sockaddr.
1071 final ee = Pointer<Uint8>.fromAddress(
1072 base.address + dataOffset,
1073 ).asTypedList(cmsg.length - (dataOffset - offset));
1074 final origin = ee[4];
1075 if (origin != Posix.originIcmp && origin != Posix.originIcmp6) {
1076 return null;
1078 final offender = ee.length >= 16 + 8
1079 ? p.address(Uint8List.fromList(ee.sublist(16)), ee.length - 16)
1080 : null;
1081 return (ee[5], ee[6], offender);
1083 offset += (cmsg.length + word - 1) & ~(word - 1);
1085 return null;
1088 void close() {
1089 if (fd >= 0) p.close(fd);
1090 fd = -1;
1091 calloc.free(_buffer);
1092 calloc.free(_address);
1093 calloc.free(_addressLength);
1094 calloc.free(_poll);
SHA-25652e6da745dda60fed30df54fc70d9b3cfc04db7c2faa0ec73601b9293970d524

CH-J Proprietary Software License 1.14 · Ibinibigay ang source sa ilalim ng CH-J Proprietary Software License 1.14. Hindi binabago ng pagiging available nito ang mga tuntunin ng lisensya at hindi ito nagbibigay ng karagdagang karapatan.

SHA-256 ng archive46e427a43e87af091004f36145590bda8fe90086dfb0f631afeb5029fb0b5655
Mga file