Ver Fonte

Resolver bug fixes

- Mitigate Go issue 40569, which causes resolver.hasRoutableIPv6Interface
  to fail on Android.

- Document SendFeedback custom DNS resolver limitations.
Rod Hynes há 3 anos atrás
pai
commit
b3e9b6c770

+ 68 - 3
MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java

@@ -47,6 +47,7 @@ import java.io.PrintStream;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
 import java.net.Inet4Address;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.SocketException;
@@ -420,6 +421,11 @@ public class PsiphonTunnel {
                                         // Unused on Android.
                                         // Unused on Android.
                                         return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
                                         return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
                                     }
                                     }
+
+                                    @Override
+                                    public long hasIPv6Route() {
+                                        return PsiphonTunnel.hasIPv6Route(context, logger);
+                                    }
                                 },
                                 },
                                 new PsiphonProviderNoticeHandler() {
                                 new PsiphonProviderNoticeHandler() {
                                     @Override
                                     @Override
@@ -455,8 +461,9 @@ public class PsiphonTunnel {
                                         }
                                         }
                                     }
                                     }
                                 },
                                 },
-                                // Do not use IPv6 synthesizer for android
-                                false);
+                                false,   // Do not use IPv6 synthesizer for Android
+                                true     // Use hasIPv6Route on Android
+                                );
                     } catch (java.lang.Exception e) {
                     } catch (java.lang.Exception e) {
                         callbackQueue.submit(new Runnable() {
                         callbackQueue.submit(new Runnable() {
                             @Override
                             @Override
@@ -642,6 +649,11 @@ public class PsiphonTunnel {
             return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
             return PsiphonTunnel.iPv6Synthesize(IPv4Addr);
         }
         }
 
 
+        @Override
+        public long hasIPv6Route() {
+            return PsiphonTunnel.hasIPv6Route(mHostService.getContext(), mHostService);
+        }
+
         @Override
         @Override
         public String getNetworkID() {
         public String getNetworkID() {
             return PsiphonTunnel.getNetworkID(mHostService.getContext());
             return PsiphonTunnel.getNetworkID(mHostService.getContext());
@@ -707,6 +719,17 @@ public class PsiphonTunnel {
         return IPv4Addr;
         return IPv4Addr;
     }
     }
 
 
+    private static long hasIPv6Route(Context context, HostLogger logger) {
+        boolean hasRoute = false;
+        try {
+            hasRoute = hasIPv6Route(context);
+        } catch (Exception e) {
+            logger.onDiagnosticMessage("failed to check IPv6 route: " + e.getMessage());
+        }
+        // TODO: change to bool return value once gobind supports that type
+        return hasRoute ? 1 : 0;
+    }
+
     private static String getNetworkID(Context context) {
     private static String getNetworkID(Context context) {
 
 
         // TODO: getActiveNetworkInfo is deprecated in API 29; once
         // TODO: getActiveNetworkInfo is deprecated in API 29; once
@@ -789,7 +812,8 @@ public class PsiphonTunnel {
                     "",
                     "",
                     new PsiphonProviderShim(this),
                     new PsiphonProviderShim(this),
                     isVpnMode(),
                     isVpnMode(),
-                    false        // Do not use IPv6 synthesizer for android
+                    false,   // Do not use IPv6 synthesizer for Android
+                    true     // Use hasIPv6Route on Android
                     );
                     );
         } catch (java.lang.Exception e) {
         } catch (java.lang.Exception e) {
             throw new Exception("failed to start Psiphon library", e);
             throw new Exception("failed to start Psiphon library", e);
@@ -1382,6 +1406,47 @@ public class PsiphonTunnel {
         return dnsAddresses;
         return dnsAddresses;
     }
     }
 
 
+    private static boolean hasIPv6Route(Context context) throws Exception {
+
+            try {
+                // This logic mirrors the logic in
+                // psiphon/common/resolver.hasRoutableIPv6Interface. That
+                // function currently doesn't work on Android due to Go's
+                // net.InterfaceAddrs failing on Android SDK 30+ (see Go issue
+                // 40569). hasIPv6Route provides the same functionality via a
+                // callback into Java code.
+
+                for (NetworkInterface netInterface : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+                    if (netInterface.isUp() &&
+                        !netInterface.isLoopback() &&
+                        !netInterface.isPointToPoint()) {
+                        for (InetAddress address : Collections.list(netInterface.getInetAddresses())) {
+
+                            // Per https://developer.android.com/reference/java/net/Inet6Address#textual-representation-of-ip-addresses,
+                            // "Java will never return an IPv4-mapped address.
+                            //  These classes can take an IPv4-mapped address as
+                            //  input, both in byte array and text
+                            //  representation. However, it will be converted
+                            //  into an IPv4 address." As such, when the type of
+                            //  the IP address is Inet6Address, this should be
+                            //  an actual IPv6 address.
+
+                            if (address instanceof Inet6Address &&
+                                !address.isLinkLocalAddress() &&
+                                !address.isSiteLocalAddress() &&
+                                !address.isMulticastAddress ()) {
+                                return true;
+                            }
+                        }
+                    }
+                }
+                } catch (SocketException e) {
+                throw new Exception("hasIPv6Route failed", e);
+            }
+
+            return false;
+    }
+
     //----------------------------------------------------------------------------------------------
     //----------------------------------------------------------------------------------------------
     // Exception
     // Exception
     //----------------------------------------------------------------------------------------------
     //----------------------------------------------------------------------------------------------

+ 17 - 3
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -41,6 +41,7 @@
 NSErrorDomain _Nonnull const PsiphonTunnelErrorDomain = @"com.psiphon3.ios.PsiphonTunnelErrorDomain";
 NSErrorDomain _Nonnull const PsiphonTunnelErrorDomain = @"com.psiphon3.ios.PsiphonTunnelErrorDomain";
 
 
 const BOOL UseIPv6Synthesizer = TRUE; // Must always use IPv6Synthesizer for iOS
 const BOOL UseIPv6Synthesizer = TRUE; // Must always use IPv6Synthesizer for iOS
+const BOOL UseHasIPv6RouteGetter = FALSE;
 
 
 /// Error codes which can returned by PsiphonTunnel
 /// Error codes which can returned by PsiphonTunnel
 typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
 typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
@@ -309,6 +310,7 @@ typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
                 self,
                 self,
                 self->tunnelWholeDevice, // useDeviceBinder
                 self->tunnelWholeDevice, // useDeviceBinder
                 UseIPv6Synthesizer,
                 UseIPv6Synthesizer,
+                UseHasIPv6RouteGetter,
                 &e);
                 &e);
             
             
             if (e != nil) {
             if (e != nil) {
@@ -1283,6 +1285,11 @@ typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
     return [IPv6Synthesizer IPv4ToIPv6:IPv4Addr];
     return [IPv6Synthesizer IPv4ToIPv6:IPv4Addr];
 }
 }
 
 
+- (NSString *)hasIPv6Route:()BOOL {
+    // Unused on iOS.
+    return FALSE;
+}
+
 - (NSString *)getNetworkID {
 - (NSString *)getNetworkID {
     return [NetworkID getNetworkID:[self->reachability currentReachabilityStatus]];
     return [NetworkID getNetworkID:[self->reachability currentReachabilityStatus]];
 }
 }
@@ -1738,9 +1745,16 @@ typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
 
 
         PsiphonProviderNetwork *networkInfoProvider = [[PsiphonProviderNetwork alloc] init];
         PsiphonProviderNetwork *networkInfoProvider = [[PsiphonProviderNetwork alloc] init];
 
 
-        GoPsiStartSendFeedback(psiphonConfig, feedbackJson, uploadPath,
-                               innerFeedbackHandler, networkInfoProvider, noticeHandler,
-                               UseIPv6Synthesizer, &err);
+        GoPsiStartSendFeedback(
+            psiphonConfig,
+            feedbackJson,
+            uploadPath,
+            innerFeedbackHandler,
+            networkInfoProvider,
+            noticeHandler,
+            UseIPv6Synthesizer,
+            UseHasIPv6RouteGetter,
+            &err);
         if (err != nil) {
         if (err != nil) {
             NSError *outError = [NSError errorWithDomain:PsiphonTunnelErrorDomain
             NSError *outError = [NSError errorWithDomain:PsiphonTunnelErrorDomain
                                                     code:PsiphonTunnelErrorCodeSendFeedbackError
                                                     code:PsiphonTunnelErrorCodeSendFeedbackError

+ 38 - 10
MobileLibrary/psi/psi.go

@@ -47,6 +47,7 @@ type PsiphonProviderNetwork interface {
 	HasNetworkConnectivity() int
 	HasNetworkConnectivity() int
 	GetNetworkID() string
 	GetNetworkID() string
 	IPv6Synthesize(IPv4Addr string) string
 	IPv6Synthesize(IPv4Addr string) string
+	HasIPv6Route() int
 }
 }
 
 
 type PsiphonProvider interface {
 type PsiphonProvider interface {
@@ -54,6 +55,9 @@ type PsiphonProvider interface {
 	PsiphonProviderNetwork
 	PsiphonProviderNetwork
 	BindToDevice(fileDescriptor int) (string, error)
 	BindToDevice(fileDescriptor int) (string, error)
 
 
+	// TODO: move GetDNSServersAsString to PsiphonProviderNetwork to
+	// facilitate custom tunnel-core resolver support in SendFeedback.
+
 	// GetDNSServersAsString must return a comma-delimited list of DNS server
 	// GetDNSServersAsString must return a comma-delimited list of DNS server
 	// addresses. A single string return value is used since gobind does not
 	// addresses. A single string return value is used since gobind does not
 	// support string slice types.
 	// support string slice types.
@@ -118,12 +122,13 @@ var stopController context.CancelFunc
 var controllerWaitGroup *sync.WaitGroup
 var controllerWaitGroup *sync.WaitGroup
 
 
 func Start(
 func Start(
-	configJson,
-	embeddedServerEntryList,
+	configJson string,
+	embeddedServerEntryList string,
 	embeddedServerEntryListFilename string,
 	embeddedServerEntryListFilename string,
 	provider PsiphonProvider,
 	provider PsiphonProvider,
-	useDeviceBinder,
-	useIPv6Synthesizer bool) error {
+	useDeviceBinder bool,
+	useIPv6Synthesizer bool,
+	useHasIPv6RouteGetter bool) error {
 
 
 	controllerMutex.Lock()
 	controllerMutex.Lock()
 	defer controllerMutex.Unlock()
 	defer controllerMutex.Unlock()
@@ -152,19 +157,24 @@ func Start(
 		return fmt.Errorf("error loading configuration file: %s", err)
 		return fmt.Errorf("error loading configuration file: %s", err)
 	}
 	}
 
 
-	config.NetworkConnectivityChecker = wrappedProvider
+	// Set up callbacks.
 
 
+	config.NetworkConnectivityChecker = wrappedProvider
 	config.NetworkIDGetter = wrappedProvider
 	config.NetworkIDGetter = wrappedProvider
+	config.DNSServerGetter = wrappedProvider
 
 
 	if useDeviceBinder {
 	if useDeviceBinder {
 		config.DeviceBinder = wrappedProvider
 		config.DeviceBinder = wrappedProvider
-		config.DNSServerGetter = wrappedProvider
 	}
 	}
 
 
 	if useIPv6Synthesizer {
 	if useIPv6Synthesizer {
 		config.IPv6Synthesizer = wrappedProvider
 		config.IPv6Synthesizer = wrappedProvider
 	}
 	}
 
 
+	if useHasIPv6RouteGetter {
+		config.HasIPv6RouteGetter = wrappedProvider
+	}
+
 	// All config fields should be set before calling Commit.
 	// All config fields should be set before calling Commit.
 
 
 	err = config.Commit(true)
 	err = config.Commit(true)
@@ -371,7 +381,8 @@ func StartSendFeedback(
 	feedbackHandler PsiphonProviderFeedbackHandler,
 	feedbackHandler PsiphonProviderFeedbackHandler,
 	networkInfoProvider PsiphonProviderNetwork,
 	networkInfoProvider PsiphonProviderNetwork,
 	noticeHandler PsiphonProviderNoticeHandler,
 	noticeHandler PsiphonProviderNoticeHandler,
-	useIPv6Synthesizer bool) error {
+	useIPv6Synthesizer bool,
+	useHasIPv6RouteGetter bool) error {
 
 
 	// Cancel any ongoing uploads.
 	// Cancel any ongoing uploads.
 	StopSendFeedback()
 	StopSendFeedback()
@@ -395,14 +406,27 @@ func StartSendFeedback(
 		return fmt.Errorf("error loading configuration file: %s", err)
 		return fmt.Errorf("error loading configuration file: %s", err)
 	}
 	}
 
 
-	config.NetworkConnectivityChecker = networkInfoProvider
+	// Set up callbacks.
 
 
+	config.NetworkConnectivityChecker = networkInfoProvider
 	config.NetworkIDGetter = networkInfoProvider
 	config.NetworkIDGetter = networkInfoProvider
 
 
 	if useIPv6Synthesizer {
 	if useIPv6Synthesizer {
 		config.IPv6Synthesizer = networkInfoProvider
 		config.IPv6Synthesizer = networkInfoProvider
 	}
 	}
 
 
+	if useHasIPv6RouteGetter {
+		config.HasIPv6RouteGetter = networkInfoProvider
+	}
+
+	// Limitation: config.DNSServerGetter is not set up in the SendFeedback
+	// case, as we don't currently implement network path and system DNS
+	// server monitoring for SendFeedback in the platform code. To ensure we
+	// fallback to the system resolver and don't always use the custom
+	// resolver with alternate DNS servers, clear that config field (this may
+	// still be set via tactics).
+	config.DNSResolverAlternateServers = nil
+
 	// All config fields should be set before calling Commit.
 	// All config fields should be set before calling Commit.
 
 
 	err = config.Commit(true)
 	err = config.Commit(true)
@@ -510,17 +534,21 @@ func (p *mutexPsiphonProvider) IPv6Synthesize(IPv4Addr string) string {
 	return p.p.IPv6Synthesize(IPv4Addr)
 	return p.p.IPv6Synthesize(IPv4Addr)
 }
 }
 
 
+func (p *mutexPsiphonProvider) HasIPv6Route() int {
+	p.Lock()
+	defer p.Unlock()
+	return p.p.HasIPv6Route()
+}
+
 func (p *mutexPsiphonProvider) GetDNSServersAsString() string {
 func (p *mutexPsiphonProvider) GetDNSServersAsString() string {
 	p.Lock()
 	p.Lock()
 	defer p.Unlock()
 	defer p.Unlock()
 	return p.p.GetDNSServersAsString()
 	return p.p.GetDNSServersAsString()
 }
 }
 
 
-// GetDNSServers implements psiphon.DNSServerGetter.
 func (p *mutexPsiphonProvider) GetDNSServers() []string {
 func (p *mutexPsiphonProvider) GetDNSServers() []string {
 	p.Lock()
 	p.Lock()
 	defer p.Unlock()
 	defer p.Unlock()
-	// Convert the workaround format, used for gobind, to a normal string slice.
 	return strings.Split(p.p.GetDNSServersAsString(), ",")
 	return strings.Split(p.p.GetDNSServersAsString(), ",")
 }
 }
 
 

+ 22 - 7
psiphon/common/resolver/resolver.go

@@ -73,6 +73,13 @@ type NetworkConfig struct {
 	// endpoint. IPv6Synthesize may be nil.
 	// endpoint. IPv6Synthesize may be nil.
 	IPv6Synthesize func(IPv4 string) string
 	IPv6Synthesize func(IPv4 string) string
 
 
+	// HasIPv6Route should return true when the host has an IPv6 route.
+	// Resolver has an internal implementation, hasRoutableIPv6Interface, to
+	// determine this, but it can fail on some platforms ("route ip+net:
+	// netlinkrib: permission denied" on Android, for example; see Go issue
+	// 40569). When HasIPv6Route is nil, the internal implementation is used.
+	HasIPv6Route func() bool
+
 	// LogWarning is an optional callback which is used to log warnings and
 	// LogWarning is an optional callback which is used to log warnings and
 	// transient errors which would otherwise not be recorded or returned.
 	// transient errors which would otherwise not be recorded or returned.
 	LogWarning func(error)
 	LogWarning func(error)
@@ -897,14 +904,22 @@ func (r *Resolver) updateNetworkState(networkID string) {
 	// similarly use NAT 64 (on iOS; on Android, 464XLAT will handle this
 	// similarly use NAT 64 (on iOS; on Android, 464XLAT will handle this
 	// transparently).
 	// transparently).
 	if updateIPv6Route {
 	if updateIPv6Route {
-		hasIPv6Route, err := hasRoutableIPv6Interface()
-		if err != nil {
-			// Log warning and proceed without IPv6.
-			r.networkConfig.logWarning(
-				errors.Tracef("unable to determine IPv6 route: %v", err))
-			hasIPv6Route = false
+
+		if r.networkConfig.HasIPv6Route != nil {
+
+			r.hasIPv6Route = r.networkConfig.HasIPv6Route()
+
+		} else {
+
+			hasIPv6Route, err := hasRoutableIPv6Interface()
+			if err != nil {
+				// Log warning and proceed without IPv6.
+				r.networkConfig.logWarning(
+					errors.Tracef("unable to determine IPv6 route: %v", err))
+				hasIPv6Route = false
+			}
+			r.hasIPv6Route = hasIPv6Route
 		}
 		}
-		r.hasIPv6Route = hasIPv6Route
 	}
 	}
 
 
 	// Update the list of system DNS servers. It's not an error condition here
 	// Update the list of system DNS servers. It's not an error condition here

+ 5 - 0
psiphon/config.go

@@ -288,6 +288,11 @@ type Config struct {
 	// doc.
 	// doc.
 	IPv6Synthesizer IPv6Synthesizer
 	IPv6Synthesizer IPv6Synthesizer
 
 
+	// HasIPv6RouteGetter is an interface that allows tunnel-core to call into
+	// the host application to determine if the host has an IPv6 route. See:
+	// HasIPv6RouteGetter doc.
+	HasIPv6RouteGetter HasIPv6RouteGetter
+
 	// DNSServerGetter is an interface that enables tunnel-core to call into
 	// DNSServerGetter is an interface that enables tunnel-core to call into
 	// the host application to discover the native network DNS server
 	// the host application to discover the native network DNS server
 	// settings. See: DNSServerGetter doc.
 	// settings. See: DNSServerGetter doc.

+ 14 - 0
psiphon/net.go

@@ -155,6 +155,14 @@ type IPv6Synthesizer interface {
 	IPv6Synthesize(IPv4Addr string) string
 	IPv6Synthesize(IPv4Addr string) string
 }
 }
 
 
+// HasIPv6RouteGetter defines the interface to the external HasIPv6Route
+// provider which calls into the host application to determine if the host
+// has an IPv6 route.
+type HasIPv6RouteGetter interface {
+	// TODO: change to bool return value once gobind supports that type
+	HasIPv6Route() int
+}
+
 // NetworkIDGetter defines the interface to the external GetNetworkID
 // NetworkIDGetter defines the interface to the external GetNetworkID
 // provider, which returns an identifier for the host's current active
 // provider, which returns an identifier for the host's current active
 // network.
 // network.
@@ -335,6 +343,12 @@ func NewResolver(config *Config, useBindToDevice bool) *resolver.Resolver {
 		networkConfig.IPv6Synthesize = config.IPv6Synthesizer.IPv6Synthesize
 		networkConfig.IPv6Synthesize = config.IPv6Synthesizer.IPv6Synthesize
 	}
 	}
 
 
+	if config.HasIPv6RouteGetter != nil {
+		networkConfig.HasIPv6Route = func() bool {
+			return config.HasIPv6RouteGetter.HasIPv6Route() == 1
+		}
+	}
+
 	return resolver.NewResolver(networkConfig, config.GetNetworkID())
 	return resolver.NewResolver(networkConfig, config.GetNetworkID())
 }
 }