فهرست منبع

Update library glue to support packet tunnel

Adam Pritchard 8 سال پیش
والد
کامیت
a6275cb52e

+ 51 - 16
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h

@@ -103,18 +103,18 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
    - `FetchRoutesTimeoutSeconds`
    - `HttpProxyOriginServerTimeoutSeconds`
  - Fields which should only be set by Psiphon proper:
+   - `TunnelWholeDevice`
    - `LocalHttpProxyPort`
    - `LocalSocksProxyPort`
-   - `TunnelWholeDevice`: For stats purposes, but must be accurate. Defaults to 0 (false).
  @endcode
 
  @note All other config fields must not be set.
 
  See the tunnel-core config code for details about the fields.
  https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/config.go
- 
+
  @return  JSON string with config that should used to run the Psiphon tunnel, or NULL on error.
- 
+
  Swift: @code func getPsiphonConfig() -> String? @endcode
  */
 - (NSString * _Nullable)getPsiphonConfig;
@@ -138,7 +138,7 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
  */
 - (void)onDiagnosticMessage:(NSString * _Nonnull)message;
 
-/*! 
+/*!
  Called when the tunnel is in the process of connecting.
  Swift: @code func onConnecting() @endcode
  */
@@ -178,8 +178,8 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
 /*!
  Called when the device's Internet connection state is interrupted.
  This may mean that it had connectivity and now doesn't, or went from Wi-Fi to
- WWAN or vice versa. 
- @note For many/most apps, the response to this callback should be to restart 
+ WWAN or vice versa.
+ @note For many/most apps, the response to this callback should be to restart
  the Psiphon tunnel. It will eventually notice and begin reconnecting, but it
  may take much longer, depending on attempts to use the tunnel.
  Swift: @code func onDeviceInternetConnectivityInterrupted() @endcode
@@ -288,6 +288,13 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
  */
 - (void)onClientUpgradeDownloaded:(NSString * _Nonnull)filename;
 
+/*!
+ Implementing this method is *required* if whole device mode is enabled (and otherwise should not be implemented).
+ The implementation of this must send the given packet to the device.
+ @param packet  The data packet to send to the device.
+ */
+- (void)sendToDevice:(NSMutableData * _Nonnull)packet;
+
 @end
 
 /*!
@@ -299,7 +306,7 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
  Returns an instance of PsiphonTunnel. This is either a new instance or the pre-existing singleton. If an instance already exists, it will be stopped when this function is called.
  @param tunneledAppDelegate  The delegate implementation to use for callbacks.
  @return  The PsiphonTunnel instance.
- Swift: @code open class func newPsiphonTunnel(_ tunneledAppDelegate: TunneledAppDelegate) -> Self @endcode
+ Swift: @code class func newPsiphonTunnel(_ tunneledAppDelegate: TunneledAppDelegate) -> Self @endcode
  */
 + (PsiphonTunnel * _Nonnull)newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate;
 
@@ -307,36 +314,64 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
  Start connecting the PsiphonTunnel. Returns before connection is complete -- delegate callbacks (such as `onConnected` and `onConnectionStateChanged`) are used to indicate progress and state.
  @param ifNeeded  If TRUE, the tunnel will only be started if it's not already connected and healthy. If FALSE, the tunnel will be forced to stop and reconnect.
  @return TRUE if the connection start was successful, FALSE otherwise.
- Swift: @code open func start(_ ifNeeded: Bool) -> Bool @endcode
+ Swift: @code func start(_ ifNeeded: Bool) -> Bool @endcode
  */
 - (BOOL)start:(BOOL)ifNeeded;
 
 /*!
  Stop the tunnel (regardless of its current connection state). Returns before full stop is complete -- `TunneledAppDelegate::onExiting` is called when complete.
- Swift: @code open func stop() @endcode
+ Swift: @code func stop() @endcode
  */
 - (void)stop;
 
 /*!
  Returns the current tunnel connection state.
  @return  The current connection state.
- Swift: @code open func getConnectionState() -> PsiphonConnectionState @endcode
+ Swift: @code func getConnectionState() -> PsiphonConnectionState @endcode
  */
 - (PsiphonConnectionState)getConnectionState;
 
 /*!
  Provides the port number of the local SOCKS proxy. Only valid when currently connected (will return 0 otherwise).
  @return  The current local SOCKS proxy port number.
- Swift: @code open func getLocalSocksProxyPort() -> Int @endcode
+ Swift: @code func getLocalSocksProxyPort() -> Int @endcode
  */
--(NSInteger)getLocalSocksProxyPort;
+- (NSInteger)getLocalSocksProxyPort;
 
 /*!
  Provides the port number of the local HTTP proxy. Only valid when currently connected (will return 0 otherwise).
  @return  The current local HTTP proxy port number.
- Swift: @code open func getLocalHttpProxyPort() -> Int @endcode
+ Swift: @code func getLocalHttpProxyPort() -> Int @endcode
  */
--(NSInteger)getLocalHttpProxyPort;
+- (NSInteger)getLocalHttpProxyPort;
+
+/*!
+ Only valid in whole device mode. Provides the MTU the packet tunnel requires the device to use.
+ @return  The MTU size.
+ Swift: @code func getPacketTunnelMTU() -> Int @endcode
+ */
+- (long)getPacketTunnelMTU;
+
+/*!
+ Only valid in whole device mode. Provides the DNS resolver IP address that is provided by the packet tunnel to the device.
+  @return  The IP address of the DNS resolver as a string.
+  Swift: @code func getPacketTunnelDNSResolverIPv4Address() -> String @endcode
+ */
+- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv4Address;
+
+/*!
+ Only valid in whole device mode. Provides the DNS resolver IP address that is provided by the packet tunnel to the device.
+ @return  The IP address of the DNS resolver as a string.
+  Swift: @code func getPacketTunnelDNSResolverIPv6Address() -> String @endcode
+ */
+- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv6Address;
+
+/*!
+ Only valid in whole device mode. This function should be called when a packet has been read from the device and should be sent through the tunnel.
+ @param packet  The data packet.
+ Swift: @code func received(fromDevice packet: Data) @endcode
+ */
+- (void)receivedFromDevice:(NSMutableData *_Nonnull)packet;
 
 /*!
  Upload a feedback package to Psiphon Inc. The app collects feedback and diagnostics information in a particular format, then calls this function to upload it for later investigation.
@@ -345,11 +380,11 @@ typedef NS_ENUM(NSInteger, PsiphonConnectionState)
  @param b64EncodedPublicKey  The key that will be used to encrypt the payload before uploading.
  @param uploadServer  The server and path to which the data will be uploaded.
  @param uploadServerHeaders  The request headers that will be used when uploading.
- Swift: @code open func sendFeedback(_ feedbackJson: String, publicKey b64EncodedPublicKey: String, uploadServer: String, uploadServerHeaders: String) @endcode
+ Swift: @code func sendFeedback(_ feedbackJson: String, publicKey b64EncodedPublicKey: String, uploadServer: String, uploadServerHeaders: String) @endcode
  */
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
         uploadServer:(NSString * _Nonnull)uploadServer
  uploadServerHeaders:(NSString * _Nonnull)uploadServerHeaders;
 
-@end
+ @end

+ 71 - 20
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -29,7 +29,7 @@
 #import "JailbreakCheck/JailbreakCheck.h"
 
 
-@interface PsiphonTunnel () <GoPsiPsiphonProvider>
+@interface PsiphonTunnel () <GoPsiPsiphonProvider, GoPsiPacketTunnelDeviceSender>
 
 @property (weak) id <TunneledAppDelegate> tunneledAppDelegate;
 
@@ -43,6 +43,9 @@
 
     Reachability* reachability;
     NetworkStatus previousNetworkStatus;
+
+    BOOL tunnelWholeDevice;
+    GoPsiPacketTunnelDeviceBridge* packetTunnelDeviceBridge; // only used in whole device mode
 }
 
 - (id)init {
@@ -50,6 +53,8 @@
     atomic_init(&localSocksProxyPort, 0);
     atomic_init(&localHttpProxyPort, 0);
     reachability = [Reachability reachabilityForInternetConnection];
+    tunnelWholeDevice = FALSE;
+    packetTunnelDeviceBridge = NULL;
 
     return self;
 }
@@ -57,7 +62,7 @@
 #pragma mark - PsiphonTunnel public methods
 
 // See comment in header
-+(PsiphonTunnel * _Nonnull) newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate {
++ (PsiphonTunnel * _Nonnull)newPsiphonTunnel:(id<TunneledAppDelegate> _Nonnull)tunneledAppDelegate {
     @synchronized (PsiphonTunnel.self) {
         // Only one PsiphonTunnel instance may exist at a time, as the underlying
         // go.psi.Psi and tun2socks implementations each contain global state.
@@ -67,7 +72,7 @@
         dispatch_once(&onceToken, ^{
             sharedInstance = [[self alloc] init];
         });
-        
+
         [sharedInstance stop];
         sharedInstance.tunneledAppDelegate = tunneledAppDelegate;
 
@@ -76,7 +81,7 @@
 }
 
 // See comment in header
--(BOOL) start:(BOOL)ifNeeded {
+- (BOOL)start:(BOOL)ifNeeded {
     if (ifNeeded) {
         return [self startIfNeeded];
     }
@@ -84,14 +89,11 @@
     return [self start];
 }
 
--(BOOL) start {
+- (BOOL)start {
     @synchronized (PsiphonTunnel.self) {
         [self stop];
         [self logMessage:@"Starting Psiphon library"];
 
-        // Not supported on iOS.
-        const BOOL useDeviceBinder = FALSE;
-
         // Must always use IPv6Synthesizer for iOS
         const BOOL useIPv6Synthesizer = TRUE;
         
@@ -116,7 +118,7 @@
                            configStr,
                            embeddedServerEntries,
                            self,
-                           useDeviceBinder,
+                           tunnelWholeDevice, // useDeviceBinder
                            useIPv6Synthesizer,
                            &e);
             
@@ -142,7 +144,7 @@
     }
 }
 
--(BOOL) startIfNeeded {
+- (BOOL)startIfNeeded {
     PsiphonConnectionState connState = [self getConnectionState];
     BOOL localProxyAlive = [self isLocalProxyAlive];
 
@@ -164,7 +166,7 @@
 }
 
 // See comment in header.
--(void) stop {
+- (void)stop {
     @synchronized (PsiphonTunnel.self) {
         [self logMessage: @"Stopping Psiphon library"];
 
@@ -176,26 +178,47 @@
 
         atomic_store(&localSocksProxyPort, 0);
         atomic_store(&localHttpProxyPort, 0);
+        packetTunnelDeviceBridge = NULL;
 
         [self changeConnectionStateTo:PsiphonConnectionStateDisconnected evenIfSameState:NO];
     }
 }
 
 // See comment in header.
--(PsiphonConnectionState) getConnectionState {
+- (PsiphonConnectionState)getConnectionState {
     return atomic_load(&connectionState);
 }
 
 // See comment in header.
--(NSInteger) getLocalSocksProxyPort {
+- (NSInteger)getLocalSocksProxyPort {
     return atomic_load(&localSocksProxyPort);
 }
 
 // See comment in header.
--(NSInteger) getLocalHttpProxyPort {
+- (NSInteger)getLocalHttpProxyPort {
     return atomic_load(&localHttpProxyPort);
 }
 
+// See comment in header.
+- (long)getPacketTunnelMTU {
+    return GoPsiGetPacketTunnelMTU();
+}
+
+// See comment in header.
+- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv4Address {
+    return GoPsiGetPacketTunnelDNSResolverIPv4Address();
+}
+
+// See comment in header.
+- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv6Address {
+    return GoPsiGetPacketTunnelDNSResolverIPv6Address();
+}
+
+// See comment in header.
+- (void)receivedFromDevice:(NSMutableData * _Nonnull)packet {
+    [packetTunnelDeviceBridge receivedFromDevice:packet];
+}
+
 // See comment in header.
 - (void)sendFeedback:(NSString * _Nonnull)feedbackJson
            publicKey:(NSString * _Nonnull)b64EncodedPublicKey
@@ -215,7 +238,7 @@
  Build the config string for the tunnel.
  @returns String containing the JSON config. `nil` on error.
  */
--(NSString * _Nullable)getConfig {
+- (NSString * _Nullable)getConfig {
     // tunneledAppDelegate is a weak reference, so check it.
     if (self.tunneledAppDelegate == nil) {
         [self logMessage:@"tunneledApp delegate lost"];
@@ -374,9 +397,19 @@
         [self logMessage:[NSString stringWithFormat: @"UpgradeDownloadFilename overridden from '%@' to '%@'", defaultUpgradeDownloadFilename, config[@"UpgradeDownloadFilename"]]];
     }
 
+    //
+    // Tunnel Whole Device (defaults to not whole device)
+    //
+
+    // We'll record our state about what mode we're in.
+    tunnelWholeDevice = ([config[@"TunnelWholeDevice"] integerValue] == 1);
+    if (tunnelWholeDevice && ![self.tunneledAppDelegate respondsToSelector:@selector(sendToDevice:)]) {
+        [self logMessage:@"If TunnelWholeDevice is desired, then sendToDevice must be implemented"];
+        return nil;
+    }
+
     // Other optional fields not being altered. If not set, their defaults will be used:
     // * EstablishTunnelTimeoutSeconds
-    // * TunnelWholeDevice
     // * LocalSocksProxyPort
     // * LocalHttpProxyPort
     // * UpstreamProxyUrl
@@ -669,10 +702,10 @@
 #pragma mark - GoPsiPsiphonProvider protocol implementation (private)
 
 - (BOOL)bindToDevice:(long)fileDescriptor error:(NSError **)error {
-    // This function is only called in TunnelWholeDevice mode
-    
-    // TODO: Does this function ever get called?
-    
+    if (!tunnelWholeDevice) {
+        return FALSE;
+    }
+
     // TODO: Determine if this is robust.
     unsigned int interfaceIndex = if_nametoindex("ap1");
     
@@ -687,11 +720,13 @@
 
 - (NSString *)getPrimaryDnsServer {
     // This function is only called when BindToDevice is used/supported.
+    // TODO: Implement correctly
     return @"8.8.8.8";
 }
 
 - (NSString *)getSecondaryDnsServer {
     // This function is only called when BindToDevice is used/supported.
+    // TODO: Implement correctly
     return @"8.8.4.4";
 }
 
@@ -722,6 +757,22 @@
     [self handlePsiphonNotice:noticeJSON];
 }
 
+- (GoPsiPacketTunnelDeviceBridge*)getPacketTunnelDeviceBridge {
+    if (!packetTunnelDeviceBridge) {
+        packetTunnelDeviceBridge = GoPsiNewPacketTunnelDeviceBridge(self);
+    }
+    return packetTunnelDeviceBridge;
+}
+
+
+#pragma mark - GoPsiPacketTunnelDeviceWriter protocol implementation (private)
+
+- (void)sendToDevice:(NSMutableData *)packet {
+    // The check to see if the delegate responds to this optional selector was
+    // done when processing the config, so we're not going to do it again for every packet.
+    [self.tunneledAppDelegate sendToDevice:packet];
+}
+
 
 #pragma mark - Helpers (private)