import 'dart:typed_data'; /// ICMP (RFC 792) and ICMPv6 (RFC 4443) message types used by ping and /// traceroute. abstract final class IcmpType { static const echoReply4 = 0, unreachable4 = 3, echoRequest4 = 8; static const timeExceeded4 = 11; static const unreachable6 = 1, timeExceeded6 = 3; static const echoRequest6 = 128, echoReply6 = 129; } /// What a received ICMP message means for an outstanding echo request. enum IcmpKind { echoReply, timeExceeded, unreachable, other } /// A parsed ICMP message. For error messages, [id] and [seq] come from the /// echo request quoted inside the error, so the error can be matched to the /// probe that caused it. class IcmpMessage { final IcmpKind kind; final int type, code; final int? id, seq; /// For errors about a UDP or TCP packet: the quoted transport protocol /// (17 or 6) and its source and destination ports. final int? protocol, sourcePort, destinationPort; const IcmpMessage( this.kind, this.type, this.code, { this.id, this.seq, this.protocol, this.sourcePort, this.destinationPort, }); /// ICMP "port unreachable": the destination itself answered a UDP probe. bool portUnreachable(bool v6) => kind == IcmpKind.unreachable && code == (v6 ? 4 : 3); @override String toString() => 'IcmpMessage($kind, type $type code $code ' 'id $id seq $seq)'; } /// Internet checksum (RFC 1071). int icmpChecksum(Uint8List data) { var sum = 0; for (var i = 0; i + 1 < data.length; i += 2) { sum += (data[i] << 8) | data[i + 1]; } if (data.length.isOdd) sum += data.last << 8; while (sum >> 16 != 0) { sum = (sum & 0xffff) + (sum >> 16); } return ~sum & 0xffff; } /// An echo request with [size] payload bytes. The IPv4 checksum is filled /// in; for ICMPv6 the kernel computes it over the pseudo-header. Uint8List buildEchoRequest({ required bool v6, required int id, required int seq, int size = 56, }) { final packet = Uint8List(8 + size); packet[0] = v6 ? IcmpType.echoRequest6 : IcmpType.echoRequest4; packet[4] = (id >> 8) & 0xff; packet[5] = id & 0xff; packet[6] = (seq >> 8) & 0xff; packet[7] = seq & 0xff; for (var i = 0; i < size; i++) { packet[8 + i] = (0x20 + i) & 0xff; } if (!v6) { final sum = icmpChecksum(packet); packet[2] = sum >> 8; packet[3] = sum & 0xff; } return packet; } int _u16(Uint8List b, int at) => (b[at] << 8) | b[at + 1]; /// Parses a received message. Darwin's ICMPv4 datagram sockets deliver the /// IPv4 header first ([ipHeader] true); Linux ping sockets and all ICMPv6 /// sockets deliver the ICMP message alone. Returns null for data too short /// or malformed to be an ICMP message. IcmpMessage? parseIcmp( Uint8List data, { required bool v6, bool ipHeader = false, }) { var at = 0; if (!v6 && ipHeader) { if (data.isEmpty || data[0] >> 4 != 4) return null; at = (data[0] & 0x0f) * 4; if (at < 20) return null; } if (data.length < at + 8) return null; final type = data[at], code = data[at + 1]; final reply = v6 ? IcmpType.echoReply6 : IcmpType.echoReply4; if (type == reply) { return IcmpMessage( IcmpKind.echoReply, type, code, id: _u16(data, at + 4), seq: _u16(data, at + 6), ); } final exceeded = v6 ? IcmpType.timeExceeded6 : IcmpType.timeExceeded4; final unreachable = v6 ? IcmpType.unreachable6 : IcmpType.unreachable4; final kind = type == exceeded ? IcmpKind.timeExceeded : type == unreachable ? IcmpKind.unreachable : IcmpKind.other; if (kind == IcmpKind.other) return IcmpMessage(kind, type, code); // The error quotes the offending packet: its IP header, then at least the // first 8 bytes of our echo request (type, code, checksum, id, sequence). final quoted = at + 8; int inner, protocol; if (v6) { // Fixed IPv6 header; extension headers are not followed. if (data.length < quoted + 40 + 8) return IcmpMessage(kind, type, code); protocol = data[quoted + 6]; inner = quoted + 40; } else { if (data.length < quoted + 20 || data[quoted] >> 4 != 4) { return IcmpMessage(kind, type, code); } final length = (data[quoted] & 0x0f) * 4; if (length < 20 || data.length < quoted + length + 8) { return IcmpMessage(kind, type, code); } protocol = data[quoted + 9]; inner = quoted + length; } if (protocol == 17 || protocol == 6) { // UDP and TCP both start with source and destination port. return IcmpMessage( kind, type, code, protocol: protocol, sourcePort: _u16(data, inner), destinationPort: _u16(data, inner + 2), ); } if (protocol != (v6 ? 58 : 1)) return IcmpMessage(kind, type, code); final request = v6 ? IcmpType.echoRequest6 : IcmpType.echoRequest4; if (data[inner] != request) return IcmpMessage(kind, type, code); return IcmpMessage( kind, type, code, id: _u16(data, inner + 4), seq: _u16(data, inner + 6), ); } /// The echo id and sequence of a packet this device sent, as returned with /// a Linux error-queue notification. ({int id, int seq})? sentEcho(Uint8List data, {required bool v6}) { final request = v6 ? IcmpType.echoRequest6 : IcmpType.echoRequest4; if (data.length < 8 || data[0] != request) return null; return (id: _u16(data, 4), seq: _u16(data, 6)); }