Skip to content

Latest commit

 

History

History
994 lines (809 loc) · 28.9 KB

File metadata and controls

994 lines (809 loc) · 28.9 KB

🛠️ Troubleshooting & Debug Guide

Complete debugging solutions for React Native OpenVPN integration
Comprehensive troubleshooting guide with practical solutions and debugging tools

🎯 Quick Navigation

🎯 Category 📝 Description 🔗 Link
🤖 Android Issues Permission, crashes, notifications View →
📱 iOS Issues Extensions, entitlements, simulator View →
🌐 Connection Problems Authentication, network, state issues View →
🚀 Production Issues Release builds, performance, optimization View →
🔧 Debug Tools Logging, testing, diagnostics View →
📞 Getting Help Bug reports, community resources View →

🚨 Quick Issue Reference

🔍 Issue 📱 Platform ⚠️ Severity Quick Solution
Permission Denied 🤖 Android 🔴 Critical Call prepare() before connecting
Network Extension Not Found 🍎 iOS 🔴 Critical Check bundle identifier configuration
Connection Fails Immediately 🌐 Both 🟡 Medium Verify credentials and config
State Not Updating 🌐 Both 🟢 Low Check event listener setup
Production Failures 🌐 Both 🟡 Medium Check release build configuration

🤖 Android Issues

Common Android-specific problems and their solutions

🚫 Permission Denied

🎯 SymptomsConnection fails with "VPN permission denied" error
🔄 Statusprepare() returns false
🧠 Root CauseAndroid requires explicit VPN permission from user
⚠️ Severity🔴 Critical - Blocks all VPN functionality

💡 Solution:

const connectWithPermission = async () => {
  try {
    // ✅ Check if permission is already granted
    const isPrepared = await OpenVPN.isPrepared();
    
    if (!isPrepared) {
      // 🔑 Request permission from user
      const granted = await OpenVPN.prepare();
      
      if (!granted) {
        Alert.alert(
          '🔐 Permission Required',
          'VPN permission is required to establish secure connection.',
          [
            { text: 'Cancel', style: 'cancel' },
            { text: 'Settings', onPress: () => Linking.openSettings() }
          ]
        );
        return;
      }
    }
    
    // 🚀 Now safe to connect
    await OpenVPN.connect(config);
  } catch (error) {
    console.error('❌ Permission error:', error);
  }
};

🛡️ Best Practice:

Always check permissions before attempting connection on Android


💥 VPN Service Crashes

🎯 SymptomsApp crashes when connecting, "VPN service stopped unexpectedly"
⚠️ Severity🔴 Critical - App instability

🧠 Common Causes:

  1. 📱 Device incompatibility with OpenVPN3
  2. 🧠 Insufficient memory allocation
  3. ⚙️ Invalid configuration parameters

💡 Solutions:

🔧 Use OpenVPN 2.x for compatibility:

androidOptions: {
  useOpenVPN3: false,
  compatibilityMode: AndroidCompatibilityMode.OpenVPN_2_5_x,
}

🛡️ Add crash detection and fallback:

const connectSafely = async () => {
  try {
    await OpenVPN.connect(config);
  } catch (error) {
    if (error.message.includes('service') || error.message.includes('crash')) {
      // 🔄 Fallback to legacy mode
      await OpenVPN.connect({
        ...config,
        androidOptions: {
          ...config.androidOptions,
          useOpenVPN3: false,
          compatibilityMode: AndroidCompatibilityMode.OpenVPN_2_4_x,
        }
      });
    }
  }
};

🔔 Notification Issues

🎯 SymptomsVPN notification not showing or actions not working
⚠️ Severity🟡 Medium - Affects user experience

💡 Solution:

// ✅ Ensure all required notification fields are provided
androidOptions: {
  Notification: {
    openActivityPackageName: 'com.yourapp.MainActivity', // Must match exactly
    titleNotification: '🔐 VPN Connection',
    titleConnected: '✅ Secure connection active',
    titleConnecting: '🔄 Establishing connection...',
    showDisconnectAction: true,
    titleDisconnectButton: '🔌 Disconnect',
  }
}

📱 Android 13+ Requirements:

Add notification permission to AndroidManifest.xml:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Request permission at runtime:

import { PermissionsAndroid } from 'react-native';

const requestNotificationPermission = async () => {
  if (Platform.OS === 'android' && Platform.Version >= 33) {
    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
    );
    return granted === PermissionsAndroid.RESULTS.GRANTED;
  }
  return true;
};

📱 iOS Issues

iOS-specific challenges and comprehensive solutions

🚫 Network Extension Not Found

🎯 Symptoms"Network Extension not found" error on iOS
🔄 StatusConnection fails immediately
⚠️ Severity🔴 Critical - No VPN functionality

🧠 Common Causes:

  1. 🏷️ Incorrect bundle identifier configuration
  2. 🎯 Missing Network Extension target in Xcode
  3. 📂 App Groups not properly configured

💡 Solutions:

🔧 Verify Bundle Identifier:

iOSOptions: {
  // ⚠️ Must match your Network Extension target exactly
  networkExtensionBundleIdentifier: 'com.yourapp.vpn-extension',
  localizedDescription: '🔐 My VPN Connection',
  disconnectOnSleep: false,
  onDemandEnabled: false,
}

📱 Xcode Configuration Checklist:

  • ✅ Network Extension target exists in your project
  • ✅ Bundle identifier matches exactly
  • ✅ App Groups enabled for both main app and extension
  • ✅ Correct provisioning profiles selected

🔍 Debug Logging:

const debugConnect = async () => {
  console.log('🏷️ Bundle ID:', 'com.yourapp.vpn-extension');
  console.log('⚙️ Config:', iOSOptions);
  
  try {
    await OpenVPN.connect(config);
    console.log('✅ Connection successful');
  } catch (error) {
    console.error('❌ iOS Connection Error:', error);
    console.error('📋 Error details:', JSON.stringify(error, null, 2));
  }
};

🔐 Entitlements Issues

🎯 Symptoms"Missing entitlements" error, App Store rejection
⚠️ Severity🔴 Critical - Prevents deployment

💡 Solution:

📄 Main App Entitlements:

<!-- YourApp.entitlements -->
<key>com.apple.developer.networking.networkextension</key>
<array>
    <string>packet-tunnel-provider</string>
</array>

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.yourapp.vpn</string>
</array>

🔌 Network Extension Entitlements:

<!-- VPNExtension.entitlements -->
<key>com.apple.developer.networking.networkextension</key>
<array>
    <string>packet-tunnel-provider</string>
</array>

<key>com.apple.security.application-groups</key>
<array>
    <string>group.com.yourapp.vpn</string>
</array>

🎯 Important Notes:

  • 🔗 App Group identifier must match in both entitlements
  • 📱 Both main app and extension need Network Extension capability
  • 🏪 Required for App Store approval

🖥️ iOS Simulator Limitations

🎯 SymptomsVPN appears connected but no traffic routes
🔄 StatusConnection timeouts in simulator
🧠 Root CauseiOS Simulator doesn't support VPN functionality
⚠️ Severity🟡 Testing Issue - Development only

💡 Solution:

Always test VPN functionality on physical iOS devices

const checkPlatform = () => {
  if (__DEV__ && Platform.OS === 'ios') {
    Alert.alert(
      '📱 Development Note',
      'VPN functionality requires physical iOS device. Simulator testing is limited.',
      [{ text: 'OK', style: 'default' }]
    );
  }
};

🧪 Testing Recommendations:

  • ✅ Use physical iPhone/iPad for VPN testing
  • 🔧 Test both development and release builds
  • 📊 Verify on multiple iOS versions
  • 🌐 Test different network conditions

🌐 Connection Problems

Cross-platform connection issues and debugging strategies

⚡ Connection Fails Immediately

🎯 SymptomsConnection state goes directly to ERROR
🔄 StatusNo network traffic through VPN
⚠️ Severity🟡 Medium - Configuration issue

🔍 Debugging Steps:

1️⃣ Validate Configuration:

const validateConfig = (config: ConnectionParams) => {
  const errors = [];
  
  if (!config.username) errors.push('❌ Username missing');
  if (!config.password) errors.push('❌ Password missing');
  if (!config.openVPNConfig && !config.openVPNConfigLocalFile) {
    errors.push('❌ OpenVPN config missing');
  }
  
  if (errors.length > 0) {
    throw new Error(`🚫 Configuration errors: ${errors.join(', ')}`);
  }
  
  console.log('✅ Configuration validation passed');
};

2️⃣ Test Network Connectivity:

const testConnectivity = async () => {
  try {
    const response = await fetch('https://google.com', { timeout: 5000 });
    console.log('🌐 Network available:', response.status === 200);
    return response.status === 200;
  } catch (error) {
    console.error('❌ Network test failed:', error);
    throw new Error('🚫 No internet connection');
  }
};

3️⃣ Enable Verbose Logging (Android):

androidOptions: {
  useCustomConfig: true,
  customOptions: 'verb 4\nlog /storage/emulated/0/openvpn.log'
}

🔐 Authentication Failures

🎯 Symptoms"Authentication failed" error, connection timeouts
⚠️ Severity🟡 Medium - Credential or server issue

🧠 Common Causes:

  1. 🔑 Incorrect credentials
  2. 📜 Server certificate issues
  3. ⏰ Time synchronization problems

💡 Solutions:

🔑 Credential Validation:

const validateCredentials = async (username: string, password: string) => {
  // ✅ Implement your credential validation logic
  if (!username || username.length < 3) {
    throw new Error('❌ Invalid username');
  }
  
  if (!password || password.length < 6) {
    throw new Error('❌ Invalid password');
  }
  
  console.log('✅ Credentials validation passed');
};

📜 Certificate Troubleshooting:

androidOptions: {
  // 🛡️ Disable strict certificate checking for testing
  tlsProfileSecurity: TLSSecurityProfile.LEGACY,
  expectServerTLSCert: false,
  certificateHostnameCheck: false,
  
  // 📊 Enable detailed logging
  useCustomConfig: true,
  customOptions: 'verb 4\nauth-retry interact'
}

🔄 State Not Updating

🎯 SymptomsUI doesn't reflect connection changes
🔄 StatusState listener not triggered
⚠️ Severity🟢 Low - UI synchronization issue

🧠 Common Causes:

  1. 🎧 Event listener not properly registered
  2. 🔄 Component unmounted before state change
  3. 🔀 Multiple listeners interfering

💡 Solution:

const VPNComponent = () => {
  const [vpnState, setVpnState] = useState(ConnectionState.DISCONNECTED);
  
  useEffect(() => {
    // 🎧 Single listener with proper cleanup
    const subscription = OpenVPN.addOpenVPNStateChangeListener((state) => {
      console.log('🔄 State change:', state.state);
      setVpnState(state.state);
    });
    
    // 📋 Request initial state
    OpenVPN.requestCurrentState();
    
    // 🧹 Cleanup on unmount
    return () => {
      console.log('🗑️ Removing VPN state listener');
      subscription.remove();
    };
  }, []); // ✅ Empty dependency array
  
  return (
    <View>
      <Text>🔄 Current State: {vpnState}</Text>
      {vpnState === ConnectionState.CONNECTED && <Text> Connected</Text>}
      {vpnState === ConnectionState.CONNECTING && <Text>🔄 Connecting...</Text>}
      {vpnState === ConnectionState.ERROR && <Text> Error</Text>}
    </View>
  );
};

🚀 Production Issues

Release build challenges and performance optimization

🏭 VPN Works in Dev, Fails in Production

🎯 SymptomsDevelopment works perfectly, production builds fail
⚠️ Severity🔴 Critical - Deployment blocker

🧠 Common Causes:

  1. 📦 Missing native dependencies in release build
  2. 🔒 Proguard/R8 obfuscation issues (Android)
  3. 🏷️ Bundle identifier mismatches (iOS)

🤖 Android Solutions:

🔧 Update Proguard Rules:

# 🔐 React Native OpenVPN Protection
-keep class com.openvpn.** { *; }
-keep class net.openvpn.ovpn3.** { *; }
-keep class de.blinkt.openvpn.** { *; }

# 🚫 Don't warn about missing classes
-dontwarn com.openvpn.**
-dontwarn net.openvpn.ovpn3.**
-dontwarn de.blinkt.openvpn.**

🧪 Test Release Build Locally:

cd android
./gradlew assembleRelease
adb install app/build/outputs/apk/release/app-release.apk

📱 iOS Solutions:

🔍 Verify Archive Build:

  • ✅ Test with Archive build, not just Release scheme
  • ✅ Check both app and extension are included in archive
  • ✅ Verify provisioning profiles are correct

🔍 Debug Release Issues:

const logBuildInfo = () => {
  console.log('🏗️ Build type:', __DEV__ ? 'Development' : 'Production');
  console.log('🏷️ Bundle ID check:', bundleIdentifier);
  console.log('📱 Platform:', Platform.OS, Platform.Version);
};

⚡ Performance Issues

🎯 SymptomsHigh battery drain, app unresponsive, memory leaks
⚠️ Severity🟡 Medium - User experience impact

💡 Optimization Solutions:

🎧 Optimize State Listeners:

// ❌ Bad: Multiple listeners
useEffect(() => {
  const listener1 = OpenVPN.addOpenVPNStateChangeListener(handler1);
  const listener2 = OpenVPN.addOpenVPNStateChangeListener(handler2);
  // Creates unnecessary overhead
}, []);

// ✅ Good: Single listener with multiplexing
useEffect(() => {
  const subscription = OpenVPN.addOpenVPNStateChangeListener((state) => {
    handler1(state);
    handler2(state);
    // Single listener handles all cases
  });
  
  return () => subscription.remove();
}, []);

🔔 Reduce Notification Updates (Android):

androidOptions: {
  Notification: {
    showTimer: false, // 🔋 Reduces battery usage
    showDisconnectAction: true,
    // ✅ Only essential notification info
  }
}

🧠 Memory Management:

// ✅ Properly cleanup resources
const useVPNConnection = () => {
  useEffect(() => {
    const subscription = OpenVPN.addOpenVPNStateChangeListener(handleStateChange);
    
    return () => {
      subscription.remove();
      // 🧹 Additional cleanup if needed
      OpenVPN.removeAllListeners?.();
    };
  }, []);
};

🔧 Build Configuration Issues

🎯 ProblemComplex build environment issues
⚠️ Severity🟡 Medium - Environment specific

🛠️ Android Build Troubleshooting:

📋 Gradle Configuration:

// android/app/build.gradle
android {
    packagingOptions {
        pickFirst '**/libc++_shared.so'
        pickFirst '**/libjsc.so'
    }
    
    // ✅ Ensure native libraries are included
    splits {
        abi {
            enable false
        }
    }
}

📱 iOS Build Troubleshooting:

🎯 Xcode Settings:

  • ✅ Ensure "Build Active Architecture Only" is NO for Release
  • ✅ Check that both app and extension have same iOS deployment target
  • ✅ Verify all required frameworks are linked

🔍 Debug Build Issues:

const debugBuildInfo = () => {
  console.log('📊 Build Configuration:');
  console.log('- Development:', __DEV__);
  console.log('- Platform:', Platform.OS);
  console.log('- Version:', Platform.Version);
  console.log('- Bundle ID:', bundleIdentifier);
};

🔧 Debug Tools

Advanced debugging tools and diagnostic techniques

📊 Enable Detailed Logging

🎯 PurposeCapture comprehensive connection logs for troubleshooting
📱 PlatformAndroid & iOS (different approaches)

🤖 Android Logging:

androidOptions: {
  useCustomConfig: true,
  customOptions: [
    'verb 4',                                           // 📊 Verbose logging level
    'log /storage/emulated/0/Documents/openvpn.log',   // 📝 Log file location
    'status /storage/emulated/0/Documents/openvpn-status.log 10' // 📈 Status updates
  ].join('\n')
}

📱 iOS Logging:

// In your Network Extension
override func startTunnel(options: [String : NSObject]?) throws {
    NSLog("🚀 VPN Extension starting with options: \(options ?? [:])")
    // Your implementation
}

📋 Access Logs:

const downloadLogs = async () => {
  try {
    // 🤖 Android: Access via file system
    if (Platform.OS === 'android') {
      const logPath = '/storage/emulated/0/Documents/openvpn.log';
      console.log('📁 Log file location:', logPath);
    }
    
    // 📱 iOS: Use system console
    if (Platform.OS === 'ios') {
      console.log('📱 Check Xcode console or device logs');
    }
  } catch (error) {
    console.error('❌ Log access error:', error);
  }
};

🌐 Network Testing

🎯 PurposeVerify VPN functionality and IP address changes
🔧 UsageAutomated testing and validation

🧪 Comprehensive VPN Test:

const testVPNConnection = async () => {
  console.log('🧪 Testing VPN connection...');
  
  try {
    // 1️⃣ Test before VPN
    console.log('📡 Testing connection before VPN...');
    const beforeIP = await fetch('https://api.ipify.org?format=json', {
      timeout: 10000
    }).then(r => r.json());
    console.log('🌐 IP before VPN:', beforeIP.ip);
    
    // 2️⃣ Connect VPN
    console.log('🔄 Connecting to VPN...');
    await OpenVPN.connect(config);
    
    // 3️⃣ Wait for stable connection
    await new Promise(resolve => setTimeout(resolve, 8000));
    
    // 4️⃣ Verify connection state
    const currentState = await OpenVPN.getCurrentState();
    console.log('📊 Current VPN state:', currentState);
    
    if (currentState !== ConnectionState.CONNECTED) {
      throw new Error(`❌ VPN not connected. State: ${currentState}`);
    }
    
    // 5️⃣ Test after VPN
    console.log('📡 Testing connection after VPN...');
    const afterIP = await fetch('https://api.ipify.org?format=json', {
      timeout: 10000
    }).then(r => r.json());
    console.log('🌐 IP after VPN:', afterIP.ip);
    
    // 6️⃣ Verify IP change
    if (beforeIP.ip !== afterIP.ip) {
      console.log('✅ VPN is working - IP changed successfully');
      console.log(`🔄 ${beforeIP.ip}${afterIP.ip}`);
      return true;
    } else {
      console.log('❌ VPN may not be working - IP unchanged');
      return false;
    }
    
  } catch (error) {
    console.error('🚫 VPN test failed:', error);
    return false;
  }
};

🔍 Network Diagnostics:

const runNetworkDiagnostics = async () => {
  const diagnostics = {
    timestamp: new Date().toISOString(),
    tests: {}
  };
  
  // 🌐 Basic connectivity
  try {
    const response = await fetch('https://google.com', { timeout: 5000 });
    diagnostics.tests.basicConnectivity = {
      status: '✅ PASS',
      responseTime: Date.now(),
      httpStatus: response.status
    };
  } catch (error) {
    diagnostics.tests.basicConnectivity = {
      status: '❌ FAIL',
      error: error.message
    };
  }
  
  // 🔒 SSL/TLS test
  try {
    const response = await fetch('https://www.howsmyssl.com/a/check', { timeout: 5000 });
    const tlsInfo = await response.json();
    diagnostics.tests.tlsSupport = {
      status: '✅ PASS',
      tlsVersion: tlsInfo.tls_version
    };
  } catch (error) {
    diagnostics.tests.tlsSupport = {
      status: '❌ FAIL',
      error: error.message
    };
  }
  
  console.log('📊 Network Diagnostics:', JSON.stringify(diagnostics, null, 2));
  return diagnostics;
};

🔍 Connection State Monitoring

🎯 PurposeReal-time monitoring and debugging of connection states
🎧 UsageDevelopment and production debugging

📊 Advanced State Monitor:

const createStateMonitor = () => {
  const stateHistory = [];
  const maxHistorySize = 50;
  
  const monitor = {
    start: () => {
      console.log('🎧 Starting VPN state monitor...');
      
      const subscription = OpenVPN.addOpenVPNStateChangeListener((state) => {
        const timestamp = new Date().toISOString();
        const stateInfo = {
          timestamp,
          state: state.state,
          message: state.message || '',
          duration: state.duration || 0
        };
        
        // 📝 Add to history
        stateHistory.push(stateInfo);
        if (stateHistory.length > maxHistorySize) {
          stateHistory.shift();
        }
        
        // 📊 Log state change with visual indicators
        const stateEmoji = {
          [ConnectionState.CONNECTED]: '✅',
          [ConnectionState.CONNECTING]: '🔄',
          [ConnectionState.DISCONNECTED]: '⚪',
          [ConnectionState.DISCONNECTING]: '🔄',
          [ConnectionState.ERROR]: '❌',
          [ConnectionState.RECONNECTING]: '🔁'
        };
        
        console.log(`${stateEmoji[state.state] || '❓'} State: ${state.state} | ${timestamp}`);
        if (state.message) console.log(`   💬 Message: ${state.message}`);
        if (state.duration) console.log(`   ⏱️ Duration: ${state.duration}s`);
      });
      
      return subscription;
    },
    
    getHistory: () => stateHistory,
    
    exportLogs: () => {
      const logs = stateHistory.map(entry => 
        `${entry.timestamp} | ${entry.state} | ${entry.message} | ${entry.duration}s`
      ).join('\n');
      
      console.log('📋 State History Export:\n', logs);
      return logs;
    }
  };
  
  return monitor;
};

// 🚀 Usage
const monitor = createStateMonitor();
const subscription = monitor.start();

// 🧹 Cleanup when done
// subscription.remove();

📞 Getting Help

Community resources and professional support options

📋 Before Opening an Issue

Required StepsComplete these steps before seeking help

🔍 Pre-Issue Checklist:

  1. Review this troubleshooting guide - Check all relevant sections
  2. 📱 Test on physical devices - Avoid simulator-only testing
  3. ⚙️ Verify your configuration - Double-check all parameters
  4. 🏗️ Check platform-specific requirements - iOS/Android setup
  5. 📊 Enable verbose logging - Capture detailed error information
  6. 🧪 Test with minimal configuration - Isolate the issue

🐛 Bug Report Information

📊 Required DataInclude this information for faster resolution

📋 Bug Report Template:

const getBugReportInfo = async () => {
  const info = {
    // 🏗️ Environment Information
    platform: Platform.OS,
    platformVersion: Platform.Version,
    reactNativeVersion: '0.73.x', // Your RN version
    libraryVersion: '1.x.x',      // react-native-openvpn version
    
    // 📱 Device Information
    deviceInfo: {
      // Add device-specific details
      model: 'iPhone 15 Pro / Samsung Galaxy S24',
      osVersion: Platform.Version,
      isEmulator: false // Always test on real devices
    },
    
    // 🔄 VPN State Information
    vpnState: await OpenVPN.getCurrentState(),
    isPrepared: Platform.OS === 'android' ? await OpenVPN.isPrepared() : 'N/A',
    
    // ⚙️ Configuration (sanitized)
    config: {
      hasConfig: !!config.openVPNConfig,
      hasLocalFile: !!config.openVPNConfigLocalFile,
      hasCredentials: !!(config.username && config.password),
      platform: Platform.OS,
      // ⚠️ DO NOT include actual credentials or server details
    },
    
    // 📊 Error Information
    errorDetails: {
      // Include specific error messages
      // Include stack traces if available
    }
  };
  
  console.log('🐛 Bug Report Info:', JSON.stringify(info, null, 2));
  return info;
};

🔒 Security Notes:

  • Never include usernames, passwords, or server configurations
  • Never include private keys or certificates
  • Do include sanitized configuration structure
  • Do include error messages and stack traces

🌐 Community Resources

🔗 Resource📝 Description🎯 Best For
GitHub IssuesBug reports and feature requestsTechnical problems and enhancements
GitHub DiscussionsCommunity support and Q&AGeneral questions and sharing experiences
Examples DirectoryWorking code samplesImplementation guidance
DocumentationComplete API and setup guidesLearning and reference

🔗 Quick Links:


🏆 Contributing to Solutions

🤝 How You Can Help🌟 Impact
Share working configurationsHelp others with similar setups
Report platform-specific issuesImprove library compatibility
Contribute documentation improvementsEnhance developer experience
Submit tested bug fixesIncrease library stability

📝 Contribution Guidelines:

  • 🧪 Test thoroughly on both iOS and Android
  • 📚 Update documentation for any changes
  • 🔧 Follow coding standards established in the project
  • Include unit tests for new features

💡 Pro Tips for Faster Resolution

💡 TipBenefit
Create minimal reproduction caseFaster debugging and resolution
Test on multiple devices/versionsBetter understanding of scope
Check recent issues for duplicatesAvoid duplicate reports
Provide before/after comparisonsClear understanding of expected behavior

🎯 Remember: Most VPN issues are configuration-related. Double-check your setup before assuming it's a library bug!

🔧 Quick Validation: Test with the minimal configuration from our examples first, then gradually add your custom settings to isolate issues.