discoverMappingBehavior method

Future<NatMappingBehavior> discoverMappingBehavior()

Discovers the NAT mapping behavior

This test requires a STUN server that supports RFC 5780. It sends multiple binding requests to the same STUN server but with different CHANGE-REQUEST attributes to test how the NAT maps internal endpoints to external endpoints.

Implementation

Future<NatMappingBehavior> discoverMappingBehavior() async {
  try {
    // First, send a normal binding request to get our mapped address
    final socket1 = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
    final localPort = socket1.port;

    try {
      // Test 1: Get mapped address from primary address
      final response1 = await _sendRequest(socket1, StunMessage.createBindingRequest());
      if (response1 == null) {
        return NatMappingBehavior.unknown;
      }

      final mappedAddress1 = _extractMappedAddress(response1);
      if (mappedAddress1 == null) {
        return NatMappingBehavior.unknown;
      }

      // Get the OTHER-ADDRESS attribute to find the alternate address
      final otherAddress = StunMessage.extractOtherAddress(response1);
      if (otherAddress == null) {
        print('STUN server does not support RFC 5780 (no OTHER-ADDRESS attribute)');
        return NatMappingBehavior.unknown;
      }

      // Test 2: Send to alternate IP address
      final server = await stunClient.stunServer;
      final alternateServer = otherAddress.address;

      // Create a new socket with the same local port
      final socket2 = await RawDatagramSocket.bind(InternetAddress.anyIPv4, socket1.port);
      try {
        final request2 = StunMessage.createBindingRequest();
        final response2 = await _sendRequestToServer(socket2, request2, alternateServer, otherAddress.port);

        if (response2 == null) {
          return NatMappingBehavior.unknown;
        }

        final mappedAddress2 = _extractMappedAddress(response2);
        if (mappedAddress2 == null) {
          return NatMappingBehavior.unknown;
        }

        // Compare the mapped addresses
        if (mappedAddress1.port == mappedAddress2.port) {
          // Same port for different destination IP addresses
          // This is endpoint-independent mapping
          return NatMappingBehavior.endpointIndependent;
        }

        // Test 3: Send to primary IP but different port
        final socket3 = await RawDatagramSocket.bind(InternetAddress.anyIPv4, socket1.port);
        try {
          final request3 = StunMessage.createBindingRequest();
          // Use a different port on the primary server
          final differentPort = stunClient.stunPort + 1;
          final response3 = await _sendRequestToServer(socket3, request3, server, differentPort);

          if (response3 == null) {
            // If we can't get a response from a different port,
            // assume address-dependent mapping
            return NatMappingBehavior.addressDependent;
          }

          final mappedAddress3 = _extractMappedAddress(response3);
          if (mappedAddress3 == null) {
            return NatMappingBehavior.addressDependent;
          }

          // Compare the mapped addresses
          if (mappedAddress1.port == mappedAddress3.port) {
            // Same port for same IP but different port
            // This is address-dependent mapping
            return NatMappingBehavior.addressDependent;
          } else {
            // Different port for different destination port
            // This is address-and-port-dependent mapping
            return NatMappingBehavior.addressAndPortDependent;
          }
        } finally {
          socket3.close();
        }
      } finally {
        socket2.close();
      }
    } finally {
      socket1.close();
    }
  } catch (e) {
    print('Error discovering mapping behavior: $e');
    return NatMappingBehavior.unknown;
  }
}