import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; /// Minimal POSIX socket bindings for unprivileged ICMP. /// /// Darwin (iOS, macOS) and Linux (Android) both allow `SOCK_DGRAM` sockets /// with `IPPROTO_ICMP`/`IPPROTO_ICMPV6` without root. They differ in /// constants, in the `sockaddr` layout, in whether the IPv4 header is /// delivered, and in how ICMP errors arrive: Darwin delivers them as normal /// messages, Linux through the socket error queue (`IP_RECVERR`). final class PollFd extends Struct { @Int32() external int fd; @Int16() external int events; @Int16() external int revents; } final class IoVec extends Struct { external Pointer base; @IntPtr() external int length; } /// Linux `struct msghdr`; the FFI layout follows the 32/64-bit ABI. final class MsgHdr extends Struct { external Pointer name; @Uint32() external int nameLength; external Pointer iov; @IntPtr() external int iovLength; external Pointer control; @IntPtr() external int controlLength; @Int32() external int flags; } /// Linux `struct cmsghdr`. final class CmsgHdr extends Struct { @IntPtr() external int length; @Int32() external int level; @Int32() external int type; } class Posix { final bool darwin; final DynamicLibrary _lib; Posix._(this.darwin, this._lib); static Posix? _instance; /// Null where neither the Darwin nor the Linux implementation applies. static Posix? get current { if (_instance != null) return _instance; if (Platform.isIOS || Platform.isMacOS) { return _instance = Posix._(true, DynamicLibrary.process()); } if (Platform.isAndroid) { return _instance = Posix._(false, DynamicLibrary.open('libc.so')); } if (Platform.isLinux) { return _instance = Posix._(false, DynamicLibrary.process()); } return null; } // Constants (Darwin / Linux). int get afInet => 2; int get afInet6 => darwin ? 30 : 10; int get sockDgram => 2; int get sockStream => 1; int get ipprotoUdp => 17; int get ipprotoTcp => 6; int get solSocket => darwin ? 0xffff : 1; int get soError => darwin ? 0x1007 : 4; int get fGetFl => 3; int get fSetFl => 4; int get oNonBlock => darwin ? 0x4 : 0x800; // Don't Fragment: IP_DONTFRAG on Darwin; IP_MTU_DISCOVER = IP_PMTUDISC_DO // on Linux. IPv6: IPV6_DONTFRAG on Darwin; IPV6_MTU_DISCOVER on Linux. int get ipDontFrag => darwin ? 28 : 10; int get ipv6DontFrag => darwin ? 62 : 23; int get dontFragValue => darwin ? 1 : 2; int get ipprotoIp => 0; int get ipprotoIpv6 => 41; int get ipprotoIcmp => 1; int get ipprotoIcmpv6 => 58; int get ipTtl => darwin ? 4 : 2; int get ipv6UnicastHops => darwin ? 4 : 16; int get ipRecvErr => 11; int get ipv6RecvErr => 25; int get msgErrQueue => 0x2000; int get msgDontWait => darwin ? 0x80 : 0x40; static const pollIn = 0x1, pollOut = 0x4, pollErr = 0x8; // Linux sock_extended_err.ee_origin values. static const originIcmp = 2, originIcmp6 = 3; late final socket = _lib .lookupFunction< Int32 Function(Int32, Int32, Int32), int Function(int, int, int) >('socket'); late final close = _lib .lookupFunction('close'); late final setsockopt = _lib .lookupFunction< Int32 Function(Int32, Int32, Int32, Pointer, Uint32), int Function(int, int, int, Pointer, int) >('setsockopt'); late final sendto = _lib .lookupFunction< IntPtr Function( Int32, Pointer, IntPtr, Int32, Pointer, Uint32, ), int Function(int, Pointer, int, int, Pointer, int) >('sendto'); late final recvfrom = _lib .lookupFunction< IntPtr Function( Int32, Pointer, IntPtr, Int32, Pointer, Pointer, ), int Function( int, Pointer, int, int, Pointer, Pointer, ) >('recvfrom'); late final connect = _lib .lookupFunction< Int32 Function(Int32, Pointer, Uint32), int Function(int, Pointer, int) >('connect'); late final getsockname = _lib .lookupFunction< Int32 Function(Int32, Pointer, Pointer), int Function(int, Pointer, Pointer) >('getsockname'); late final getsockopt = _lib .lookupFunction< Int32 Function(Int32, Int32, Int32, Pointer, Pointer), int Function(int, int, int, Pointer, Pointer) >('getsockopt'); late final fcntl = _lib .lookupFunction< Int32 Function(Int32, Int32, VarArgs<(Int32,)>), int Function(int, int, int) >('fcntl'); late final recvmsg = _lib .lookupFunction< IntPtr Function(Int32, Pointer, Int32), int Function(int, Pointer, int) >('recvmsg'); // nfds_t is unsigned int on Darwin and unsigned long on Linux. late final int Function(Pointer, int, int) poll = darwin ? _lib.lookupFunction< Int32 Function(Pointer, Uint32, Int32), int Function(Pointer, int, int) >('poll') : _lib.lookupFunction< Int32 Function(Pointer, UnsignedLong, Int32), int Function(Pointer, int, int) >('poll'); late final Pointer Function() _errnoLocation = _lib .lookupFunction Function(), Pointer Function()>( darwin ? '__error' : (Platform.isAndroid ? '__errno' : '__errno_location'), ); /// The calling thread's errno, read right after a failed call. int get errno => _errnoLocation().value; // errno values that need a specific explanation. int get eAccess => 13; int get ePerm => 1; int get eNetUnreach => darwin ? 51 : 101; int get eHostUnreach => darwin ? 65 : 113; int get eInProgress => darwin ? 36 : 115; int get eConnRefused => darwin ? 61 : 111; int get eMsgSize => darwin ? 40 : 90; /// `struct sockaddr_in` / `sockaddr_in6` for [address] and [port]. Uint8List sockaddr(InternetAddress address, [int port = 0]) { final v6 = address.type == InternetAddressType.IPv6; final raw = address.rawAddress; final out = Uint8List(v6 ? 28 : 16); if (darwin) { out[0] = out.length; out[1] = v6 ? afInet6 : afInet; } else { final family = v6 ? afInet6 : afInet; out[0] = family & 0xff; out[1] = family >> 8; } out[2] = (port >> 8) & 0xff; out[3] = port & 0xff; out.setRange(v6 ? 8 : 4, v6 ? 24 : 8, raw); return out; } /// The port of a `sockaddr_in` / `sockaddr_in6` (network byte order). int port(Uint8List sockaddr) => sockaddr.length < 4 ? 0 : (sockaddr[2] << 8) | sockaddr[3]; /// The address in a `sockaddr_in` / `sockaddr_in6` returned by the kernel. InternetAddress? address(Uint8List sockaddr, int length) { if (length >= 24 && sockaddr.length >= 24) { final family = darwin ? sockaddr[1] : sockaddr[0] | (sockaddr[1] << 8); if (family == afInet6) { return InternetAddress.fromRawAddress(sockaddr.sublist(8, 24)); } } if (length >= 8 && sockaddr.length >= 8) { final family = darwin ? sockaddr[1] : sockaddr[0] | (sockaddr[1] << 8); if (family == afInet) { return InternetAddress.fromRawAddress(sockaddr.sublist(4, 8)); } } return null; } }