diff --git a/.gitignore b/.gitignore index cef89500..6aef4412 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ client/qrc_*.cpp client/ui_*.h client/ui_*.cpp client/Makefile* +client/fastlane/build/ client/*build-* client/AmneziaVPN.xcodeproj client/Debug-iphonesimulator/ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/Contents.json b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..25af9c29 --- /dev/null +++ b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,62 @@ +{ + "images" : [ + { + "filename" : "icon_40x40.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "filename" : "icon_60x60.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "filename" : "icon_58x58.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "filename" : "icon_87x87.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "filename" : "icon_80x80.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "filename" : "icon_120x120.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "filename" : "icon_120x120-1.png", + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "filename" : "icon_180x180.png", + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "filename" : "icon_1024x1024.png", + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_1024x1024.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_1024x1024.png new file mode 100644 index 00000000..695a30a5 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_1024x1024.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120-1.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120-1.png new file mode 100644 index 00000000..4349d194 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120-1.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120.png new file mode 100644 index 00000000..4349d194 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_120x120.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_180x180.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_180x180.png new file mode 100644 index 00000000..4bf6715a Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_180x180.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_40x40.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_40x40.png new file mode 100644 index 00000000..ad3d0746 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_40x40.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_58x58.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_58x58.png new file mode 100644 index 00000000..dbacdbe7 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_58x58.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_60x60.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_60x60.png new file mode 100644 index 00000000..0778ec28 Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_60x60.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_80x80.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_80x80.png new file mode 100644 index 00000000..8a1fdc1d Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_80x80.png differ diff --git a/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_87x87.png b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_87x87.png new file mode 100644 index 00000000..e57c6fbb Binary files /dev/null and b/client/AmneziaVPN/Images.xcassets/AppIcon.appiconset/icon_87x87.png differ diff --git a/client/AmneziaVPN/Images.xcassets/Contents.json b/client/AmneziaVPN/Images.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/client/AmneziaVPN/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/client/fastlane/Appfile b/client/fastlane/Appfile new file mode 100644 index 00000000..2e9184e1 --- /dev/null +++ b/client/fastlane/Appfile @@ -0,0 +1,7 @@ +app_identifier(["org.amnezia.AmneziaVPN", "org.amnezia.AmneziaVPN.network-extension"]) # The bundle identifier of your app +apple_id("dartsyms@gmail.com") # Your Apple email address + +itc_team_id("122591025") # App Store Connect Team ID +team_id("X7UJ388FXK") # Developer Portal Team ID +#itc_team_id("119381903") +#team_id("7VAGZXD78P") diff --git a/client/fastlane/Fastfile b/client/fastlane/Fastfile new file mode 100644 index 00000000..3027dbdc --- /dev/null +++ b/client/fastlane/Fastfile @@ -0,0 +1,197 @@ +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +project_name = "AmneziaVPN" +project_scheme = "AmneziaVPN" +project_identifier = "org.amnezia.AmneziaVPN" +itunes_username = "dartsyms@gmail.com" + +before_all do + ENV['GYM_SCHEME'] = project_scheme +end + +def tag_name(version_number, build_number) + "#{version_number}_#{build_number}" +end + +def incrementBuild + increment_build_number(xcodeproj: "./#{project_name}.xcodeproj") +end + +default_platform :ios + +# Fastfile actions accept additional configuration, but +# don't worry, fastlane will prompt you for required +# info which you can add here later +lane :beta do + # build your iOS app + build_app( + scheme: "AmneziaVPN", + export_method: "ad-hoc" + ) +end + +lane :incrementVersion do + increment_build_number( + xcodeproj: "./#{project_name}.xcodeproj" + ) + + version_number = get_version_number( + xcodeproj: "./#{project_name}.xcodeproj", + target: project_name + ) + + build_number = get_build_number( + xcodeproj: "./#{project_name}.xcodeproj" + ) + + puts "Version = #{version_number} Build = #{build_number}" +end + +desc "Update Certificates, Run All tests, Build app" +desc "Add tag to git, send to Testflight, slack notification" +lane :addToTestFlight do + certificates + clean_build_artifacts + + build_app( + scheme: project_scheme, + configuration: "Release", # Debug / Release + export_method: "app-store", # Valid values are: app-store, ad-hoc, package, enterprise, development, developer-id + clean: true, + include_bitcode: false, + include_symbols: true, + export_options: { + provisioningProfiles: { + "org.amnezia.AmneziaVPN"=>"match AppStore org.amnezia.AmneziaVPN", + "org.amnezia.AmneziaVPN.network-extension"=>"match development AmneziaVPN networkextension", + } + }, + output_directory: "fastlane/build/", # Destination directory. Defaults to current directory. + output_name: "#{project_name}.ipa" + ) + + testflight( + username: itunes_username, + app_identifier: project_identifier, + ipa: "fastlane/build/#{project_name}.ipa", + skip_waiting_for_build_processing: true + ) + + # Section for Slack + version_number = get_version_number( + xcodeproj: "./#{project_name}.xcodeproj", + target: project_name + ) + + build_number = get_build_number( + xcodeproj: "./#{project_name}.xcodeproj" + ) + + msg = "Build v.#{version_number} (#{build_number}) was Created for TestFlight" + puts msg + # msgToSlack(msg, true) + increment_build_number +end + +lane :certificates do + match( + username: itunes_username, + type: "appstore", + app_identifier: [project_identifier], + readonly: true, + git_url: "https://github.com/amnezia-vpn/amnezia-ios-certificates.git" + ) + notification(subtitle: "Finished", message: "Certificates done") +end + +lane :adhoc_certificates do + match( + username: itunes_username, + type: "adhoc", + app_identifier: [project_identifier], + readonly: true, + git_url: "https://github.com/amnezia-vpn/amnezia-ios-certificates.git" + ) + notification(subtitle: "Finished", message: "Certificates done") +end + +lane :createAPNS do + get_push_certificate( + force: true, # create a new profile, even if the old one is still valid + app_identifier: project_identifier, # optional app identifier, + username: itunes_username, + p12_password: apns_pass, + pem_name: apns_pem_name, + output_path: "fastlane/APNS", + save_private_key: true, + new_profile: proc do |profile_path| # this block gets called when a new profile was generated + puts profile_path # the absolute path to the new PEM file + # insert the code to upload the PEM file to the server + end + ) + + # msgToSlack("Successfully APNS Created", true) +end + +desc "Distribute app via Firebase for testers" +lane :firebase_test do + adhoc_certificates + build_ios_app( + scheme: project_scheme, + output_directory: "./fastlane/builds/#{project_scheme}", + output_name: "#{project_scheme}.ipa", + export_options: { + method: "ad-hoc", + compileBitcode: false + }, + clean: true, + include_bitcode: true, + include_symbols: true, + skip_package_ipa: false + ) + +# firebase_app_distribution( +# app: "1:880592554695:ios:af464a9ed02207f6284ce2", +# testers: "dartsyms@gmail.com, shogun14@yandex.ru, qa@wevied.com", +# release_notes: "Another build: ready to test." +# ) + + clean_build_artifacts + increment_build_number +end + +desc "Distribute app via Firebase for testers" +lane :distribute_firebase do + adhoc_certificates + clean_build_artifacts + + build_ios_app( + scheme: project_scheme, + output_directory: "./fastlane/builds/#{project_scheme}", + output_name: "#{project_scheme}.ipa", + export_options: { + method: "ad-hoc", + compileBitcode: false + }, + clean: true, + include_bitcode: true, + include_symbols: true, + skip_package_ipa: false + ) + +# firebase_app_distribution( +# app: "1:880592554695:ios:af464a9ed02207f6284ce2", +# testers: "dartsyms@gmail.com, shogun14@yandex.ru, qa@wevied.com", +# release_notes: "Another build: ready to test." +# ) +end + +def msgToSlack(msg, result) + slack( + message: msg, + success: result, + default_payloads: [:lane, :test_result, :git_branch, :git_author] # Optional, lets you specify a whitelist of default payloads to include. Pass an empty array to suppress all the default payloads. + # Don't add this key, or pass nil, if you want all the default payloads. The available default payloads are: `lane`, `test_result`, `git_branch`, `git_author`, `last_git_commit_message`, `last_git_commit_hash`. + ) +end diff --git a/client/fastlane/Matchfile b/client/fastlane/Matchfile new file mode 100644 index 00000000..2da8aed0 --- /dev/null +++ b/client/fastlane/Matchfile @@ -0,0 +1,13 @@ +git_url("https://github.com/amnezia-vpn/amnezia-ios-certificates") + +storage_mode("git") + +type("appstore") # The default type, can be: appstore, adhoc, enterprise or development + +# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"]) +# username("user@fastlane.tools") # Your Apple Developer Portal username + +# For all available options run `fastlane match --help` +# Remove the # in the beginning of the line to enable the other options + +# The docs are available on https://docs.fastlane.tools/actions/match diff --git a/client/fastlane/Pluginfile b/client/fastlane/Pluginfile new file mode 100644 index 00000000..50b5b39e --- /dev/null +++ b/client/fastlane/Pluginfile @@ -0,0 +1,6 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +#gem 'fastlane-plugin-run_tests_firebase_testlab' +#gem 'fastlane-plugin-firebase_app_distribution' diff --git a/client/fastlane/README.md b/client/fastlane/README.md new file mode 100644 index 00000000..3375b46f --- /dev/null +++ b/client/fastlane/README.md @@ -0,0 +1,88 @@ +fastlane documentation +---- + +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +```sh +xcode-select --install +``` + +For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) + +# Available Actions + +### beta + +```sh +[bundle exec] fastlane beta +``` + + + +### incrementVersion + +```sh +[bundle exec] fastlane incrementVersion +``` + + + +### addToTestFlight + +```sh +[bundle exec] fastlane addToTestFlight +``` + +Update Certificates, Run All tests, Build app + +Add tag to git, send to Testflight, slack notification + +### certificates + +```sh +[bundle exec] fastlane certificates +``` + + + +### adhoc_certificates + +```sh +[bundle exec] fastlane adhoc_certificates +``` + + + +### createAPNS + +```sh +[bundle exec] fastlane createAPNS +``` + + + +### firebase_test + +```sh +[bundle exec] fastlane firebase_test +``` + +Distribute app via Firebase for testers + +### distribute_firebase + +```sh +[bundle exec] fastlane distribute_firebase +``` + +Distribute app via Firebase for testers + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. + +More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). + +The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/client/fastlane/report.xml b/client/fastlane/report.xml new file mode 100644 index 00000000..279b432b --- /dev/null +++ b/client/fastlane/report.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/ios/app/Info.plist b/client/ios/app/Info.plist index 3c0f289e..73a71c11 100644 --- a/client/ios/app/Info.plist +++ b/client/ios/app/Info.plist @@ -25,13 +25,15 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 3 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace + NSCameraUsageDescription + Amnezia VPN needs access to the camera for reading QR-codes. UILaunchStoryboardName AmneziaVPNLaunchScreen UIRequiresFullScreen diff --git a/client/ios/app/main.entitlements b/client/ios/app/main.entitlements index 23a6914f..d9f00bb1 100644 --- a/client/ios/app/main.entitlements +++ b/client/ios/app/main.entitlements @@ -8,13 +8,13 @@ com.apple.security.application-groups - group.ru.kotit.AmneziaVPN.udev + group.org.amnezia.AmneziaVPN com.apple.security.files.user-selected.read-write keychain-access-groups - $(AppIdentifierPrefix)group.ru.kotit.AmneziaVPN.udev + $(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN diff --git a/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements b/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements index 7d350b19..fd6983a9 100644 --- a/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements +++ b/client/ios/networkextension/AmneziaVPNNetworkExtension.entitlements @@ -8,11 +8,17 @@ com.apple.security.application-groups - group.ru.kotit.AmneziaVPN.udev + group.org.amnezia.AmneziaVPN keychain-access-groups - $(AppIdentifierPrefix)group.ru.kotit.AmneziaVPN.udev + $(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN + + + + + + diff --git a/client/macos/networkextension/Info.plist b/client/macos/networkextension/Info.plist index 96d82459..b19b1255 100644 --- a/client/macos/networkextension/Info.plist +++ b/client/macos/networkextension/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - $(CURRENT_PROJECT_VERSION) + 3 ITSAppUsesNonExemptEncryption LSMinimumSystemVersion diff --git a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h index bac84dfe..8fbada72 100644 --- a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -5,6 +5,8 @@ #include "macos/gobridge/wireguard.h" #include "wireguard-go-version.h" #include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +//#include "../../platforms/ios/Shadowsocks.h" + #include #include diff --git a/client/platforms/ios/Shadowsocks.h b/client/platforms/ios/Shadowsocks.h new file mode 100644 index 00000000..8b9e3278 --- /dev/null +++ b/client/platforms/ios/Shadowsocks.h @@ -0,0 +1,65 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef Shadowsocks_h +#define Shadowsocks_h + +#import + +/** + * Manages the lifecycle and configuration of ss-local, the Shadowsocks client library. + */ +@interface Shadowsocks : NSObject + +extern const int kShadowsocksLocalPort; + +typedef NS_ENUM(NSInteger, ErrorCode) { + noError = 0, + undefinedError = 1, + vpnPermissionNotGranted = 2, + invalidServerCredentials = 3, + udpRelayNotEnabled = 4, + serverUnreachable = 5, + vpnStartFailure = 6, + illegalServerConfiguration = 7, + shadowsocksStartFailure = 8, + configureSystemProxyFailure = 9, + noAdminPermissions = 10, + unsupportedRoutingTable = 11, + systemMisconfigured = 12 +}; + +@property (nonatomic) NSDictionary *config; + +/** + * Initializes the object with a Shadowsocks server configuration, |config|. + */ +- (id)init:(NSDictionary *)config; + +/** + * Starts ss-local on a separate thread with the configuration supplied in the constructor. + * If |checkConnectivity| is true, verifies that the server credentials are valid and that + * the remote supports UDP forwarding, calling |completion| with the result. + */ +- (void)startWithConnectivityChecks:(bool)checkConnectivity + completion:(void (^)(ErrorCode))completion; + +/** + * Stops the thread running ss-local. Calls |completion| with the success of the operation. + */ +- (void)stop:(void (^)(ErrorCode))completion; + +@end + +#endif /* Shadowsocks_h */ \ No newline at end of file diff --git a/client/platforms/ios/Shadowsocks.m b/client/platforms/ios/Shadowsocks.m new file mode 100644 index 00000000..ca843659 --- /dev/null +++ b/client/platforms/ios/Shadowsocks.m @@ -0,0 +1,202 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "Shadowsocks.h" +#include +#include +#import "ShadowsocksConnectivity.h" +#import + +const int kShadowsocksLocalPort = 9999; +static const int kShadowsocksTimeoutSecs = INT_MAX; +static const int kShadowsocksTcpAndUdpMode = + 1; // See https://github.com/shadowsocks/shadowsocks-libev/blob/4ea517/src/jconf.h#L44 +static char *const kShadowsocksLocalAddress = "127.0.0.1"; + +@interface Shadowsocks () +@property (nonatomic) pthread_t ssLocalThreadId; +@property (nonatomic, copy) void (^startCompletion)(ErrorCode); +@property (nonatomic, copy) void (^stopCompletion)(ErrorCode); +@property (nonatomic) dispatch_queue_t dispatchQueue; +@property (nonatomic) dispatch_group_t dispatchGroup; +@property(nonatomic) bool checkConnectivity; +@property(nonatomic) ShadowsocksConnectivity *ssConnectivity; +@end + +@implementation Shadowsocks + +- (id) init:(NSDictionary *)config { + self = [super init]; + if (self) { + _config = config; + _dispatchQueue = dispatch_queue_create("Shadowsocks", DISPATCH_QUEUE_SERIAL); + _dispatchGroup = dispatch_group_create(); + _ssConnectivity = [[ShadowsocksConnectivity alloc] initWithPort:kShadowsocksLocalPort]; + } + return self; +} + +- (void)startWithConnectivityChecks:(bool)checkConnectivity + completion:(void (^)(ErrorCode))completion { + if (self.ssLocalThreadId != 0) { + return completion(shadowsocksStartFailure); + } + self.checkConnectivity = checkConnectivity; + dispatch_async(dispatch_get_main_queue(), ^{ + // Start ss-local from the main application thread. + self.startCompletion = completion; + [self startShadowsocksThread]; + }); +} + +-(void)stop:(void (^)(ErrorCode))completion { + if (self.ssLocalThreadId == 0) { + return; + } + dispatch_async(dispatch_get_main_queue(), ^{ + // The ev_loop in the ss-local thread will not break unless it is signaled to stop from the main + // application thread. + self.stopCompletion = completion; + pthread_kill(self.ssLocalThreadId, SIGUSR1); + self.ssLocalThreadId = 0; + }); +} + +#pragma mark - Lifecycle + +void shadowsocksCallback(int socks_fd, int udp_fd, void *udata) { + if (socks_fd <= 0 || udp_fd <= 0) { + return; + } + Shadowsocks* ss = (__bridge Shadowsocks *)udata; + [ss checkServerConnectivity]; +} + +- (void)startShadowsocks { + if (self.config == nil) { + self.startCompletion(illegalServerConfiguration); + return; + } + int port = [self.config[@"port"] intValue]; + char *host = (char *)[self.config[@"host"] UTF8String]; + char *password = (char *)[self.config[@"password"] UTF8String]; + char *method = (char *)[self.config[@"method"] UTF8String]; + const profile_t profile = {.remote_host = host, + .local_addr = kShadowsocksLocalAddress, + .method = method, + .password = password, + .remote_port = port, + .local_port = kShadowsocksLocalPort, + .timeout = kShadowsocksTimeoutSecs, + .acl = NULL, + .log = NULL, + .fast_open = 0, + .mode = kShadowsocksTcpAndUdpMode, + .verbose = 0}; + int success = start_ss_local_server_with_callback(profile, shadowsocksCallback, + (__bridge void *)self); + if (success < 0) { + self.startCompletion(shadowsocksStartFailure); + return; + } + + if (self.stopCompletion) { + self.stopCompletion(noError); + self.stopCompletion = nil; + } +} + +// Entry point for the Shadowsocks POSIX thread. +void *startShadowsocks(void *udata) { + Shadowsocks* ss = (__bridge Shadowsocks *)udata; + [ss startShadowsocks]; + return NULL; +} + +// Starts a POSIX thread that runs ss-local. +- (void)startShadowsocksThread { + pthread_attr_t attr; + int err = pthread_attr_init(&attr); + if (err) { + self.startCompletion(shadowsocksStartFailure); + return; + } + err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err) { + self.startCompletion(shadowsocksStartFailure); + return; + } + err = pthread_create(&_ssLocalThreadId, &attr, &startShadowsocks, (__bridge void *)self); + if (err) { + self.startCompletion(shadowsocksStartFailure); + } + err = pthread_attr_destroy(&attr); + if (err) { + self.startCompletion(shadowsocksStartFailure); + return; + } +} + +#pragma mark - Connectivity + +/** + * Checks that the remote server is reachable, allows UDP forwarding, and the credentials are valid. + * Synchronizes and parallelizes the execution of the connectivity checks and calls + * |startCompletion| with the combined outcome. + * Only performs the tests if |checkConnectivity| is true; otherwise calls |startCompletion| + * with success. + */ +- (void) checkServerConnectivity { + if (!self.checkConnectivity) { + self.startCompletion(noError); + return; + } + __block BOOL isRemoteUdpForwardingEnabled = false; + __block BOOL serverCredentialsAreValid = false; + __block BOOL isServerReachable = false; + + // Enter the group once for each check + dispatch_group_enter(self.dispatchGroup); + dispatch_group_enter(self.dispatchGroup); + dispatch_group_enter(self.dispatchGroup); + + [self.ssConnectivity isUdpForwardingEnabled:^(BOOL enabled) { + isRemoteUdpForwardingEnabled = enabled; + dispatch_group_leave(self.dispatchGroup); + }]; + [self.ssConnectivity isReachable:self.config[@"host"] + port:[self.config[@"port"] intValue] + completion:^(BOOL isReachable) { + isServerReachable = isReachable; + dispatch_group_leave(self.dispatchGroup); + }]; + [self.ssConnectivity checkServerCredentials:^(BOOL valid) { + serverCredentialsAreValid = valid; + dispatch_group_leave(self.dispatchGroup); + }]; + + dispatch_group_notify(self.dispatchGroup, self.dispatchQueue, ^{ + if (isRemoteUdpForwardingEnabled) { + self.startCompletion(noError); + } else if (serverCredentialsAreValid) { + self.startCompletion(udpRelayNotEnabled); + } else if (isServerReachable) { + self.startCompletion(invalidServerCredentials); + } else { + self.startCompletion(serverUnreachable); + } + }); +} + +@end diff --git a/client/platforms/ios/ShadowsocksConnectivity.h b/client/platforms/ios/ShadowsocksConnectivity.h new file mode 100644 index 00000000..a2c68bdc --- /dev/null +++ b/client/platforms/ios/ShadowsocksConnectivity.h @@ -0,0 +1,50 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ShadowsocksConnectivity_h +#define ShadowsocksConnectivity_h + +#import + +/** + * Non-thread-safe class to perform Shadowsocks connectivity checks. + */ +@interface ShadowsocksConnectivity : NSObject + +/** + * Initializes the object with a local Shadowsocks port, |shadowsocksPort|. + */ +- (id)initWithPort:(uint16_t)shadowsocksPort; + +/** + * Verifies that the server has enabled UDP forwarding. Performs an end-to-end test by sending + * a DNS request through the proxy. This method is a superset of |checkServerCredentials|, as its + * success implies that the server credentials are valid. + */ +- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion; + +/** + * Verifies that the server credentials are valid. Performs an end-to-end authentication test + * by issuing an HTTP HEAD request to a target domain through the proxy. + */ +- (void)checkServerCredentials:(void (^)(BOOL))completion; + +/** + * Checks that the server is reachable on |host| and |port|. + */ +- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion; + +@end + +#endif /* ShadowsocksConnectivity_h */ diff --git a/client/platforms/ios/ShadowsocksConnectivity.m b/client/platforms/ios/ShadowsocksConnectivity.m new file mode 100644 index 00000000..d6723ba1 --- /dev/null +++ b/client/platforms/ios/ShadowsocksConnectivity.m @@ -0,0 +1,333 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "ShadowsocksConnectivity.h" +#include +#import +#import + +//@import CocoaAsyncSocket; + +static char *const kShadowsocksLocalAddress = "127.0.0.1"; + +static char *const kDnsResolverAddress = "208.67.222.222"; // OpenDNS +static const uint16_t kDnsResolverPort = 53; +static const size_t kDnsRequestNumBytes = 28; + +static const size_t kSocksHeaderNumBytes = 10; +static const uint8_t kSocksMethodsResponseNumBytes = 2; +static const size_t kSocksConnectResponseNumBytes = 10; +static const uint8_t kSocksVersion = 0x5; +static const uint8_t kSocksMethodNoAuth = 0x0; +static const uint8_t kSocksCmdConnect = 0x1; +static const uint8_t kSocksAtypIpv4 = 0x1; +static const uint8_t kSocksAtypDomainname = 0x3; + +static const NSTimeInterval kTcpSocketTimeoutSecs = 10.0; +static const NSTimeInterval kUdpSocketTimeoutSecs = 1.0; +static const long kSocketTagHttpRequest = 100; +static const int kUdpForwardingMaxChecks = 5; +static const uint16_t kHttpPort = 80; + +@interface ShadowsocksConnectivity () + +@property(nonatomic) uint16_t shadowsocksPort; + +@property(nonatomic, copy) void (^udpForwardingCompletion)(BOOL); +@property(nonatomic, copy) void (^reachabilityCompletion)(BOOL); +@property(nonatomic, copy) void (^credentialsCompletion)(BOOL); + +@property(nonatomic) dispatch_queue_t dispatchQueue; +@property(nonatomic) GCDAsyncUdpSocket *udpSocket; +@property(nonatomic) GCDAsyncSocket *credentialsSocket; +@property(nonatomic) GCDAsyncSocket *reachabilitySocket; + +@property(nonatomic) bool isRemoteUdpForwardingEnabled; +@property(nonatomic) bool areServerCredentialsValid; +@property(nonatomic) bool isServerReachable; +@property(nonatomic) int udpForwardingNumChecks; +@end + +@implementation ShadowsocksConnectivity + +- (id)initWithPort:(uint16_t)shadowsocksPort { + self = [super init]; + if (self) { + _shadowsocksPort = shadowsocksPort; + _dispatchQueue = dispatch_queue_create("ShadowsocksConnectivity", DISPATCH_QUEUE_SERIAL); + } + return self; +} + +#pragma mark - UDP Forwarding + +struct socks_udp_header { + uint16_t rsv; + uint8_t frag; + uint8_t atyp; + uint32_t addr; + uint16_t port; +}; + +- (void)isUdpForwardingEnabled:(void (^)(BOOL))completion { + self.isRemoteUdpForwardingEnabled = false; + self.udpForwardingNumChecks = 0; + self.udpForwardingCompletion = completion; + self.udpSocket = + [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; + struct in_addr dnsResolverAddress; + if (!inet_aton(kDnsResolverAddress, &dnsResolverAddress)) { + [self udpForwardingCheckDone:false]; + return; + } + struct socks_udp_header socksHeader = { + .atyp = kSocksAtypIpv4, + .addr = dnsResolverAddress.s_addr, // Already in network order + .port = htons(kDnsResolverPort)}; + uint8_t *dnsRequest = [self getDnsRequest]; + size_t packetNumBytes = kSocksHeaderNumBytes + kDnsRequestNumBytes; + uint8_t socksPacket[packetNumBytes]; + memset(socksPacket, 0, packetNumBytes); + memcpy(socksPacket, &socksHeader, kSocksHeaderNumBytes); + memcpy(socksPacket + kSocksHeaderNumBytes, dnsRequest, kDnsRequestNumBytes); + + NSData *packetData = [[NSData alloc] initWithBytes:socksPacket length:packetNumBytes]; + + dispatch_source_t timer = + dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.dispatchQueue); + if (!timer) { + [self udpForwardingCheckDone:false]; + return; + } + dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), + kUdpSocketTimeoutSecs * NSEC_PER_SEC, 0); + __weak ShadowsocksConnectivity *weakSelf = self; + dispatch_source_set_event_handler(timer, ^{ + if (++weakSelf.udpForwardingNumChecks > kUdpForwardingMaxChecks || + weakSelf.isRemoteUdpForwardingEnabled) { + dispatch_source_cancel(timer); + if (!weakSelf.isRemoteUdpForwardingEnabled) { + [weakSelf udpForwardingCheckDone:false]; + } + [weakSelf.udpSocket close]; + return; + } + [weakSelf.udpSocket sendData:packetData + toHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress] + port:self.shadowsocksPort + withTimeout:kUdpSocketTimeoutSecs + tag:0]; + if (![weakSelf.udpSocket receiveOnce:nil]) { + } + }); + dispatch_resume(timer); +} + +// Returns a byte representation of a DNS request for "google.com". +- (uint8_t *)getDnsRequest { + static uint8_t kDnsRequest[] = { + 0, 0, // [0-1] query ID + 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD). + 0, 1, // [4-5] QDCOUNT (number of queries) + 0, 0, // [6-7] ANCOUNT (number of answers) + 0, 0, // [8-9] NSCOUNT (number of name server records) + 0, 0, // [10-11] ARCOUNT (number of additional records) + 6, 'g', 'o', 'o', 'g', 'l', 'e', 3, 'c', 'o', 'm', + 0, // null terminator of FQDN (root TLD) + 0, 1, // QTYPE, set to A + 0, 1 // QCLASS, set to 1 = IN (Internet) + }; + return kDnsRequest; +} + +#pragma mark - GCDAsyncUdpSocketDelegate + +- (void)udpSocket:(GCDAsyncUdpSocket *)sock + didNotSendDataWithTag:(long)tag + dueToError:(NSError *)error { +} + +- (void)udpSocket:(GCDAsyncUdpSocket *)sock + didReceiveData:(NSData *)data + fromAddress:(NSData *)address + withFilterContext:(id)filterContext { + if (!self.isRemoteUdpForwardingEnabled) { + // Only report success if it hasn't been done so already. + [self udpForwardingCheckDone:true]; + } +} + +- (void)udpForwardingCheckDone:(BOOL)enabled { + self.isRemoteUdpForwardingEnabled = enabled; + if (self.udpForwardingCompletion != NULL) { + self.udpForwardingCompletion(self.isRemoteUdpForwardingEnabled); + self.udpForwardingCompletion = NULL; + } +} + +#pragma mark - Credentials + +struct socks_methods_request { + uint8_t ver; + uint8_t nmethods; + uint8_t method; +}; + +struct socks_request_header { + uint8_t ver; + uint8_t cmd; + uint8_t rsv; + uint8_t atyp; +}; + +- (void)checkServerCredentials:(void (^)(BOOL))completion { + self.areServerCredentialsValid = false; + self.credentialsCompletion = completion; + self.credentialsSocket = + [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; + NSError *error; + [self.credentialsSocket + connectToHost:[[NSString alloc] initWithUTF8String:kShadowsocksLocalAddress] + onPort:self.shadowsocksPort + withTimeout:kTcpSocketTimeoutSecs + error:&error]; + if (error) { + [self serverCredentialsCheckDone]; + return; + } + + struct socks_methods_request methodsRequest = { + .ver = kSocksVersion, .nmethods = 0x1, .method = kSocksMethodNoAuth}; + NSData *methodsRequestData = + [[NSData alloc] initWithBytes:&methodsRequest length:sizeof(struct socks_methods_request)]; + [self.credentialsSocket writeData:methodsRequestData withTimeout:kTcpSocketTimeoutSecs tag:0]; + [self.credentialsSocket readDataToLength:kSocksMethodsResponseNumBytes + withTimeout:kTcpSocketTimeoutSecs + tag:0]; + + size_t socksRequestHeaderNumBytes = sizeof(struct socks_request_header); + NSString *domain = [self chooseRandomDomain]; + uint8_t domainNameNumBytes = domain.length; + size_t socksRequestNumBytes = socksRequestHeaderNumBytes + domainNameNumBytes + + sizeof(uint16_t) /* port */ + + sizeof(uint8_t) /* domain name length */; + + struct socks_request_header socksRequestHeader = { + .ver = kSocksVersion, .cmd = kSocksCmdConnect, .atyp = kSocksAtypDomainname}; + uint8_t socksRequest[socksRequestNumBytes]; + memset(socksRequest, 0x0, socksRequestNumBytes); + memcpy(socksRequest, &socksRequestHeader, socksRequestHeaderNumBytes); + socksRequest[socksRequestHeaderNumBytes] = domainNameNumBytes; + memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t), [domain UTF8String], + domainNameNumBytes); + uint16_t httpPort = htons(kHttpPort); + memcpy(socksRequest + socksRequestHeaderNumBytes + sizeof(uint8_t) + domainNameNumBytes, + &httpPort, sizeof(uint16_t)); + + NSData *socksRequestData = + [[NSData alloc] initWithBytes:socksRequest length:socksRequestNumBytes]; + [self.credentialsSocket writeData:socksRequestData withTimeout:kTcpSocketTimeoutSecs tag:0]; + [self.credentialsSocket readDataToLength:kSocksConnectResponseNumBytes + withTimeout:kTcpSocketTimeoutSecs + tag:0]; + + NSString *httpRequest = + [[NSString alloc] initWithFormat:@"HEAD / HTTP/1.1\r\nHost: %@\r\n\r\n", domain]; + [self.credentialsSocket + writeData:[NSData dataWithBytes:[httpRequest UTF8String] length:httpRequest.length] + withTimeout:kTcpSocketTimeoutSecs + tag:kSocketTagHttpRequest]; + [self.credentialsSocket readDataWithTimeout:kTcpSocketTimeoutSecs tag:kSocketTagHttpRequest]; + [self.credentialsSocket disconnectAfterReading]; +} + +// Returns a statically defined array containing domain names for validating server credentials. ++ (const NSArray *)getCredentialsValidationDomains { + static const NSArray *kCredentialsValidationDomains; + static dispatch_once_t kDispatchOnceToken; + dispatch_once(&kDispatchOnceToken, ^{ + // We have chosen these domains due to their neutrality. + kCredentialsValidationDomains = + @[ @"eff.org", @"ietf.org", @"w3.org", @"wikipedia.org", @"example.com" ]; + }); + return kCredentialsValidationDomains; +} + +// Returns a random domain from |kCredentialsValidationDomains|. +- (NSString *)chooseRandomDomain { + const NSArray *domains = [ShadowsocksConnectivity getCredentialsValidationDomains]; + int index = arc4random_uniform((uint32_t)domains.count); + return domains[index]; +} + +// Calls |credentialsCompletion| once with |areServerCredentialsValid|. +- (void)serverCredentialsCheckDone { + if (self.credentialsCompletion != NULL) { + self.credentialsCompletion(self.areServerCredentialsValid); + self.credentialsCompletion = NULL; + } +} + +#pragma mark - Reachability + +- (void)isReachable:(NSString *)host port:(uint16_t)port completion:(void (^)(BOOL))completion { + self.isServerReachable = false; + self.reachabilityCompletion = completion; + self.reachabilitySocket = + [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:self.dispatchQueue]; + NSError *error; + [self.reachabilitySocket connectToHost:host + onPort:port + withTimeout:kTcpSocketTimeoutSecs + error:&error]; + if (error) { + return; + } +} + +// Calls |reachabilityCompletion| once with |isServerReachable|. +- (void)reachabilityCheckDone { + if (self.reachabilityCompletion != NULL) { + self.reachabilityCompletion(self.isServerReachable); + self.reachabilityCompletion = NULL; + } +} + +#pragma mark - GCDAsyncSocketDelegate + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { + // We don't need to inspect any of the data, as the SOCKS responses are hardcoded in ss-local and + // the fact that we have read the HTTP response indicates that the server credentials are valid. + if (tag == kSocketTagHttpRequest && data != nil) { + NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + self.areServerCredentialsValid = httpResponse != nil && [httpResponse hasPrefix:@"HTTP/1.1"]; + } +} + +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { + if ([self.reachabilitySocket isEqual:sock]) { + self.isServerReachable = true; + [self.reachabilitySocket disconnect]; + } +} + +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error { + if ([self.reachabilitySocket isEqual:sock]) { + [self reachabilityCheckDone]; + } else { + [self serverCredentialsCheckDone]; + } +} + +@end diff --git a/client/platforms/ios/Subnet.swift b/client/platforms/ios/Subnet.swift new file mode 100644 index 00000000..cfd83bda --- /dev/null +++ b/client/platforms/ios/Subnet.swift @@ -0,0 +1,86 @@ +// Copyright 2018 The Outline Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +// Represents an IP subnetwork. +@objcMembers +class Subnet: NSObject { + static let kReservedSubnets = [ + "10.0.0.0/8", + "100.64.0.0/10", + "169.254.0.0/16", + "172.16.0.0/12", + "192.0.0.0/24", + "192.0.2.0/24", + "192.31.196.0/24", + "192.52.193.0/24", + "192.88.99.0/24", + "192.168.0.0/16", + "192.175.48.0/24", + "198.18.0.0/15", + "198.51.100.0/24", + "203.0.113.0/24", + "240.0.0.0/4" + ] + + // Parses a CIDR subnet into a Subnet object. Returns nil on failure. + static func parse(_ cidrSubnet: String) -> Subnet? { + let components = cidrSubnet.components(separatedBy: "/") + guard components.count == 2 else { + NSLog("Malformed CIDR subnet") + return nil + } + guard let prefix = UInt16(components[1]) else { + NSLog("Invalid subnet prefix") + return nil + } + return Subnet(address: components[0], prefix: prefix) + } + + // Returns a list of reserved Subnets. + static func getReservedSubnets() -> [Subnet] { + var subnets: [Subnet] = [] + for cidrSubnet in kReservedSubnets { + if let subnet = self.parse(cidrSubnet) { + subnets.append(subnet) + } + } + return subnets + } + + public var address: String + public var prefix: UInt16 + public var mask: String + + init(address: String, prefix: UInt16) { + self.address = address + self.prefix = prefix + let mask = (0xffffffff as UInt32) << (32 - prefix); + self.mask = mask.IPv4String() + } +} + +extension UInt32 { + // Returns string representation of the integer as an IP address. + public func IPv4String() -> String { + let ip = self + let a = UInt8((ip>>24) & 0xff) + let b = UInt8((ip>>16) & 0xff) + let c = UInt8((ip>>8) & 0xff) + let d = UInt8(ip & 0xff) + return "\(a).\(b).\(c).\(d)" + } +} + diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 1bcc148b..080a8946 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -5,8 +5,8 @@ import Foundation import NetworkExtension import os import OpenVPNAdapter -import ShadowSocks -//import Tun2Socks +//import ShadowSocks +//import Tun2socks enum TunnelProtoType: String { case wireguard, openvpn, shadowsocks, none @@ -28,11 +28,12 @@ class PacketTunnelProvider: NEPacketTunnelProvider { return adapter }() - private var shadowSocksPort: Int32 = 0 - private var isShadowsocksRunning: Bool = false - var ssCompletion: ShadowsocksProxyCompletion = nil - private let ssQueue = DispatchQueue(label: "org.amnezia.shadowsocks") private var shadowSocksConfig: Data? = nil + var ssCompletion: ShadowsocksProxyCompletion = nil + +// private var shadowSocksPort: Int32 = 0 +// private var isShadowsocksRunning: Bool = false +// private let ssQueue = DispatchQueue(label: "org.amnezia.shadowsocks") // private var tun2socksWriter: AmneziaTun2SocksWriter? = nil // private var tun2socksTunnel: Tun2socksOutlineTunnelProtocol? = nil @@ -67,9 +68,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { case .openvpn: startOpenVPN(completionHandler: completionHandler) case .shadowsocks: - startShadowSocks { error in - - } + startShadowSocks(completionHandler: completionHandler) case .none: break } @@ -207,8 +206,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wg_log(.error, message: "Cannot start startShadowSocks()") return } -// self.shadowSocksConfig = ssConfiguration -// + self.shadowSocksConfig = ssConfiguration + // guard let config = self.shadowSocksConfig else { return } // guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else { // self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", @@ -216,36 +215,50 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) // return // } - -// let sshost = ssConfig["local_addr"] as? String -// let ssport = ssConfig["local_port"] as? Int ?? Int(self.shadowSocksPort) // +// let sshost = ssConfig["local_addr"] as? String +// let ssport = ssConfig["local_port"] as? Int ?? 8585 // let method = ssConfig["method"] as? String // let password = ssConfig["password"] as? String +// +// var errorCode: Int = 0 +// ShadowsocksCheckConnectivity(sshost, ssport, password, method, &errorCode, nil) +// if (errorCode != 0) { +// self.ssCompletion?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", +// code: 100, +// userInfo: [NSLocalizedDescriptionKey: "Error checking ss connectivity: \(errorCode)"])) +// return +// } - -// Thread.detachNewThread { [weak self] in - setupAndLaunchShadowSocksProxy(withConfig: ssConfiguration, ssHandler: { [weak self] port, error in - wg_log(.info, - message: "Prepare to start openvpn, self is \(self == nil ? "null" : "not null")") + self.setupAndlaunchOpenVPN(withConfig: ovpnConfiguration) { error in guard error == nil else { - wg_log(.error, message: "Stopping tunnel: \(error?.localizedDescription ?? "none")") + wg_log(.error, message: "Start OpenVPN tunnel error : \(error?.localizedDescription ?? "none")") completionHandler(error!) return } - - self?.setupAndlaunchOpenVPN(withConfig: ovpnConfiguration) { error in - guard error == nil else { - wg_log(.error, message: "Start OpenVPN tunnel error : \(error?.localizedDescription ?? "none")") - completionHandler(error!) - return - } - wg_log(.error, message: "OpenVPN tunnel connected.") - } - -// self?.startTun2Socks(host: sshost, port: ssport, password: password, cipher: method, isUDPEnabled: false, error: nil) - }) -// } + wg_log(.error, message: "OpenVPN tunnel connected.") +// self.startTun2Socks(host: sshost, port: ssport, password: password, cipher: method, isUDPEnabled: false, error: nil) + } +//// Thread.detachNewThread { [weak self] in +//// setupAndLaunchShadowSocksProxy(withConfig: ssConfiguration, ssHandler: { [weak self] port, error in +//// wg_log(.info, +//// message: "Prepare to start openvpn, self is \(self == nil ? "null" : "not null")") +//// guard error == nil else { +//// wg_log(.error, message: "Stopping tunnel: \(error?.localizedDescription ?? "none")") +//// completionHandler(error!) +//// return +//// } +//// +//// self?.setupAndlaunchOpenVPN(withConfig: ovpnConfiguration) { error in +//// guard error == nil else { +//// wg_log(.error, message: "Start OpenVPN tunnel error : \(error?.localizedDescription ?? "none")") +//// completionHandler(error!) +//// return +//// } +//// wg_log(.error, message: "OpenVPN tunnel connected.") +//// } +//// }) +//// } } private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { @@ -294,7 +307,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { // self.processQueue.sync { self.processPackets() } // } // } -// + // private func processPackets() { // wg_log(.info, message: "Inside startTun2SocksPacketForwarder") // packetFlow.readPacketObjects { [weak self] packets in @@ -341,100 +354,100 @@ class PacketTunnelProvider: NEPacketTunnelProvider { ovpnAdapter.connect(using: packetFlow) } - private func setupAndLaunchShadowSocksProxy(withConfig config: Data, ssHandler: ShadowsocksProxyCompletion) { - let str = String(decoding: config, as: UTF8.self) - wg_log(.info, message: "config: \(str)") - ssCompletion = ssHandler - guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else { - ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", - code: 100, - userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) - return - } - - wg_log(.info, message: "SS Config: \(ssConfig)") - - guard let remoteHost = ssConfig["server"] as? String, // UnsafeMutablePointer, - let remotePort = ssConfig["server_port"] as? Int32, - let localAddress = ssConfig["local_addr"] as? String, //UnsafeMutablePointer, - let localPort = ssConfig["local_port"] as? Int32, - let method = ssConfig["method"] as? String, //UnsafeMutablePointer, - let password = ssConfig["password"] as? String,//UnsafeMutablePointer, - let timeout = ssConfig["timeout"] as? Int32 - else { - ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", - code: 100, - userInfo: [NSLocalizedDescriptionKey: "Cannot assing profile params for ss in tunnel"])) - return - } - - /* An example profile - * - * const profile_t EXAMPLE_PROFILE = { - * .remote_host = "example.com", - * .local_addr = "127.0.0.1", - * .method = "bf-cfb", - * .password = "barfoo!", - * .remote_port = 8338, - * .local_port = 1080, - * .timeout = 600; - * .acl = NULL, - * .log = NULL, - * .fast_open = 0, - * .mode = 0, - * .verbose = 0 - * }; - */ - - var profile: profile_t = .init() - memset(&profile, 0, MemoryLayout.size) - profile.remote_host = strdup(remoteHost) - profile.remote_port = remotePort - profile.local_addr = strdup(localAddress) - profile.local_port = localPort - profile.method = strdup(method) - profile.password = strdup(password) - profile.timeout = timeout - profile.acl = nil - profile.log = nil - profile.mtu = 1600 - profile.fast_open = 1 - profile.mode = 0 - profile.verbose = 1 - - wg_log(.debug, message: "Prepare to start shadowsocks proxy server...") - let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) - ssQueue.sync { [weak self] in - let success = start_ss_local_server_with_callback(profile, { socks_fd, udp_fd, data in - wg_log(.debug, message: "Inside cb callback") - wg_log(.debug, message: "Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)") - if let obs = data { - wg_log(.debug, message: "Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)") - let mySelf = Unmanaged.fromOpaque(obs).takeUnretainedValue() - mySelf.onShadowsocksCallback(fd: socks_fd) - } - }, observer) - if success != -1 { - wg_log(.error, message: "ss proxy started on port \(localPort)") - self?.shadowSocksPort = localPort - self?.isShadowsocksRunning = true - } else { - wg_log(.error, message: "Failed to start ss proxy") - } - } - } - - private func onShadowsocksCallback(fd: Int32) { - wg_log(.debug, message: "Inside onShadowsocksCallback() with port \(fd)") - var error: NSError? = nil - if fd > 0 { -// shadowSocksPort = getSockPort(for: fd) - isShadowsocksRunning = true - } else { - error = NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", code: 100, userInfo: [NSLocalizedDescriptionKey : "Failed to start shadowsocks proxy"]) - } - ssCompletion?(shadowSocksPort, error) - } +// private func setupAndLaunchShadowSocksProxy(withConfig config: Data, ssHandler: ShadowsocksProxyCompletion) { +// let str = String(decoding: config, as: UTF8.self) +// wg_log(.info, message: "config: \(str)") +// ssCompletion = ssHandler +// guard let ssConfig = try? JSONSerialization.jsonObject(with: config, options: []) as? [String: Any] else { +// ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", +// code: 100, +// userInfo: [NSLocalizedDescriptionKey: "Cannot parse json for ss in tunnel"])) +// return +// } +// +// wg_log(.info, message: "SS Config: \(ssConfig)") +// +// guard let remoteHost = ssConfig["server"] as? String, // UnsafeMutablePointer, +// let remotePort = ssConfig["server_port"] as? Int32, +// let localAddress = ssConfig["local_addr"] as? String, //UnsafeMutablePointer, +// let localPort = ssConfig["local_port"] as? Int32, +// let method = ssConfig["method"] as? String, //UnsafeMutablePointer, +// let password = ssConfig["password"] as? String,//UnsafeMutablePointer, +// let timeout = ssConfig["timeout"] as? Int32 +// else { +// ssHandler?(0, NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", +// code: 100, +// userInfo: [NSLocalizedDescriptionKey: "Cannot assing profile params for ss in tunnel"])) +// return +// } +// +// /* An example profile +// * +// * const profile_t EXAMPLE_PROFILE = { +// * .remote_host = "example.com", +// * .local_addr = "127.0.0.1", +// * .method = "bf-cfb", +// * .password = "barfoo!", +// * .remote_port = 8338, +// * .local_port = 1080, +// * .timeout = 600; +// * .acl = NULL, +// * .log = NULL, +// * .fast_open = 0, +// * .mode = 0, +// * .verbose = 0 +// * }; +// */ +// +// var profile: profile_t = .init() +// memset(&profile, 0, MemoryLayout.size) +// profile.remote_host = strdup(remoteHost) +// profile.remote_port = remotePort +// profile.local_addr = strdup(localAddress) +// profile.local_port = localPort +// profile.method = strdup(method) +// profile.password = strdup(password) +// profile.timeout = timeout +// profile.acl = nil +// profile.log = nil +// profile.mtu = 1600 +// profile.fast_open = 1 +// profile.mode = 0 +// profile.verbose = 1 +// +// wg_log(.debug, message: "Prepare to start shadowsocks proxy server...") +// let observer = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()) +// ssQueue.sync { [weak self] in +// let success = start_ss_local_server_with_callback(profile, { socks_fd, udp_fd, data in +// wg_log(.debug, message: "Inside cb callback") +// wg_log(.debug, message: "Params: socks_fd -> \(socks_fd), udp_fd -> \(udp_fd)") +// if let obs = data { +// wg_log(.debug, message: "Prepare to call onShadowsocksCallback() with socks port \(socks_fd) and udp port \(udp_fd)") +// let mySelf = Unmanaged.fromOpaque(obs).takeUnretainedValue() +// mySelf.onShadowsocksCallback(fd: socks_fd) +// } +// }, observer) +// if success != -1 { +// wg_log(.error, message: "ss proxy started on port \(localPort)") +// self?.shadowSocksPort = localPort +// self?.isShadowsocksRunning = true +// } else { +// wg_log(.error, message: "Failed to start ss proxy") +// } +// } +// } +// +// private func onShadowsocksCallback(fd: Int32) { +// wg_log(.debug, message: "Inside onShadowsocksCallback() with port \(fd)") +// var error: NSError? = nil +// if fd > 0 { +//// shadowSocksPort = getSockPort(for: fd) +// isShadowsocksRunning = true +// } else { +// error = NSError(domain: Bundle.main.bundleIdentifier ?? "unknown", code: 100, userInfo: [NSLocalizedDescriptionKey : "Failed to start shadowsocks proxy"]) +// } +// ssCompletion?(shadowSocksPort, error) +// } private func getSockPort(for fd: Int32) -> Int32 { var addr_in = sockaddr_in(); diff --git a/client/scripts/commons.sh b/client/scripts/commons.sh index 0d3e0ae0..e459b372 100644 --- a/client/scripts/commons.sh +++ b/client/scripts/commons.sh @@ -112,4 +112,3 @@ die() { exit 1 } - diff --git a/client/scripts/pp_ios.xcconfig b/client/scripts/pp_ios.xcconfig index f538b790..891f84fe 100644 --- a/client/scripts/pp_ios.xcconfig +++ b/client/scripts/pp_ios.xcconfig @@ -17,6 +17,6 @@ SDKROOT[arch=armv7s] = iphoneos VALID_ARCHS[sdk=iphoneos*] = arm64 -PROJECT_TEMP_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/PacketProcessor/build/PacketProcessor.build -CONFIGURATION_BUILD_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/PacketProcessor/build/Release-iphoneos -BUILT_PRODUCTS_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/PacketProcessor/build/Release-iphoneos \ No newline at end of file +PROJECT_TEMP_DIR = $(SRCROOT)/client/3rd/PacketProcessor/build/PacketProcessor.build +CONFIGURATION_BUILD_DIR = $(SRCROOT)/client/3rd/PacketProcessor/build/Release-iphoneos +BUILT_PRODUCTS_DIR = $(SRCROOT)/client/3rd/PacketProcessor/build/Release-iphoneos \ No newline at end of file diff --git a/client/scripts/ss_ios.xcconfig b/client/scripts/ss_ios.xcconfig index 7c355554..19e42e00 100644 --- a/client/scripts/ss_ios.xcconfig +++ b/client/scripts/ss_ios.xcconfig @@ -19,6 +19,6 @@ SDKROOT[arch=armv7s] = iphoneos VALID_ARCHS[sdk=iphoneos*] = arm64 -PROJECT_TEMP_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/ShadowSocks/build/ShadowSocks.build -CONFIGURATION_BUILD_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/ShadowSocks/build/Release-iphoneos -BUILT_PRODUCTS_DIR = /Users/sanchez/work/vied/ios/vpn/desktop-client-bkp/client/3rd/ShadowSocks/build/Release-iphoneos \ No newline at end of file +PROJECT_TEMP_DIR = $(SRCROOT)/client/3rd/ShadowSocks/build/ShadowSocks.build +CONFIGURATION_BUILD_DIR = $(SRCROOT)/client/3rd/ShadowSocks/build/Release-iphoneos +BUILT_PRODUCTS_DIR = $(SRCROOT)/client/3rd/ShadowSocks/build/Release-iphoneos \ No newline at end of file diff --git a/client/scripts/xcode_patcher.rb b/client/scripts/xcode_patcher.rb index 48512782..56f85638 100644 --- a/client/scripts/xcode_patcher.rb +++ b/client/scripts/xcode_patcher.rb @@ -110,6 +110,9 @@ class XCodeprojPatcher config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' end end + +# images_ref = @target_main.new_reference('ios/app/Images.xcassets') +# @target_main.add_resources([images_ref]) if networkExtension # WireGuard group @@ -252,10 +255,14 @@ class XCodeprojPatcher config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= 'macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h' config.build_settings['SWIFT_PRECOMPILE_BRIDGING_HEADER'] = 'NO' config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'YES' +# config.build_settings['STRIP_BITCODE_FROM_COPIED_FILES'] = 'NO' config.build_settings['FRAMEWORK_SEARCH_PATHS'] ||= [ "$(inherited)", "$(PROJECT_DIR)/3rd", - "$(PROJECT_DIR)/3rd/OpenVPNAdapter/build/Debug-iphoneos" + "$(PROJECT_DIR)/3rd/OpenVPNAdapter/build/Release-iphoneos", +# "$(PROJECT_DIR)/3rd/ShadowSocks/build/Release-iphoneos/", +# "$(PROJECT_DIR)/3rd/PacketProcessor/build/Release-iphoneos/", +# "$(PROJECT_DIR)/3rd/outline-go-tun2socks/build/ios/" ] # Versions and names @@ -341,6 +348,11 @@ class XCodeprojPatcher 'platforms/ios/iostunnel.swift', 'platforms/ios/iosglue.mm', 'platforms/ios/ioslogger.swift', +# 'platforms/ios/Shadowsocks.h', +# 'platforms/ios/Shadowsocks.m', +# 'platforms/ios/ShadowsocksConnectivity.h', +# 'platforms/ios/ShadowsocksConnectivity.m', +# 'platforms/ios/Subnet.swift', ].each { |filename| file = group.new_file(filename) @target_extension.add_file_references([file]) @@ -369,14 +381,14 @@ class XCodeprojPatcher framework_ref = frameworks_group.new_file('3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework') frameworks_build_phase.add_file_reference(framework_ref) - framework_ref = frameworks_group.new_file('3rd/ShadowSocks/build/Release-iphoneos/ShadowSocks.framework') - frameworks_build_phase.add_file_reference(framework_ref) - -# framework_ref = frameworks_group.new_file('3rd/PacketProcessor/build/Release-iphoneos/PacketProcessor.framework') +# framework_ref = frameworks_group.new_file('3rd/ShadowSocks/build/Release-iphoneos/ShadowSocks.framework') +# frameworks_build_phase.add_file_reference(framework_ref) +# +# framework_ref = frameworks_group.new_file('3rd/PacketProcessor/PacketProcessor/CocoaAsyncSocket/CocoaAsyncSocket.framework') +# frameworks_build_phase.add_file_reference(framework_ref) +# +# framework_ref = frameworks_group.new_file('3rd/outline-go-tun2socks/build/ios/Tun2socks.framework') # frameworks_build_phase.add_file_reference(framework_ref) - - framework_ref = frameworks_group.new_file('3rd/outline-go-tun2socks/build/ios/Tun2Socks.xcframework') - frameworks_build_phase.add_file_reference(framework_ref) # This fails: @target_main.add_dependency @target_extension diff --git a/client/xcode.xconfig b/client/xcode.xconfig index b93841e8..62778188 100644 --- a/client/xcode.xconfig +++ b/client/xcode.xconfig @@ -1,5 +1,5 @@ -DEVELOPMENT_TEAM = 7VAGZXD78P +DEVELOPMENT_TEAM = X7UJ388FXK -GROUP_ID_IOS = group.ru.kotit.AmneziaVPN.udev -APP_ID_IOS = ru.kotit.AmneziaVPN -NETEXT_ID_IOS = ru.kotit.AmneziaVPN.network-extension +GROUP_ID_IOS = group.org.amnezia.AmneziaVPN +APP_ID_IOS = org.amnezia.AmneziaVPN +NETEXT_ID_IOS = org.amnezia.AmneziaVPN.network-extension