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:async';2
import 'dart:ffi';3
import 'dart:io';4
import 'dart:isolate';5
import 'dart:math' as math;6
import 'dart:typed_data';7
import 'package:ffi/ffi.dart';8
import 'icmp_packet.dart';9
import 'posix.dart';11
/// Settings of an ICMP echo measurement. [count] 0 means continuous until12
/// stopped. [ttl] null keeps the system default TTL / hop limit.13
class 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
};34
}36
/// How traceroute probes are sent.37
enum TraceMethod {38
/// ICMP echo requests with increasing TTL.39
icmp,41
/// UDP datagrams to high ports; the destination answers with ICMP port42
/// unreachable.43
udp,45
/// TCP SYN to a port; the destination answers with SYN-ACK or reset.46
tcp,47
}49
/// Settings of a traceroute.50
class 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
};79
}81
/// One echo request outcome.82
enum ProbeStatus { reply, timeout, timeExceeded, unreachable, sendFailed }84
class 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
});112
}114
/// Ping statistics in the usual min/avg/max/loss form.115
class 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
}134
}136
/// Raised when the platform does not allow an unprivileged ICMP socket or137
/// a socket call fails before any packet could be sent.138
class 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
@override145
String toString() => 'IcmpUnavailable($code, errno $errno)';146
}148
/// A running measurement: [events] streams results; [cancel] stops it at149
/// once and closes the socket.150
class IcmpRun {151
final Stream<ProbeEvent> events;152
final Future<void> Function() cancel;153
IcmpRun(this.events, this.cancel);154
}156
class 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 unprivileged161
/// ICMP datagram socket, so all methods work. Android/Linux report ICMP162
/// errors for UDP through the socket error queue; for TCP connections the163
/// kernel reports only an error code without the router's address, so TCP164
/// 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'] == null266
? null267
: InternetAddress.fromRawAddress(m['from'] as Uint8List),268
rttMs: m['rtt'] as double?,269
icmpCode: m['code'] as int?,270
time: m['time'] == null271
? null272
: DateTime.fromMicrosecondsSinceEpoch(m['time'] as int),273
note: m['note'] as String?,274
);275
}277
// ---------------------------------------------------------------------------278
// Worker isolate: owns the sockets and all blocking calls.280
Future<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 ICMP304
// 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
}314
}316
void _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
});327
Future<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.elapsedMicroseconds386
: 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
}406
}408
String _errnoNote(Posix p, int errno) =>409
errno == p.eMsgSize ? 'messageTooLong' : 'errno $errno';411
/// A probe sender for one traceroute method.412
abstract 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();420
}422
Future<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.darwin437
? _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
}507
}509
class _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 clock515
final String? note;516
_Received(517
this.seq,518
this.status,519
this.from,520
this.code,521
this.receivedAt, [522
this.note,523
]);524
}526
/// A received ICMP message with where and when it arrived.527
class _Inbound {528
final IcmpMessage message;529
final InternetAddress? from;530
final int at;531
_Inbound(this.message, this.from, this.at);532
}534
ProbeStatus? _errorStatus(IcmpKind kind) => switch (kind) {535
IcmpKind.timeExceeded => ProbeStatus.timeExceeded,536
IcmpKind.unreachable => ProbeStatus.unreachable,537
_ => null,538
};540
class _IcmpProber implements _Prober {541
final _Session s;542
final int size;543
int _seq = 0;544
_IcmpProber(this.s, this.size);545
@override546
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
@override554
_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
@override562
void close() {}563
}565
/// Classic traceroute ports: one destination port per probe.566
int _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 comes570
/// from another address than the target (anycast, a host firewall), the571
/// 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);587
}589
/// Darwin: UDP probes from an ordinary UDP socket; the ICMP errors they590
/// cause arrive on the session's ICMP socket, matched by the quoted ports.591
class _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
@override606
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
@override616
_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
@override631
void close() => s.p.close(fd);632
}634
/// Darwin: a non-blocking TCP connect with the TTL set; routers answer the635
/// SYN with time exceeded (seen on the ICMP socket), the destination with636
/// SYN-ACK (connected) or a reset (refused).637
class _DarwinTcpProber implements _Prober {638
final _Session s;639
final int port;640
int fd = -1, _source = 0;641
_DarwinTcpProber(this.s, this.port);643
@override644
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 on659
// 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
@override685
_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.fd693
..events = Posix.pollIn694
..revents = 0;695
fds[1]696
..fd = fd697
..events = Posix.pollOut698
..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 message720
// 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
@override745
void close() => _closeSocket();746
}748
/// Linux/Android: UDP probes on a socket with IP_RECVERR; ICMP errors come749
/// back on its error queue together with the original destination port.750
class _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
@override768
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
@override776
_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
@override803
void close() => s.p.close(fd);804
}806
typedef _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.814
class _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 own819
// identifier, so on Linux replies are matched by sequence only (the kernel820
// 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.ePerm844
? '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 echo937
final status = message.kind == IcmpKind.echoReply938
? ProbeStatus.reply939
: _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. On950
/// 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.ref955
..fd = fd956
..events = Posix.pollIn957
..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.timeExceeded970
: type == (v6 ? IcmpType.unreachable6 : IcmpType.unreachable4)971
? IcmpKind.unreachable972
: 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));1003
}1004
}1006
/// Linux: reads the error queue of [socket], waiting up to [micros] for1007
/// an entry. Each entry carries the offending packet's payload, its1008
/// 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.ref1013
..fd = socket1014
..events = Posix.pollIn1015
..revents = 0;1016
final ready = p.poll(_poll, 1, (micros / 1000).ceil());1017
if (ready <= 0 || _poll.ref.revents & Posix.pollErr == 0) return out;1018
}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.ref1027
..base = _buffer1028
..length = _bufferSize;1029
msg.ref1030
..name = name1031
..nameLength = 1281032
..iov = iov1033
..iovLength = 11034
..control = control1035
..controlLength = controlSize1036
..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
));1048
}1049
} finally {1050
calloc.free(name);1051
calloc.free(control);1052
calloc.free(iov);1053
calloc.free(msg);1054
}1055
}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;1077
}1078
final offender = ee.length >= 16 + 81079
? p.address(Uint8List.fromList(ee.sublist(16)), ee.length - 16)1080
: null;1081
return (ee[5], ee[6], offender);1082
}1083
offset += (cmsg.length + word - 1) & ~(word - 1);1084
}1085
return null;1086
}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);1095
}1096
}SHA-256
52e6da745dda60fed30df54fc70d9b3cfc04db7c2faa0ec73601b9293970d524CH-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