query static method

Future<List<DNSResponseRecord>> query({
  1. required String domain,
  2. required DNSRecordType dnsRecordType,
  3. required DNSServer dnsServer,
  4. int timeout = 5000,
})

Made a DNS query.

Implementation

static Future<List<DNSResponseRecord>> query({
    required String domain,
    required DNSRecordType dnsRecordType,
    required DNSServer dnsServer,
    int timeout = 5000, // 5 seconds by default (in milliseconds)
}) async {

    final List<DNSResponseRecord> records = [ ];
    final Completer<List<DNSResponseRecord>> completer = Completer();

    if(dnsServer.protocol == DNSProtocol.udp) { // UDP connection

        // Construye el paquete de consulta DNS
        final Uint8List query = _buildQuery(domain, dnsRecordType);

        // Transform hostname to ip address
        InternetAddress? addr = InternetAddress.tryParse(dnsServer.host);

        if(addr == null) {

            addr = (await InternetAddress.lookup(dnsServer.host))
                .where((i) => i.type == InternetAddressType.IPv4)
                .firstOrNull;

            if(addr == null) {
                throw ArgumentError('Host not found: ${dnsServer.host}');
            }
        }

        // Envia el paquete al servidor DNS vía UDP
        final RawDatagramSocket socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
        socket.send(query, addr, dnsServer.port);

        // Timeout
        final timer = Timer(Duration(milliseconds: timeout), () {
            if (!completer.isCompleted) {
                socket.close();
                completer.completeError(TimeoutException('DNS query timeout'));
            }
        });

        // Event receiver
        socket.listen((RawSocketEvent event) {

            if (event == RawSocketEvent.read) {
                final Datagram? response = socket.receive();

                if (response != null) {
                    records.addAll(_parseResponse(response.data));
                    completer.complete(records);
                    socket.close(); // TODO: Multistream unsupported.
                    timer.cancel();
                }

            // } else if (event == RawSocketEvent.write) {

            } else if(event == RawSocketEvent.closed) {

                if(!completer.isCompleted) {
                    completer.complete(records);
                }

                timer.cancel();
            }
        });

    } else if(dnsServer.protocol == DNSProtocol.tcp) { // TCP connection

        // Construye el paquete de consulta DNS
        final Uint8List query = _buildQuery(domain, dnsRecordType);

        // Connect to server using TCP
        final Socket socket = await Socket.connect(dnsServer.host, dnsServer.port);

        // DNS sobre TCP requiere una cabecera de 2 bytes con la longitud
        final Uint8List lengthPrefix = Uint8List(2);
        lengthPrefix[0] = (query.length >> 8) & 0xFF;
        lengthPrefix[1] = query.length & 0xFF;

        // Escribe la longitud y luego el query
        socket.add(lengthPrefix);
        socket.add(query);

        // Timeout
        final Timer timer = Timer(Duration(milliseconds: timeout), () {

            // Finalize thread with error
            if (!completer.isCompleted) {
                completer.completeError(TimeoutException('DNS TCP query timeout'));
            }

            // Destroy socket connection
            socket.destroy();
        });

        // Stream parts
        int responseLength = 0;
        List<int> responseBuffer = [ ];

        // Bind socket events
        socket.listen(

            // On receive data event
            (List<int> bytes) {

                // First stream part
                if(responseBuffer.isEmpty) {

                    if (bytes.length < 2) {

                        // Cancel timeout
                        timer.cancel();

                        // Finalize thread with error
                        if (!completer.isCompleted) {
                            completer.completeError(FormatException('TCP response too short'));
                        }

                        // Destroy socket connection
                        socket.destroy();

                        // Finalize function
                        return;
                    }

                    // Leer longitud de la respuesta
                    responseLength = (bytes[0] << 8) | bytes[1];
                    if (bytes.length - 2 < responseLength) {

                        // Cancel timeout
                        timer.cancel();

                        // Finalize thread with error
                        if (!completer.isCompleted) {
                            completer.completeError(FormatException('Incomplete answer'));
                        }

                        // Destroy socket connection
                        socket.destroy();

                        // Finalize function
                        return;
                    }
                }

                // Add stream part
                responseBuffer.addAll(bytes);

                // End TCP parts
                if(responseBuffer.length >= (responseLength + 2)) {

                    // Cancel timeout
                    timer.cancel();

                    // Transform response bytes
                    records.addAll(_parseResponse(Uint8List.fromList(responseBuffer.sublist(2, responseLength + 2))));

                    // Finalize thread with the results
                    if (!completer.isCompleted) {
                        completer.complete(records);
                    }

                    // Destroy socket connection
                    socket.destroy();

                } // else { await for other TCP parts ... }
            },
            onError: (error) {

                // Cancel timeout
                timer.cancel();

                // Finalize thread with errors
                if (!completer.isCompleted) {
                    completer.completeError(error);
                }
            },
            cancelOnError: true,
        );

    } else if(dnsServer.protocol == DNSProtocol.doh) { // DoH (HTTPS) connection

        // Create the http client
        final client = http.Client();

        // Create http POST request
        client.post(
            Uri.parse('https://${dnsServer.host}:${dnsServer.port}${dnsServer.path}'),
            headers: dnsServer.headers,
            body: _buildQuery(domain, dnsRecordType),
        )

        // Manual timeout
        .timeout(Duration(milliseconds: timeout))

        .then((http.Response response) {

            // Process response bytes
            records.addAll(_parseResponse(response.bodyBytes));

            // Finalize thread with the results
            if (!completer.isCompleted) {
                completer.complete(records);
            }

            // End http connection
            client.close();
        })

        .catchError((error) {
            // Finalize thread with errors
            if (!completer.isCompleted) {
                completer.completeError(error);
            }
        });
    }

    return completer.future;
}