Compare commits

...

177 Commits

Author SHA1 Message Date
386fe4eb12 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-17 00:43:38 +01:00
49b7d083f1 UI: add missing translations to incomplete locales
This is the wrong way to fix the problem. The correct way will involve
moving away from the whacky tr() macro and using translations functions
properly. But migrating to that will require some heavy scripting work.
So for now, use a hammer.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-17 00:40:42 +01:00
db4e2915f3 Kit: Adapter: do not treat NE settings timeouts as fatal
The general Network Extension framework is incredibly buggy, and a
timeout when setting the network settings does not necessarily imply
that the whole operation failed. Simply log the condition and move on.
This restores the app's old behavior.

Reported-by: Filipe Mendonça <cfilipem@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-17 00:26:00 +01:00
20bdf46792 Kit: PacketTunnelSettingsGenerator: do not require DNS queries if no DNS
Prior, we would set matchDomains=[""] even if the user didn't provide
any DNS servers. This was kind of incoherent, but I guess we had in mind
some kind of non-sensical leakproof scheme that never really worked
anyway. NetworkExtension didn't like this, so setTunnelNetworkSettings
would, rather than return an error, simply timeout and never call its
callback function. But everything worked fine, so we had code in the UI
to check to make sure everything was okay after 5 seconds or so of no
callback. Recent changes made the timeout fatal on the network extension
side, so rather than succeed, configs with no DNS server started
erroring out, causing user reports.

This commit attempts to handle the root cause of the timeout issue by
not twiddling with DNS settings if no DNS server was specified. For now,
however, it leaves the hard-timeout semantics in place.

Reported-by: Filipe Mendonça <cfilipem@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-17 00:10:28 +01:00
4ded3f6bfe UI: macOS: remove donation link
Apple forbids us from having a simple donation link in the "About
WireGuard" dialog, due to new policies. And arguing with the giant is
not going to be a fruitful battle. Do the practical thing and just
remove it.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-16 00:04:32 +01:00
b51113f680 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-15 16:18:07 +01:00
be96dea04a WireGuardApp: Refactor TunnelListCell
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
a786f5df60 WireGuardApp: Replace AnyObject with a concrete NSKeyValueObservation
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
9a483a46fa WireGuardApp: Animate switch control in TunnelListCell
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
5d2a337332 WireGuardApp: Remove 200ms delay when updating tunnel status switch
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
facf776602 WireGuardApp: Pin status switch to cell margin
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
d3400e3a80 WireGuardApp: Refactor indicator view initialization
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
92517bd21e WireGuardApp: Use Bundle.forInfoDictionaryKey to access Info.plist fields
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
761f635e16 WireGuardApp: Refactor indicator initialization
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
44704ba892 WireGuardApp: Fix window background color to default black
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-15 15:56:22 +01:00
9231c03513 global: support DNS search domains
This has been supported by Windows and Linux for quite some time. Add
support here for iOS and macOS.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-15 15:54:12 +01:00
27b32e60b2 WireGuardKitGo: update to latest wireguard-go tag
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-15 13:16:52 +01:00
9d5b376dcf Revert "[REVERT ME SOON] TunnelsManager: Workaround for macOS Catalina deleting tunnels arbitrarily"
This reverts commit 028e76eb3f.

It's been over a year. I really hope this is fixed by Apple.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-11 12:51:16 +01:00
8fd4883d7e WireGuardApp: modify xcodeproj when syncing translations
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-11 12:39:43 +01:00
d414cec9aa WireGuardKit: Let wireguard-go backend run in offline on macOS
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-11 11:56:05 +01:00
54e3333b72 WireGuardApp: add CrowdIn syncer and run it
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-11 11:34:19 +01:00
9f8d0e24df WireGuardKit: Conditionally turn on/off wireguard-go
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-11 11:15:22 +01:00
3de7c99301 WireGuardGoKit: drop support for armv7
Apple and Go have both dropped it, so we do the same.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-09 15:56:35 +01:00
d4fd17cd8f global: fix remaining swiftlint violations
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-09 14:35:21 +01:00
d875266db5 WireGuardKitGo: get rid of missing -Wno-unused-command-line-argument flag
Recent toolchains error out on it, and it's no longer needed.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-09 14:25:22 +01:00
d696e31b6e WireGuardKitGo: rebase boottime patch onto Go 1.15.6
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-09 14:08:45 +01:00
90acf2b220 global: bump year in header
A bit overdue, but better late than never.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-12-04 12:15:29 +01:00
27ef0c6dba WireGuardApp: Update target membership to exclude sources that are only used in network extensions
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 11:50:44 +01:00
8f67435d4a WireGuardKit: Delegate IPv*Address initialization to self.init
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 11:50:44 +01:00
b4ebe2440f WireGuardApp: Remove backend version call in Logger.swift & extract wireguard-go version script from network extension targets
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 11:50:44 +01:00
d440a91b0e WireGuardKit: Log XLAT resolution errors
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 11:50:43 +01:00
9e909a3294 WireGuardApp: Disable SWIFT_PRECOMPILE_BRIDGING_HEADER
Clang automatically picks up module.modulemap files from WireGuardKit directories when precompiling bridging header file, which causes the compiler to fail with obscure error.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
75bcf97ab2 WireGuardApp: Update swift version from 4.2 to 5.0
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
0edde8b46f Update checkout path in README
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
bcc34e0bb6 Keychain: Avoid roundtrip via items when accessing item label (stored in kSecAttrLabel)
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
90b41aed89 Keychain: Remove unnecessary cast to String in Keychain queries
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
7930b94981 WireGuardApp: Remove WireGuardKit.swift from Xcode source tree
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
54a89f6a0e WireGuadKit: Rename WireGuardAdapter.version -> .backendVersion & remove var wireGuardVersion with WireGuardKit.swift
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:09 +01:00
8976a53b05 WireGuardApp: Add back the wireguard-go version extraction script and use WIREGUARD_GO_VERSION directly
Avoids linking against libwg-go.a in order to access the WireGuard backend version.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:39:05 +01:00
9849dedf1d WireGuardApp: Include headers from WireGuardKitC
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-04 09:38:31 +01:00
547077a808 WireGuardApp: integrate WireGuardKit sources directly
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
0b0898dc3c Remove Sources/ in project folder names
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
9f9d1ffed8 WireGuardKit: Rename WireGuardKitSwift -> WireGuardKit
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
5a044e4129 Linter: Fix all linter issues across the codebase
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
35f0ada8a9 WireGuardApp: Fix build working dir for go-bridge targets
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
39e7ee07ac WireGuardNetworkExtension: Remove wireguard.h from bridging header
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
5a1c9598f1 Fix paths pointing to xcconfigs
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
101a45b6f1 WireGuardKit: Add wireguard-go files to exclude list to eliminate SwiftPM warnings
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:25 +01:00
fd527f73e6 WireGuardKit: Set publicHeadersPath = "." to flatten public headers structure
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:24 +01:00
de6aa3eb58 WireGuardKit: Fix module map for WireGuardKitC
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:24 +01:00
c9eafd82ac WireGuardKit: Fix import statements
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:24 +01:00
ec57408570 Move all source files to Sources/ and rename WireGuardKit targets
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:32:24 +01:00
9c38a1b897 WireGuardKit: Assert that resolutionResults must not contain failures
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
b34625f511 WireGuardKit: Only assign self.settingsGenerator upon success to set tunnel network settings to avoid inconsistent state
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
2e356d3d8f WireGuardKit: Remove handleLogLine from WireGuardAdapter
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
697d449dc8 WireGuardKit: Remove isStarted: bool from WireGuardAdapter
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
4f9f61f7a7 WireGuardKit: Fix docs for WireGuardAdapterError
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
bceb0a827d WireGuardKit: Fix docs for WireGuardLogLevel
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
2329f712cf WireGuardKit: Pass logHandler via constructor
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
d2c38702c8 Packet tunnel: Remove last error in the completion handler given to adapter.stop
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
def921801f WireGuardKit: Rename cannotLocateSocketDescriptor -> cannotLocateTunnelFileDescriptor in WireGuardAdapterError
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
41e006a407 WireGuardApp: Switch WireGuardKit to master branch
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
384b514290 WireGuardKit: Add TODO to log the error coming from withReresolvedIP
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
a6858bd126 WireGuardKit: Change getWireGuardVersion() -> wireGuardVersion
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
ef7de2500f Update README
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-03 13:22:52 +01:00
6099975b71 Packet tunnel: Implement packet tunnel provider using WireGuardAdapter
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:09 +01:00
828756e8ba WireGuardKit: Add WireGuardAdapter
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:09 +01:00
4deaf905c1 WireGuardKit: Add wrappers for PrivateKey, PublicKey, PreSharedKey
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:09 +01:00
76c8487a56 iOS/macOS: Remove "Extract wireguard-go version" build phase
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:09 +01:00
a05f1233f9 iOS/macOS: Remove main bundle apps dependence on WireGuardgoBridge.
Main bundle apps do not have to depend on WireGuardGoBridge<PLATFORM> as they depend on network extnesions which in turn depend on WireGuardGoBridge<PLATFORM>.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
95b833c754 iOS/macOS: Integrate WireGuardKit
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
8c057bf928 go-bridge: Add context support for wgSetLogger
Cherry picked cda99bf45c3cb95ca56204549689a0ae91ff4813 from jd/loggerCtx with the fix for wgSetLogger signature in the C header file.

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
ddf8ade9c6 WireGuardKit: Add WireGuardKitCTarget with private C sources
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
4cb21b5eb0 WireGuardKit: Set public access level for shared structs
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
a03df7d8cc WireGuardKit: Move shared structs to WireGuardKit
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
57f66f16f8 WireGuardKit: Add swift package scaffolding
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2020-12-02 11:08:08 +01:00
737f847c0d go-bridge: dup tunFd so as to not confuse NetworkExtension
The extension isn't banking on tunFd being closed ever, so dup it before
handing it to the rest of wireguard-go.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-09-02 17:21:37 +02:00
671a594945 Change QoS to .utility
As per comment by eskimo:
https://developer.apple.com/forums/thread/107904?answerId=328525022#328525022

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-06-25 17:50:15 -06:00
3646430528 Make sure that the tunnel and path monitor run on the same serial queue
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-06-25 17:50:15 -06:00
e9bd6e576f Fix retain cycle between NWPathMonitor and PacketTunnelProvider
See: https://www.marisibrothers.com/2017/04/memory-leak-in-swift-assigning-function.html

Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-06-25 17:50:15 -06:00
35300d1c5f Refactor interface name query
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-06-25 17:50:03 -06:00
112545248e Localization: Update Japanese
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-11 23:28:05 -06:00
78e6ecc4bc Localization: macOS: Add translations for 'Edit' button
By copying the 'macMenuEdit' entries to 'macButtonEdit'.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2020-04-11 16:57:51 +05:30
174a6e8e32 Localization: macOS: Fix localization for 'Edit' button
Signed-off-by: Roopesh Chander <roop@roopc.net>
2020-04-11 16:56:54 +05:30
20bcabbca4 Localization: Add German translation
Signed-off-by: Roopesh Chander <roop@roopc.net>
2020-04-11 16:41:45 +05:30
0a3554cedd Localization: Add Italian translation
Signed-off-by: Roopesh Chander <roop@roopc.net>
2020-04-11 16:41:45 +05:30
2acc7db63d Localization: Wire up Japanese translation
By adding the translated Localizable.strings to the Xcode project

Signed-off-by: Roopesh Chander <roop@roopc.net>
2020-04-11 16:41:45 +05:30
31af7049fc highlighter: insist on 256-bit keys, not 257-bit or 258-bit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-04-03 23:17:56 -06:00
52062a45c1 Japanese Translation
Translation for wireguard-apple. Checked on Xcode iOS simulator but not
all messages.

Signed-off-by: Eiji Tanioka <tanioka404@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-23 10:02:49 +01:00
30406dec6d wireguard-go-bridge: use C string instead of gostring_t
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-02-07 22:35:57 +01:00
edde27a0a0 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-01-27 12:10:53 +01:00
cfff596c30 wireguard-go-bridge: bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2020-01-27 12:10:21 +01:00
ba1c968cdf Update repo urls
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-12-30 11:54:13 +01:00
c48406ac38 wireguard-go-bridge: style
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-20 09:59:21 +01:00
14437477e6 README: specify required version in readme
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 18:59:24 +08:00
68d928192b Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 17:25:24 +08:00
028e76eb3f [REVERT ME SOON] TunnelsManager: Workaround for macOS Catalina deleting tunnels arbitrarily
In macOS Catalina, for some users, the tunnels get deleted arbitrarily
by the OS. It's not clear what triggers that.

As a workaround, in macOS Catalina, when we realize that tunnels have
been deleted outside the app, we reinstate those tunnels using the
information in the keychain.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-11-05 17:25:21 +08:00
cb0c965294 wireguard-go-bridge: update to 1.13.4
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-11-05 17:20:31 +08:00
d7ce621cb2 UI: iOS: more dark mode fixes
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-25 10:59:16 +02:00
a1ca4f6eb5 wireguard-go-bridge: work around Go 1.13.3 regression
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-25 10:36:58 +02:00
437f0dc46d Revert "NetworkExtension: don't use exit(0) hack on Catalina"
This reverts commit 3619279a65d9a506fb13d7f24909b38a5202fa8f.

Still broken!

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 16:51:50 +02:00
547eabb4ae Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 14:53:34 +02:00
bb16d3ebc8 iOS: UI: Make edit views full screen modal
This might be worse on the iPad. Oh well.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 14:51:04 +02:00
1b6170cbc9 NetworkExtension: don't use exit(0) hack on Catalina
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 11:44:13 +02:00
4c37a4b7a7 UI: iOS: adjust colors for iOS 13
To be compatible with Dark Mode, we need to change some of our
color references to be "dynamic".

Signed-off-by: Diab Neiroukh <officiallazerl0rd@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-15 00:01:17 +02:00
226166bdaf Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-12 22:20:37 +02:00
80fa72cabc iOS: UI: abort is optimized out in release builds
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-12 22:20:37 +02:00
d976d159d0 Keychain: make verification errors only happen when we're sure it's due to not found
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-11 22:07:18 +02:00
84ca7fcf40 ui: add donation link
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-11 21:44:12 +02:00
f120a6aab0 wireguard-go-bridge: reduce version checks and cleanup
We now rely on -trimpath which restricts us to >= 1.13, and the patch
application should fail too. This has the downside that the user will
need to clean their xcode project when they upgrade go, though.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-08 16:59:02 +02:00
0d8108d8da wireguard-go-bridge: update for 1.13
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-10-04 18:28:53 +02:00
e072ebee58 UI: iOS: set CFBundleDisplayName to satisfy new ITMS-90783 error
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 14:42:43 +02:00
c6767d9007 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 13:51:32 +02:00
bb5760cca4 WgQuickConfig: Swift treats \r\n as a single character
let blah = "hello\nworld\ndoes\nthis\nwork"
print(blah.split(separator: "\n"))
//output: ["hello", "world", "does", "this", "work"]

let blah2 = "hello\r\nworld\r\ndoes\r\nthis\r\nwork"
print(blah2.split(separator: "\n"))
//output: ["hello\r\nworld\r\ndoes\r\nthis\r\nwork"]
//expected: ["hello\r", "world\r", "does\r", "this\r", "work\r"]

In blah2, the string splitting fails because swift considers \r\n to be
its own character.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 12:26:39 +02:00
26b7971ba6 UI: macOS: Show useful error message on .conf import
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-28 12:07:18 +02:00
b286ede3c6 iOS: Importing: If tunnelsManager isn't ready yet, we should wait for it
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-13 23:02:54 +05:30
4ef7afe3ca macOS: Tunnel detail: Handle deletion outside app, again
This was previously done in commit f281b93, but the changes in commit
1507a97 for handling deletion of multiple tunnels undid this capability.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-13 22:21:31 +05:30
377f2f0496 TunnelsManager: store UID on macOS for keychain availability
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-11 02:18:42 +02:00
7ed5893fc6 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-10 18:58:18 +02:00
e70c397e54 TunnelProvider: remove all cleverness
This will cause more socket flaps than necessary but hopefully will fix
some bugs.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-10 18:47:39 +02:00
6a6be9edde on-demand: iOS: Fix crash on selecting Any SSID when already selected
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 23:55:44 +05:30
37f8500fe6 on-demand: Don't crash on encountering unexpected on-demand rules
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 23:55:39 +05:30
207f82dd9d macOS: Remove unused strings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-09 11:39:06 +02:00
de8fedf87a Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
98d306da5b macOS: remove store update escape hatch
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
c7b7b1247b TunnelProvider: store the entire NWPath
Otherwise [utun0, en0] == [en0, utun0] before WiFi has connected, and we
wind up not rebinding after WiFi does successfully connect, which means
people have trouble when resuming from sleep.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
a66f13eb01 README: update repo location
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-09 11:39:06 +02:00
f50e7ae686 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-06-06 10:27:39 +02:00
4d6692548c macOS: App menu > Quit shall show a prompt to quit or close window
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-06 10:27:11 +02:00
1dccd39818 macOS: Save/restore the log window's size
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-04 20:34:37 +05:30
4cb775c72f macOS: Log view: Allow resizing horizontally
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-06-04 15:48:42 +05:30
4cb783c447 go-bridge: bump version
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 19:20:51 +02:00
d20daa345a Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:30:06 +02:00
168ba2da8a NetworkExtension: bump sockets on path change
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-31 17:29:29 +02:00
714d6a41bd macOS: Dismiss modals correctly
Previously, the presented vc were leaking when discarding edits
or when closing the log view controller.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-31 17:29:29 +02:00
9b92a8f933 macOS: Update app icon
Reduce the size and add a drop shadow

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-31 17:29:29 +02:00
5e9780ef8f iOS: Should be able to re-show tunnel detail
Fixes a bug introduced in the refactoring in
commit 7322fb084087774e8b58e347902f6d7036cbde5c

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 15:36:39 +05:30
9faf814e8b macOS: Tunnel detail: No need to update runtime info on tunnelSaved()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 14:43:41 +05:30
30da10a0e9 macOS: Start refreshing runtime info in viewWillAppear(), not init()
Because when the window is closed and reopened, we should start
refreshing runtime info again.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-27 14:38:17 +05:30
a18614d6b3 macOS: Fix residual menu highlight on reopen
If we close the window with Cmd+W or Cmd+Q and then re-launch the app,
the main menu shows residual highlight from the close action. This
commit fixes that.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-26 00:12:47 +05:30
5100e597aa macOS: do not call out to recent tunnels tracker
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
0340641c4c NetworkExtension: apparently the extension process is scoped properly anyway
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
813dea6902 NetworkExtension: use excludedRoutes instead of binding on iOS
The networking stack there is to flaky and the notifier doesn't always
fire correctly. Hopefully excludedRoutes works well with XLAT; otherwise
we're in trouble.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-05-26 00:12:47 +05:30
c30d491edc iOS: Should be able to call showTunnelDetail multiple times
And the detail views should not stack up.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-26 00:12:42 +05:30
88c80d6694 iOS: Refactor showing of the tunnel detail
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
393718dfaf iOS: Show Home screen quick actions for recent tunnels
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
f852b6f919 iOS: Keep track of most-recently-activated tunnels
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-25 13:24:01 +02:00
8926434682 macOS: Workaround for unresponsive main menu when launched from Xcode
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:21 +05:30
493c7b102e macOS: Ignore bogus reopen because of login item helper
The bogus reopen occurs because the SMLoginItemSetEnabled actually runs
the helper app immediately. The helper app attempts to launch the main
app, causing a reopen Apple event (rapp) to be sent.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:21 +05:30
717bc8a26f macOS: Workaround for unresponsive main menu after reopen
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 19:52:12 +05:30
e582155a10 macOS: Ensure window is shown on app reopening
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-22 15:19:57 +05:30
70d19691a7 macOS: Simplify detecting the type of an Apple event
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:28 +05:30
40b1f0bac8 macOS: Don't show manage window when launched at login
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:28 +05:30
52ac9b82c2 macOS: Login item: Get helper app version from xcconfig
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
fc1fdbbcdb macOS: Login item: Fix Info.plist path
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
300268daa0 macOS: Show Manage Tunnels window on startup
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
9bf304a9ac macOS: Minor refactor of StatusMenuWindowDelegate
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
586a592b68 macOS: Disable 'Delete Selected' when nothing is selected
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
c0526d2efb macOS: Some menu item titles are automatically inferred
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
fdbd4f875e macOS: Use title-style capitalization for menu items
As per https://developer.apple.com/design/human-interface-guidelines/macos/menus/menu-anatomy/

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
4a037cc706 macOS: Make it clear that status menu Quit quits the app
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
5190fc2249 macOS: Quit in main menu shall just close the window
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
3f25d54dcc macOS: Get back removing tunnel using the Delete key
This now works only when the list view has focus

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
f9880907a2 macOS: Both list and detail main menu items should be always enabled
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
404fa741e8 macOS: swiftlint: Suppress incorrect warnings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
6e1f03e41c macOS: Set a main menu for the app
The main menu would be shown only when the manage tunnels window
is visible.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
6d8965e97d macOS: Remove custom key event handling
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
5e5481b69b macOS: Show app in dock when showing the manage tunnels window
This way, the app can participate in Cmd+Tab

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-20 16:42:27 +05:30
69b33c0fad macOS: Edit view: Save on Cmd+S
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 15:45:26 +05:30
167e4f0bf2 macOS: Edit view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:23:52 +05:30
6e3b28852a macOS: Log view: Dismiss on Esc
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-05-12 13:21:42 +05:30
5914e868ab iOS: Log view: Improve the look
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-28 14:29:22 +05:30
83ea9d6fa7 wireguard-go-bridge: add missing format specifier for error
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-24 13:24:03 +02:00
b954e9a4fd Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-23 06:49:16 +02:00
76894fba68 Xcode: Use dwarf for debug and dwarf-with-dsym for release
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 18:22:31 +05:30
89a564ce62 Swift 5 migration: Make use of Result type
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 17:51:42 +05:30
178fe86d36 macOS: Detect when updating from the App Store
And show an alert when tunnels are active during updation -- that
might cause the update to not work correctly.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-21 15:43:10 +05:30
252 changed files with 9994 additions and 2148 deletions

6
.gitignore vendored
View File

@ -31,6 +31,10 @@ xcuserdata
*.hmap
*.ipa
# Swift Package Manager
.swiftpm
.build/
# Fastlane
*.app.dSYM.zip
*.mobileprovision
@ -41,7 +45,7 @@ Preview.html
output
# Wireguard specific
WireGuard/WireGuard/Config/Developer.xcconfig
Sources/WireGuardApp/Config/Developer.xcconfig
# Vim
.*.sw*

View File

@ -7,6 +7,7 @@ disabled_rules:
- type_body_length
- function_body_length
- nesting
- inclusive_language
opt_in_rules:
- empty_count
- empty_string

View File

@ -1,4 +1,4 @@
Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View File

@ -136,5 +136,5 @@ Here's an example WireGuard configuration payload dictionary:
Configurations added via .mobileconfig will not be migrated into keychain until the WireGuard application is opened once.
[wg-quick(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg-quick.8
[wg(8)]: https://git.zx2c4.com/WireGuard/about/src/tools/man/wg.8
[wg-quick(8)]: https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
[wg(8)]: https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8

40
Package.swift Normal file
View File

@ -0,0 +1,40 @@
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "WireGuardKit",
platforms: [
.macOS(.v10_14),
.iOS(.v12)
],
products: [
.library(name: "WireGuardKit", targets: ["WireGuardKit"])
],
dependencies: [],
targets: [
.target(
name: "WireGuardKit",
dependencies: ["WireGuardKitGo", "WireGuardKitC"]
),
.target(
name: "WireGuardKitC",
dependencies: [],
publicHeadersPath: "."
),
.target(
name: "WireGuardKitGo",
dependencies: [],
exclude: [
"goruntime-boottime-over-monotonic.diff",
"go.mod",
"go.sum",
"api-ios.go",
"Makefile"
],
publicHeadersPath: ".",
linkerSettings: [.linkedLibrary("wg-go")]
)
]
)

View File

@ -1,22 +1,24 @@
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
This project contains an application for iOS and for macOS, as well as many components shared between the two of them. You may toggle between the two platforms by selecting the target from within Xcode.
## Building
- Clone this repo recursively:
- Clone this repo:
```
$ git clone --recursive https://git.zx2c4.com/wireguard-ios
$ cd wireguard-ios
$ git clone https://git.zx2c4.com/wireguard-apple
$ cd wireguard-apple
```
- Rename and populate developer team ID file:
```
$ cp WireGuard/WireGuard/Config/Developer.xcconfig.template WireGuard/WireGuard/Config/Developer.xcconfig
$ vim WireGuard/WireGuard/Config/Developer.xcconfig
$ cp Sources/WireGuardApp/Config/Developer.xcconfig.template Sources/WireGuardApp/Config/Developer.xcconfig
$ vim Sources/WireGuardApp/Config/Developer.xcconfig
```
- Install swiftlint and go:
- Install swiftlint and go 1.15:
```
$ brew install swiftlint go
@ -25,11 +27,57 @@ $ brew install swiftlint go
- Open project in Xcode:
```
$ open ./WireGuard/WireGuard.xcodeproj
$ open WireGuard.xcodeproj
```
- Flip switches, press buttons, and make whirling noises until Xcode builds it.
## WireGuardKit integration
1. Open your Xcode project and add the Swift package with the following URL:
```
https://git.zx2c4.com/wireguard-apple
```
2. `WireGuardKit` links against `wireguard-go-bridge` library, but it cannot build it automatically
due to Swift package manager limitations. So it needs a little help from a developer.
Please follow the instructions below to create a build target(s) for `wireguard-go-bridge`.
- In Xcode, click File -> New -> Target. Switch to "Other" tab and choose "External Build
System".
- Type in `WireGuardGoBridge<PLATFORM>` under the "Product name", replacing the `<PLATFORM>`
placeholder with the name of the platform. For example, when targeting macOS use `macOS`, or
when targeting iOS use `iOS`.
Make sure the build tool is set to: `/usr/bin/make` (default).
- In the appeared "Info" tab of a newly created target, type in the "Directory" path under
the "External Build Tool Configuration":
```
$BUILD_DIR/../../SourcePackages/checkouts/wireguard-apple/Sources/WireGuardKitGo
```
- Switch to "Build Settings" and find `SDKROOT`.
Type in `macosx` if you target macOS, or type in `iphoneos` if you target iOS.
3. Go to Xcode project settings and locate your network extension target and switch to
"Build Phases" tab.
- Locate "Dependencies" section and hit "+" to add `WireGuardGoBridge<PLATFORM>` replacing
the `<PLATFORM>` placeholder with the name of platform matching the network extension
deployment target (i.e macOS or iOS).
- Locate the "Link with binary libraries" section and hit "+" to add `WireGuardKit`.
4. In Xcode project settings, locate your main bundle app and switch to "Build Phases" tab.
Locate the "Link with binary libraries" section and hit "+" to add `WireGuardKit`.
5. iOS only: Locate Bitcode settings under your application target, Build settings -> Enable Bitcode,
change the corresponding value to "No".
Note that if you ship your app for both iOS and macOS, make sure to repeat the steps 2-4 twice,
once per platform.
## MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import os.log

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import Security
@ -7,9 +7,9 @@ import Security
class Keychain {
static func openReference(called ref: Data) -> String? {
var result: CFTypeRef?
let ret = SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword,
kSecValuePersistentRef as String: ref,
kSecReturnData as String: true] as CFDictionary,
let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
kSecValuePersistentRef: ref,
kSecReturnData: true] as CFDictionary,
&result)
if ret != errSecSuccess || result == nil {
wg_log(.error, message: "Unable to open config from keychain: \(ret)")
@ -21,27 +21,28 @@ class Keychain {
static func makeReference(containing value: String, called name: String, previouslyReferencedBy oldRef: Data? = nil) -> Data? {
var ret: OSStatus
guard var id = Bundle.main.bundleIdentifier else {
guard var bundleIdentifier = Bundle.main.bundleIdentifier else {
wg_log(.error, staticMessage: "Unable to determine bundle identifier")
return nil
}
if id.hasSuffix(".network-extension") {
id.removeLast(".network-extension".count)
if bundleIdentifier.hasSuffix(".network-extension") {
bundleIdentifier.removeLast(".network-extension".count)
}
var items: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrLabel as String: "WireGuard Tunnel: " + name,
kSecAttrAccount as String: name + ": " + UUID().uuidString,
kSecAttrDescription as String: "wg-quick(8) config",
kSecAttrService as String: id,
kSecValueData as String: value.data(using: .utf8) as Any,
kSecReturnPersistentRef as String: true]
let itemLabel = "WireGuard Tunnel: \(name)"
var items: [CFString: Any] = [kSecClass: kSecClassGenericPassword,
kSecAttrLabel: itemLabel,
kSecAttrAccount: name + ": " + UUID().uuidString,
kSecAttrDescription: "wg-quick(8) config",
kSecAttrService: bundleIdentifier,
kSecValueData: value.data(using: .utf8) as Any,
kSecReturnPersistentRef: true]
#if os(iOS)
items[kSecAttrAccessGroup as String] = FileManager.appGroupId
items[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
items[kSecAttrAccessGroup] = FileManager.appGroupId
items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlock
#elseif os(macOS)
items[kSecAttrSynchronizable as String] = false
items[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
items[kSecAttrSynchronizable] = false
items[kSecAttrAccessible] = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
guard let extensionPath = Bundle.main.builtInPlugInsURL?.appendingPathComponent("WireGuardNetworkExtension.appex").path else {
wg_log(.error, staticMessage: "Unable to determine app extension path")
@ -60,14 +61,12 @@ class Keychain {
return nil
}
var access: SecAccess?
ret = SecAccessCreate((items[kSecAttrLabel as String] as? String)! as CFString,
[extensionApp!, mainApp!] as CFArray,
&access)
ret = SecAccessCreate(itemLabel as CFString, [extensionApp!, mainApp!] as CFArray, &access)
if ret != errSecSuccess || access == nil {
wg_log(.error, message: "Unable to create keychain ACL object: \(ret)")
return nil
}
items[kSecAttrAccess as String] = access!
items[kSecAttrAccess] = access!
#else
#error("Unimplemented")
#endif
@ -85,7 +84,7 @@ class Keychain {
}
static func deleteReference(called ref: Data) {
let ret = SecItemDelete([kSecValuePersistentRef as String: ref] as CFDictionary)
let ret = SecItemDelete([kSecValuePersistentRef: ref] as CFDictionary)
if ret != errSecSuccess {
wg_log(.error, message: "Unable to delete config from keychain: \(ret)")
}
@ -93,10 +92,10 @@ class Keychain {
static func deleteReferences(except whitelist: Set<Data>) {
var result: CFTypeRef?
let ret = SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Bundle.main.bundleIdentifier as Any,
kSecMatchLimit as String: kSecMatchLimitAll,
kSecReturnPersistentRef as String: true] as CFDictionary,
let ret = SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
kSecAttrService: Bundle.main.bundleIdentifier as Any,
kSecMatchLimit: kSecMatchLimitAll,
kSecReturnPersistentRef: true] as CFDictionary,
&result)
if ret != errSecSuccess || result == nil {
return
@ -110,8 +109,8 @@ class Keychain {
}
static func verifyReference(called ref: Data) -> Bool {
return SecItemCopyMatching([kSecClass as String: kSecClassGenericPassword,
kSecValuePersistentRef as String: ref] as CFDictionary,
nil) == errSecSuccess
return SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
kSecValuePersistentRef: ref] as CFDictionary,
nil) != errSecItemNotFound
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import os.log
@ -49,8 +49,8 @@ public class Logger {
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
}
let goBackendVersion = WIREGUARD_GO_VERSION
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
Logger.global?.log(message: "App version: \(appVersion)")
}
}

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
*/
#include <string.h>

View File

@ -1,6 +1,6 @@
/* SPDX-License-Identifier: MIT
*
* Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
* Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
*/
#ifndef RINGLOGGER_H

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import NetworkExtension
@ -22,6 +22,9 @@ extension NETunnelProviderProtocol {
if passwordReference == nil {
return nil
}
#if os(macOS)
providerConfiguration = ["UID": getuid()]
#endif
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
if endpoints.count == 1 {
@ -60,11 +63,25 @@ extension NETunnelProviderProtocol {
* in the keychain. But it's still useful to keep the migration
* around so that .mobileconfig files are easier.
*/
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
providerConfiguration = nil
guard passwordReference == nil else { return true }
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
return true
if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String {
#if os(macOS)
providerConfiguration = ["UID": getuid()]
#elseif os(iOS)
providerConfiguration = nil
#else
#error("Unimplemented")
#endif
guard passwordReference == nil else { return true }
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
return true
}
#if os(macOS)
if passwordReference != nil && providerConfiguration?["UID"] == nil && verifyConfigurationReference() {
providerConfiguration = ["UID": getuid()]
return true
}
#endif
return false
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
@ -39,7 +39,7 @@ extension TunnelConfiguration {
var interfaceConfiguration: InterfaceConfiguration?
var peerConfigurations = [PeerConfiguration]()
let lines = wgQuickConfig.split(separator: "\n")
let lines = wgQuickConfig.split { $0.isNewline }
var parserState = ParserState.notInASection
var attributes = [String: String]()
@ -111,7 +111,7 @@ extension TunnelConfiguration {
}
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray)
if peerPublicKeysArray.count != peerPublicKeysSet.count {
throw ParseError.multiplePeersWithSamePublicKey
}
@ -125,9 +125,7 @@ extension TunnelConfiguration {
func asWgQuickConfig() -> String {
var output = "[Interface]\n"
if let privateKey = interface.privateKey.base64Key() {
output.append("PrivateKey = \(privateKey)\n")
}
output.append("PrivateKey = \(interface.privateKey.base64Key)\n")
if let listenPort = interface.listenPort {
output.append("ListenPort = \(listenPort)\n")
}
@ -135,8 +133,10 @@ extension TunnelConfiguration {
let addressString = interface.addresses.map { $0.stringRepresentation }.joined(separator: ", ")
output.append("Address = \(addressString)\n")
}
if !interface.dns.isEmpty {
let dnsString = interface.dns.map { $0.stringRepresentation }.joined(separator: ", ")
if !interface.dns.isEmpty || !interface.dnsSearch.isEmpty {
var dnsLine = interface.dns.map { $0.stringRepresentation }
dnsLine.append(contentsOf: interface.dnsSearch)
let dnsString = dnsLine.joined(separator: ", ")
output.append("DNS = \(dnsString)\n")
}
if let mtu = interface.mtu {
@ -145,10 +145,8 @@ extension TunnelConfiguration {
for peer in peers {
output.append("\n[Peer]\n")
if let publicKey = peer.publicKey.base64Key() {
output.append("PublicKey = \(publicKey)\n")
}
if let preSharedKey = peer.preSharedKey?.base64Key() {
output.append("PublicKey = \(peer.publicKey.base64Key)\n")
if let preSharedKey = peer.preSharedKey?.base64Key {
output.append("PresharedKey = \(preSharedKey)\n")
}
if !peer.allowedIPs.isEmpty {
@ -170,7 +168,7 @@ extension TunnelConfiguration {
guard let privateKeyString = attributes["privatekey"] else {
throw ParseError.interfaceHasNoPrivateKey
}
guard let privateKey = Data(base64Key: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else {
guard let privateKey = PrivateKey(base64Key: privateKeyString) else {
throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString)
}
var interface = InterfaceConfiguration(privateKey: privateKey)
@ -192,13 +190,16 @@ extension TunnelConfiguration {
}
if let dnsString = attributes["dns"] {
var dnsServers = [DNSServer]()
var dnsSearch = [String]()
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
guard let dnsServer = DNSServer(from: dnsServerString) else {
throw ParseError.interfaceHasInvalidDNS(dnsServerString)
if let dnsServer = DNSServer(from: dnsServerString) {
dnsServers.append(dnsServer)
} else {
dnsSearch.append(dnsServerString)
}
dnsServers.append(dnsServer)
}
interface.dns = dnsServers
interface.dnsSearch = dnsSearch
}
if let mtuString = attributes["mtu"] {
guard let mtu = UInt16(mtuString) else {
@ -213,12 +214,12 @@ extension TunnelConfiguration {
guard let publicKeyString = attributes["publickey"] else {
throw ParseError.peerHasNoPublicKey
}
guard let publicKey = Data(base64Key: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else {
guard let publicKey = PublicKey(base64Key: publicKeyString) else {
throw ParseError.peerHasInvalidPublicKey(publicKeyString)
}
var peer = PeerConfiguration(publicKey: publicKey)
if let preSharedKeyString = attributes["presharedkey"] {
guard let preSharedKey = Data(base64Key: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else {
guard let preSharedKey = PreSharedKey(base64Key: preSharedKeyString) else {
throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString)
}
peer.preSharedKey = preSharedKey

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
// iOS permission prompts

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
// Generic alert action names
@ -291,19 +291,41 @@
"alertSystemErrorMessageTunnelConfigurationReadWriteFailed" = "Reading or writing the configuration failed.";
"alertSystemErrorMessageTunnelConfigurationUnknown" = "Unknown system error.";
// Mac status bar menu / pulldown menu
// Mac status bar menu / pulldown menu / main menu
"macMenuNetworks (%@)" = "Networks: %@";
"macMenuNetworksNone" = "Networks: None";
"macMenuTitle" = "WireGuard";
"macMenuManageTunnels" = "Manage tunnels";
"macMenuImportTunnels" = "Import tunnel(s) from file…";
"macMenuAddEmptyTunnel" = "Add empty tunnel…";
"macMenuViewLog" = "View log";
"macMenuExportTunnels" = "Export tunnels to zip…";
"macMenuManageTunnels" = "Manage Tunnels";
"macMenuImportTunnels" = "Import Tunnel(s) from File…";
"macMenuAddEmptyTunnel" = "Add Empty Tunnel…";
"macMenuViewLog" = "View Log";
"macMenuExportTunnels" = "Export Tunnels to Zip…";
"macMenuAbout" = "About WireGuard";
"macMenuQuit" = "Quit";
"macMenuQuit" = "Quit WireGuard";
"macMenuHideApp" = "Hide WireGuard";
"macMenuHideOtherApps" = "Hide Others";
"macMenuShowAllApps" = "Show All";
"macMenuFile" = "File";
"macMenuCloseWindow" = "Close Window";
"macMenuEdit" = "Edit";
"macMenuCut" = "Cut";
"macMenuCopy" = "Copy";
"macMenuPaste" = "Paste";
"macMenuSelectAll" = "Select All";
"macMenuTunnel" = "Tunnel";
"macMenuToggleStatus" = "Toggle Status";
"macMenuEditTunnel" = "Edit…";
"macMenuDeleteSelected" = "Delete Selected";
"macMenuWindow" = "Window";
"macMenuMinimize" = "Minimize";
"macMenuZoom" = "Zoom";
// Mac manage tunnels window
@ -327,6 +349,8 @@
"macButtonDeleteTunnels (%d)" = "Delete %d tunnels";
"macButtonEdit" = "Edit";
// Mac detail/edit view fields
"macFieldKey (%@)" = "%@:";
@ -383,10 +407,14 @@
// Mac alert
"macConfirmAndQuitAlertMessage" = "Do you want to close the tunnels manager or quit WireGuard entirely?";
"macConfirmAndQuitAlertInfo" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.";
"macConfirmAndQuitInfoWithActiveTunnel (%@)" = "If you close the tunnels manager, WireGuard will continue to be available from the menu bar icon.\n\nNote that if you quit WireGuard entirely the currently active tunnel ('%@') will still remain active until you deactivate it from this application or through the Network panel in System Preferences.";
"macConfirmAndQuitAlertQuitWireGuard" = "Quit WireGuard";
"macConfirmAndQuitAlertCloseWindow" = "Close Tunnels Manager";
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel";
"macAppExitingWithActiveTunnelInfo" = "The tunnel will remain active after exiting. You may disable it by reopening this application or through the Network panel in System Preferences.";
"macPrivacyNoticeMessage" = "Privacy notice: be sure you trust this configuration file";
"macPrivacyNoticeInfo" = "You will be prompted by the system to allow or disallow adding a VPN configuration. While this application does not send any information to the WireGuard project, information is by design sent to the servers specified inside of the configuration file you have just added, which configures your computer to use those servers as a VPN. Be certain that you trust this configuration before clicking “Allow” in the following dialog.";
// Mac tooltip
@ -405,3 +433,13 @@
"macUnusableTunnelMessage" = "The configuration for this tunnel cannot be found in the keychain.";
"macUnusableTunnelInfo" = "In case this tunnel was created by another user, only that user can view, edit, or activate this tunnel.";
"macUnusableTunnelButtonTitleDeleteTunnel" = "Delete tunnel";
// Mac App Store updating alert
"macAppStoreUpdatingAlertMessage" = "App Store would like to update WireGuard";
"macAppStoreUpdatingAlertInfoWithOnDemand (%@)" = "Please disable on-demand for tunnel %@, deactivate it, and then continue updating in App Store.";
"macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)" = "Please deactivate tunnel %@ and then continue updating in App Store.";
// Donation
"donateLink" = "♥ Donate to the WireGuard Project";

View File

@ -0,0 +1,2 @@
VERSION_NAME = 1.0.11
VERSION_ID = 20

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import NetworkExtension
@ -46,46 +46,59 @@ extension ActivateOnDemandOption {
}
init(from tunnelProviderManager: NETunnelProviderManager) {
let rules = tunnelProviderManager.onDemandRules ?? []
let activateOnDemandOption: ActivateOnDemandOption
if tunnelProviderManager.isOnDemandEnabled, let onDemandRules = tunnelProviderManager.onDemandRules {
self = ActivateOnDemandOption.create(from: onDemandRules)
} else {
self = .off
}
}
private static func create(from rules: [NEOnDemandRule]) -> ActivateOnDemandOption {
switch rules.count {
case 0:
activateOnDemandOption = .off
return .off
case 1:
let rule = rules[0]
precondition(rule.action == .connect)
activateOnDemandOption = .anyInterface(.anySSID)
guard rule.action == .connect else { return .off }
return .anyInterface(.anySSID)
case 2:
let connectRule = rules.first(where: { $0.action == .connect })!
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
guard let connectRule = rules.first(where: { $0.action == .connect }) else {
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no connect rule.")
return .off
}
guard let disconnectRule = rules.first(where: { $0.action == .disconnect }) else {
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but no disconnect rule.")
return .off
}
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
activateOnDemandOption = .wiFiInterfaceOnly(.anySSID)
return .wiFiInterfaceOnly(.anySSID)
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
activateOnDemandOption = .nonWiFiInterfaceOnly
return .nonWiFiInterfaceOnly
} else {
fatalError("Unexpected onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found but interface types are inconsistent.")
return .off
}
case 3:
let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil })!
let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType })!
guard let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil }) else { return .off }
guard let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType }) else { return .off }
let ssids = ssidRule.ssidMatch!
switch (ssidRule.action, nonWiFiRule.action) {
case (.connect, .connect):
activateOnDemandOption = .anyInterface(.onlySpecificSSIDs(ssids))
return .anyInterface(.onlySpecificSSIDs(ssids))
case (.connect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
return .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
case (.disconnect, .connect):
activateOnDemandOption = .anyInterface(.exceptSpecificSSIDs(ssids))
return .anyInterface(.exceptSpecificSSIDs(ssids))
case (.disconnect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
return .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
default:
fatalError("Unexpected SSID onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected onDemandRules set on tunnel provider manager: \(rules.count) rules found")
return .off
}
default:
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
wg_log(.error, message: "Unexpected number of onDemandRules set on tunnel provider manager: \(rules.count) rules found")
return .off
}
self = activateOnDemandOption
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import NetworkExtension
@ -26,11 +26,11 @@ class MockTunnels {
static func createMockTunnels() -> [NETunnelProviderManager] {
return tunnelNames.map { tunnelName -> NETunnelProviderManager in
var interface = InterfaceConfiguration(privateKey: Curve25519.generatePrivateKey())
var interface = InterfaceConfiguration(privateKey: PrivateKey())
interface.addresses = [IPAddressRange(from: String(format: address, Int.random(in: 1 ... 10), Int.random(in: 1 ... 254)))!]
interface.dns = dnsServers.map { DNSServer(from: $0)! }
var peer = PeerConfiguration(publicKey: Curve25519.generatePublicKey(fromPrivateKey: Curve25519.generatePrivateKey()))
var peer = PeerConfiguration(publicKey: PrivateKey().publicKey)
peer.endpoint = Endpoint(from: endpoint)
peer.allowedIPs = [IPAddressRange(from: allowedIPs)!]

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
@ -67,13 +67,14 @@ extension TunnelConfiguration {
}
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray)
if peerPublicKeysArray.count != peerPublicKeysSet.count {
throw ParseError.multiplePeersWithSamePublicKey
}
interfaceConfiguration?.addresses = base?.interface.addresses ?? []
interfaceConfiguration?.dns = base?.interface.dns ?? []
interfaceConfiguration?.dnsSearch = base?.interface.dnsSearch ?? []
interfaceConfiguration?.mtu = base?.interface.mtu
if let interfaceConfiguration = interfaceConfiguration {
@ -87,7 +88,7 @@ extension TunnelConfiguration {
guard let privateKeyString = attributes["private_key"] else {
throw ParseError.interfaceHasNoPrivateKey
}
guard let privateKey = Data(hexKey: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else {
guard let privateKey = PrivateKey(hexKey: privateKeyString) else {
throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString)
}
var interface = InterfaceConfiguration(privateKey: privateKey)
@ -106,18 +107,18 @@ extension TunnelConfiguration {
guard let publicKeyString = attributes["public_key"] else {
throw ParseError.peerHasNoPublicKey
}
guard let publicKey = Data(hexKey: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else {
guard let publicKey = PublicKey(hexKey: publicKeyString) else {
throw ParseError.peerHasInvalidPublicKey(publicKeyString)
}
var peer = PeerConfiguration(publicKey: publicKey)
if let preSharedKeyString = attributes["preshared_key"] {
guard let preSharedKey = Data(hexKey: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else {
guard let preSharedKey = PreSharedKey(hexKey: preSharedKeyString) else {
throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString)
}
// TODO(zx2c4): does the compiler optimize this away?
var accumulator: UInt8 = 0
for index in 0..<preSharedKey.count {
accumulator |= preSharedKey[index]
for index in 0..<preSharedKey.rawValue.count {
accumulator |= preSharedKey.rawValue[index]
}
if accumulator != 0 {
peer.preSharedKey = preSharedKey

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import NetworkExtension

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import NetworkExtension

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import NetworkExtension
@ -33,7 +33,7 @@ class TunnelsManager {
startObservingTunnelConfigurations()
}
static func create(completionHandler: @escaping (WireGuardResult<TunnelsManager>) -> Void) {
static func create(completionHandler: @escaping (Result<TunnelsManager, TunnelsManagerError>) -> Void) {
#if targetEnvironment(simulator)
completionHandler(.success(TunnelsManager(tunnelProviders: MockTunnels.createMockTunnels())))
#else
@ -46,7 +46,11 @@ class TunnelsManager {
var tunnelManagers = managers ?? []
var refs: Set<Data> = []
var tunnelNames: Set<String> = []
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
if let tunnelName = tunnelManager.localizedDescription {
tunnelNames.insert(tunnelName)
}
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
tunnelManager.saveToPreferences { _ in }
@ -54,18 +58,27 @@ class TunnelsManager {
#if os(iOS)
let passwordRef = proto.verifyConfigurationReference() ? proto.passwordReference : nil
#elseif os(macOS)
let passwordRef = proto.passwordReference // To handle multiple users in macOS, we skip verifying
let passwordRef: Data?
if proto.providerConfiguration?["UID"] as? uid_t == getuid() {
passwordRef = proto.verifyConfigurationReference() ? proto.passwordReference : nil
} else {
passwordRef = proto.passwordReference // To handle multiple users in macOS, we skip verifying
}
#else
#error("Unimplemented")
#endif
if let ref = passwordRef {
refs.insert(ref)
} else {
wg_log(.info, message: "Removing orphaned tunnel with non-verifying keychain entry: \(tunnelManager.localizedDescription ?? "<unknown>")")
tunnelManager.removeFromPreferences { _ in }
tunnelManagers.remove(at: index)
}
}
Keychain.deleteReferences(except: refs)
#if os(iOS)
RecentTunnelsTracker.cleanupTunnels(except: tunnelNames)
#endif
completionHandler(.success(TunnelsManager(tunnelProviders: tunnelManagers)))
}
#endif
@ -104,7 +117,7 @@ class TunnelsManager {
}
}
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (Result<TunnelContainer, TunnelsManagerError>) -> Void) {
let tunnelName = tunnelConfiguration.name ?? ""
if tunnelName.isEmpty {
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
@ -167,9 +180,15 @@ class TunnelsManager {
let tail = tunnelConfigurations.dropFirst()
add(tunnelConfiguration: head) { [weak self, tail] result in
DispatchQueue.main.async {
let numberSuccessful = numberSuccessful + (result.isSuccess ? 1 : 0)
let lastError = lastError ?? (result.error as? TunnelsManagerError)
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful, lastError: lastError, completionHandler: completionHandler)
var numberSuccessfulCount = numberSuccessful
var lastError: TunnelsManagerError?
switch result {
case .failure(let error):
lastError = error
case .success:
numberSuccessfulCount = numberSuccessful + 1
}
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessfulCount, lastError: lastError, completionHandler: completionHandler)
}
}
}
@ -182,7 +201,8 @@ class TunnelsManager {
}
let tunnelProviderManager = tunnel.tunnelProvider
let isNameChanged = tunnelName != tunnelProviderManager.localizedDescription
let oldName = tunnelProviderManager.localizedDescription ?? ""
let isNameChanged = tunnelName != oldName
if isNameChanged {
guard !tunnels.contains(where: { $0.name == tunnelName }) else {
completionHandler(TunnelsManagerError.tunnelAlreadyExistsWithThatName)
@ -214,6 +234,9 @@ class TunnelsManager {
self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
let newIndex = self.tunnels.firstIndex(of: tunnel)!
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
#if os(iOS)
RecentTunnelsTracker.handleTunnelRenamed(oldName: oldName, newName: tunnelName)
#endif
}
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
@ -245,10 +268,15 @@ class TunnelsManager {
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
let tunnelProviderManager = tunnel.tunnelProvider
if tunnel.isTunnelConfigurationAvailableInKeychain {
#if os(macOS)
if tunnel.isTunnelAvailableToUser {
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
}
#elseif os(iOS)
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
#else
#error("Unimplemented")
#endif
tunnelProviderManager.removeFromPreferences { [weak self] error in
guard error == nil else {
wg_log(.error, message: "Remove: Saving configuration failed: \(error!)")
@ -260,6 +288,10 @@ class TunnelsManager {
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
}
completionHandler(nil)
#if os(iOS)
RecentTunnelsTracker.handleTunnelRemoved(tunnelName: tunnel.name)
#endif
}
}
@ -292,6 +324,10 @@ class TunnelsManager {
return tunnels[index]
}
func mapTunnels<T>(transform: (TunnelContainer) throws -> T) rethrows -> [T] {
return try tunnels.map(transform)
}
func index(of tunnel: TunnelContainer) -> Int? {
return tunnels.firstIndex(of: tunnel)
}
@ -337,6 +373,10 @@ class TunnelsManager {
#else
tunnel.startActivation(activationDelegate: activationDelegate)
#endif
#if os(iOS)
RecentTunnelsTracker.handleTunnelActivated(tunnelName: tunnel.name)
#endif
}
func startDeactivation(of tunnel: TunnelContainer) {
@ -409,8 +449,8 @@ class TunnelsManager {
}
}
static func tunnelNameIsLessThan(_ a: String, _ b: String) -> Bool {
return a.compare(b, options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive, .numeric]) == .orderedAscending
static func tunnelNameIsLessThan(_ lhs: String, _ rhs: String) -> Bool {
return lhs.compare(rhs, options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive, .numeric]) == .orderedAscending
}
}
@ -464,14 +504,16 @@ class TunnelContainer: NSObject {
return tunnelProvider.tunnelConfiguration
}
var isTunnelConfigurationAvailableInKeychain: Bool {
return tunnelProvider.isTunnelConfigurationAvailableInKeychain
}
var onDemandOption: ActivateOnDemandOption {
return ActivateOnDemandOption(from: tunnelProvider)
}
#if os(macOS)
var isTunnelAvailableToUser: Bool {
return (tunnelProvider.protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration?["UID"] as? uid_t == getuid()
}
#endif
init(tunnel: NETunnelProviderManager) {
name = tunnel.localizedDescription ?? "Unnamed"
let status = TunnelStatus(from: tunnel.connection.status)
@ -580,18 +622,8 @@ class TunnelContainer: NSObject {
}
extension NETunnelProviderManager {
private static var cachedIsConfigAvailableInKeychainKey: UInt8 = 0
private static var cachedConfigKey: UInt8 = 0
var isTunnelConfigurationAvailableInKeychain: Bool {
if let cachedNumber = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey) as? NSNumber {
return cachedNumber.boolValue
}
let isAvailable = (protocolConfiguration as? NETunnelProviderProtocol)?.verifyConfigurationReference() ?? false
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: isAvailable), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return isAvailable
}
var tunnelConfiguration: TunnelConfiguration? {
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
return cached
@ -607,17 +639,9 @@ extension NETunnelProviderManager {
protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration)
localizedDescription = tunnelConfiguration.name
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: true), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
func isEquivalentTo(_ tunnel: TunnelContainer) -> Bool {
switch (isTunnelConfigurationAvailableInKeychain, tunnel.isTunnelConfigurationAvailableInKeychain) {
case (true, true):
return tunnelConfiguration == tunnel.tunnelConfiguration
case (false, false):
return localizedDescription == tunnel.name
default:
return false
}
return localizedDescription == tunnel.name && tunnelConfiguration == tunnel.tunnelConfiguration
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
protocol ErrorPresenterProtocol {
static func showErrorAlert(title: String, message: String, from sourceVC: AnyObject?, onPresented: (() -> Void)?, onDismissal: (() -> Void)?)

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
import LocalAuthentication

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
@ -16,10 +16,10 @@ class TunnelImporter {
if url.pathExtension.lowercased() == "zip" {
dispatchGroup.enter()
ZipImporter.importConfigFiles(from: url) { result in
if let error = result.error {
switch result {
case .failure(let error):
lastFileImportErrorText = error.alertText
}
if let configsInZip = result.value {
case .success(let configsInZip):
configs.append(contentsOf: configsInZip)
}
dispatchGroup.leave()
@ -44,10 +44,20 @@ class TunnelImporter {
}
return
}
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
var parseError: Error?
var tunnelConfiguration: TunnelConfiguration?
do {
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
} catch let error {
parseError = error
}
DispatchQueue.main.async {
if tunnelConfiguration == nil {
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
if parseError != nil {
if let parseError = parseError as? WireGuardAppError {
lastFileImportErrorText = parseError.alertText
} else {
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
}
}
configs.append(tunnelConfiguration)
dispatchGroup.leave()

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
@ -109,9 +109,9 @@ class TunnelViewModel {
scratchpad[field] = stringValue
}
if field == .privateKey {
if stringValue.count == TunnelViewModel.keyLengthInBase64, let privateKey = Data(base64Key: stringValue), privateKey.count == TunnelConfiguration.keyLength {
let publicKey = Curve25519.generatePublicKey(fromPrivateKey: privateKey).base64Key() ?? ""
scratchpad[.publicKey] = publicKey
if stringValue.count == TunnelViewModel.keyLengthInBase64,
let privateKey = PrivateKey(base64Key: stringValue) {
scratchpad[.publicKey] = privateKey.publicKey.base64Key
} else {
scratchpad.removeValue(forKey: .publicKey)
}
@ -128,8 +128,8 @@ class TunnelViewModel {
private static func createScratchPad(from config: InterfaceConfiguration, name: String) -> [InterfaceField: String] {
var scratchpad = [InterfaceField: String]()
scratchpad[.name] = name
scratchpad[.privateKey] = config.privateKey.base64Key() ?? ""
scratchpad[.publicKey] = config.publicKey.base64Key() ?? ""
scratchpad[.privateKey] = config.privateKey.base64Key
scratchpad[.publicKey] = config.privateKey.publicKey.base64Key
if !config.addresses.isEmpty {
scratchpad[.addresses] = config.addresses.map { $0.stringRepresentation }.joined(separator: ", ")
}
@ -139,8 +139,10 @@ class TunnelViewModel {
if let mtu = config.mtu {
scratchpad[.mtu] = String(mtu)
}
if !config.dns.isEmpty {
scratchpad[.dns] = config.dns.map { $0.stringRepresentation }.joined(separator: ", ")
if !config.dns.isEmpty || !config.dnsSearch.isEmpty {
var dns = config.dns.map { $0.stringRepresentation }
dns.append(contentsOf: config.dnsSearch)
scratchpad[.dns] = dns.joined(separator: ", ")
}
return scratchpad
}
@ -158,7 +160,7 @@ class TunnelViewModel {
fieldsWithError.insert(.privateKey)
return .error(tr("alertInvalidInterfaceMessagePrivateKeyRequired"))
}
guard let privateKey = Data(base64Key: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else {
guard let privateKey = PrivateKey(base64Key: privateKeyString) else {
fieldsWithError.insert(.privateKey)
return .error(tr("alertInvalidInterfaceMessagePrivateKeyInvalid"))
}
@ -194,15 +196,16 @@ class TunnelViewModel {
}
if let dnsString = scratchpad[.dns] {
var dnsServers = [DNSServer]()
var dnsSearch = [String]()
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
if let dnsServer = DNSServer(from: dnsServerString) {
dnsServers.append(dnsServer)
} else {
fieldsWithError.insert(.dns)
errorMessages.append(tr("alertInvalidInterfaceMessageDNSInvalid"))
dnsSearch.append(dnsServerString)
}
}
config.dns = dnsServers
config.dnsSearch = dnsSearch
}
guard errorMessages.isEmpty else { return .error(errorMessages.first!) }
@ -251,12 +254,12 @@ class TunnelViewModel {
var scratchpad = [PeerField: String]()
var fieldsWithError = Set<PeerField>()
var validatedConfiguration: PeerConfiguration?
var publicKey: Data? {
var publicKey: PublicKey? {
if let validatedConfiguration = validatedConfiguration {
return validatedConfiguration.publicKey
}
if let scratchPadPublicKey = scratchpad[.publicKey] {
return Data(base64Key: scratchPadPublicKey)
return PublicKey(base64Key: scratchPadPublicKey)
}
return nil
}
@ -301,10 +304,8 @@ class TunnelViewModel {
private static func createScratchPad(from config: PeerConfiguration) -> [PeerField: String] {
var scratchpad = [PeerField: String]()
if let publicKey = config.publicKey.base64Key() {
scratchpad[.publicKey] = publicKey
}
if let preSharedKey = config.preSharedKey?.base64Key() {
scratchpad[.publicKey] = config.publicKey.base64Key
if let preSharedKey = config.preSharedKey?.base64Key {
scratchpad[.preSharedKey] = preSharedKey
}
if !config.allowedIPs.isEmpty {
@ -337,14 +338,14 @@ class TunnelViewModel {
fieldsWithError.insert(.publicKey)
return .error(tr("alertInvalidPeerMessagePublicKeyRequired"))
}
guard let publicKey = Data(base64Key: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else {
guard let publicKey = PublicKey(base64Key: publicKeyString) else {
fieldsWithError.insert(.publicKey)
return .error(tr("alertInvalidPeerMessagePublicKeyInvalid"))
}
var config = PeerConfiguration(publicKey: publicKey)
var errorMessages = [String]()
if let preSharedKeyString = scratchpad[.preSharedKey] {
if let preSharedKey = Data(base64Key: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength {
if let preSharedKey = PreSharedKey(base64Key: preSharedKeyString) {
config.preSharedKey = preSharedKey
} else {
fieldsWithError.insert(.preSharedKey)
@ -559,7 +560,7 @@ class TunnelViewModel {
}
let peerPublicKeysArray = peerConfigurations.map { $0.publicKey }
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray)
if peerPublicKeysArray.count != peerPublicKeysSet.count {
return .error(tr("alertInvalidPeerMessagePublicKeyDuplicated"))
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
import os.log
@ -9,12 +9,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var mainVC: MainViewController?
var isLaunchedForSpecificAction = false
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
if let launchOptions = launchOptions {
if launchOptions[.url] != nil || launchOptions[.shortcutItem] != nil {
isLaunchedForSpecificAction = true
}
}
let window = UIWindow(frame: UIScreen.main.bounds)
window.backgroundColor = .white
self.window = window
let mainVC = MainViewController()
@ -27,16 +33,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
_ = FileManager.deleteFile(at: url)
}
mainVC?.importFromDisposableFile(url: url)
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
mainVC?.refreshTunnelConnectionStatuses()
}
func applicationWillResignActive(_ application: UIApplication) {
guard let allTunnelNames = mainVC?.allTunnelNames() else { return }
application.shortcutItems = QuickActionItem.createItems(allTunnelNames: allTunnelNames)
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
guard shortcutItem.type == QuickActionItem.type else {
completionHandler(false)
return
}
let tunnelName = shortcutItem.localizedTitle
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: true)
completionHandler(true)
}
}
extension AppDelegate {
@ -45,7 +63,7 @@ extension AppDelegate {
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
return !self.isLaunchedForSpecificAction
}
func application(_ application: UIApplication, viewControllerWithRestorationIdentifierPath identifierComponents: [String], coder: NSCoder) -> UIViewController? {
@ -58,7 +76,7 @@ extension AppDelegate {
}
} else {
// Show it when tunnelsManager is available
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false)
mainVC?.showTunnelDetailForTunnel(named: tunnelName, animated: false, shouldToggleStatus: false)
}
}
return nil

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
import os.log

View File

@ -64,6 +64,8 @@
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleDisplayName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>

View File

@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
class QuickActionItem: UIApplicationShortcutItem {
static let type = "WireGuardTunnelActivateAndShow"
init(tunnelName: String) {
super.init(type: QuickActionItem.type, localizedTitle: tunnelName, localizedSubtitle: nil, icon: nil, userInfo: nil)
}
static func createItems(allTunnelNames: [String]) -> [QuickActionItem] {
let numberOfItems = 10
// Currently, only 4 items shown by iOS, but that can increase in the future.
// iOS will discard additional items we give it.
var tunnelNames = RecentTunnelsTracker.recentlyActivatedTunnelNames(limit: numberOfItems)
let numberOfSlotsRemaining = numberOfItems - tunnelNames.count
if numberOfSlotsRemaining > 0 {
let moreTunnels = allTunnelNames.filter { !tunnelNames.contains($0) }.prefix(numberOfSlotsRemaining)
tunnelNames.append(contentsOf: moreTunnels)
}
return tunnelNames.map { QuickActionItem(tunnelName: $0) }
}
}

View File

@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Foundation
class RecentTunnelsTracker {
private static let keyRecentlyActivatedTunnelNames = "recentlyActivatedTunnelNames"
private static let maxNumberOfTunnels = 10
private static var userDefaults: UserDefaults? {
guard let appGroupId = FileManager.appGroupId else {
wg_log(.error, staticMessage: "Cannot obtain app group ID from bundle for tracking recently used tunnels")
return nil
}
guard let userDefaults = UserDefaults(suiteName: appGroupId) else {
wg_log(.error, staticMessage: "Cannot obtain shared user defaults for tracking recently used tunnels")
return nil
}
return userDefaults
}
static func handleTunnelActivated(tunnelName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
recentTunnels.remove(at: existingIndex)
}
recentTunnels.insert(tunnelName, at: 0)
if recentTunnels.count > maxNumberOfTunnels {
recentTunnels.removeLast(recentTunnels.count - maxNumberOfTunnels)
}
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
static func handleTunnelRemoved(tunnelName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: tunnelName) {
recentTunnels.remove(at: existingIndex)
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func handleTunnelRenamed(oldName: String, newName: String) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if let existingIndex = recentTunnels.firstIndex(of: oldName) {
recentTunnels[existingIndex] = newName
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func cleanupTunnels(except tunnelNamesToKeep: Set<String>) {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
let oldCount = recentTunnels.count
recentTunnels.removeAll { !tunnelNamesToKeep.contains($0) }
if oldCount != recentTunnels.count {
userDefaults.set(recentTunnels, forKey: keyRecentlyActivatedTunnelNames)
}
}
static func recentlyActivatedTunnelNames(limit: Int) -> [String] {
guard let userDefaults = RecentTunnelsTracker.userDefaults else { return [] }
var recentTunnels = userDefaults.stringArray(forKey: keyRecentlyActivatedTunnelNames) ?? []
if limit < recentTunnels.count {
recentTunnels.removeLast(recentTunnels.count - limit)
}
return recentTunnels
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -9,8 +9,8 @@ class ButtonCell: UITableViewCell {
set(value) { button.setTitle(value, for: .normal) }
}
var hasDestructiveAction: Bool {
get { return button.tintColor == .red }
set(value) { button.tintColor = value ? .red : buttonStandardTintColor }
get { return button.tintColor == .systemRed }
set(value) { button.tintColor = value ? .systemRed : buttonStandardTintColor }
}
var onTapped: (() -> Void)?

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -9,7 +9,11 @@ class KeyValueCell: UITableViewCell {
let keyLabel = UILabel()
keyLabel.font = UIFont.preferredFont(forTextStyle: .body)
keyLabel.adjustsFontForContentSizeCategory = true
keyLabel.textColor = .black
if #available(iOS 13.0, *) {
keyLabel.textColor = .label
} else {
keyLabel.textColor = .black
}
keyLabel.textAlignment = .left
return keyLabel
}()
@ -31,7 +35,11 @@ class KeyValueCell: UITableViewCell {
valueTextField.autocapitalizationType = .none
valueTextField.autocorrectionType = .no
valueTextField.spellCheckingType = .no
valueTextField.textColor = .gray
if #available(iOS 13.0, *) {
valueTextField.textColor = .secondaryLabel
} else {
valueTextField.textColor = .gray
}
return valueTextField
}()
@ -56,10 +64,18 @@ class KeyValueCell: UITableViewCell {
var isValueValid = true {
didSet {
if isValueValid {
keyLabel.textColor = .black
if #available(iOS 13.0, *) {
if isValueValid {
keyLabel.textColor = .label
} else {
keyLabel.textColor = .systemRed
}
} else {
keyLabel.textColor = .red
if isValueValid {
keyLabel.textColor = .black
} else {
keyLabel.textColor = .red
}
}
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -16,7 +16,11 @@ class SwitchCell: UITableViewCell {
get { return switchView.isEnabled }
set(value) {
switchView.isEnabled = value
textLabel?.textColor = value ? .black : .gray
if #available(iOS 13.0, *) {
textLabel?.textColor = value ? .label : .secondaryLabel
} else {
textLabel?.textColor = value ? .black : .gray
}
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -28,7 +28,11 @@ class TextCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
message = ""
setTextColor(.black)
if #available(iOS 13.0, *) {
setTextColor(.label)
} else {
setTextColor(.black)
}
setTextAlignment(.left)
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -30,7 +30,11 @@ class TunnelEditEditableKeyValueCell: TunnelEditKeyValueCell {
super.init(style: style, reuseIdentifier: reuseIdentifier)
copyableGesture = false
valueTextField.textColor = .black
if #available(iOS 13.0, *) {
valueTextField.textColor = .label
} else {
valueTextField.textColor = .black
}
valueTextField.isEnabled = true
valueLabelScrollView.isScrollEnabled = false
valueTextField.widthAnchor.constraint(equalTo: valueLabelScrollView.widthAnchor).isActive = true

View File

@ -1,20 +1,20 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
class TunnelListCell: UITableViewCell {
var tunnel: TunnelContainer? {
didSet(value) {
didSet {
// Bind to the tunnel's name
nameLabel.text = tunnel?.name ?? ""
nameObservationToken = tunnel?.observe(\.name) { [weak self] tunnel, _ in
self?.nameLabel.text = tunnel.name
}
// Bind to the tunnel's status
update(from: tunnel?.status)
update(from: tunnel?.status, animated: false)
statusObservationToken = tunnel?.observe(\.status) { [weak self] tunnel, _ in
self?.update(from: tunnel.status)
self?.update(from: tunnel.status, animated: true)
}
}
}
@ -29,88 +29,88 @@ class TunnelListCell: UITableViewCell {
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
let busyIndicator: UIActivityIndicatorView
if #available(iOS 13.0, *) {
busyIndicator = UIActivityIndicatorView(style: .medium)
} else {
busyIndicator = UIActivityIndicatorView(style: .gray)
}
busyIndicator.hidesWhenStopped = true
return busyIndicator
}()
let statusSwitch = UISwitch()
private var statusObservationToken: AnyObject?
private var nameObservationToken: AnyObject?
private var statusObservationToken: NSKeyValueObservation?
private var nameObservationToken: NSKeyValueObservation?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(statusSwitch)
statusSwitch.translatesAutoresizingMaskIntoConstraints = false
accessoryType = .disclosureIndicator
for subview in [statusSwitch, busyIndicator, nameLabel] {
subview.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(subview)
}
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
let nameLabelBottomConstraint =
contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
nameLabelBottomConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
statusSwitch.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
contentView.trailingAnchor.constraint(equalTo: statusSwitch.trailingAnchor)
])
statusSwitch.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.trailingAnchor, multiplier: 1),
contentView.addSubview(busyIndicator)
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
statusSwitch.leadingAnchor.constraint(equalToSystemSpacingAfter: busyIndicator.trailingAnchor, multiplier: 1)
])
contentView.addSubview(nameLabel)
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: nameLabel.bottomAnchor, multiplier: 1)
bottomAnchorConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
nameLabelBottomConstraint,
nameLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
busyIndicator.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1),
bottomAnchorConstraint
])
accessoryType = .disclosureIndicator
busyIndicator.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
busyIndicator.leadingAnchor.constraint(equalToSystemSpacingAfter: nameLabel.trailingAnchor, multiplier: 1)
])
statusSwitch.addTarget(self, action: #selector(switchToggled), for: .valueChanged)
}
@objc func switchToggled() {
onSwitchToggled?(statusSwitch.isOn)
}
private func update(from status: TunnelStatus?) {
guard let status = status else {
reset()
return
}
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak statusSwitch, weak busyIndicator] in
guard let statusSwitch = statusSwitch, let busyIndicator = busyIndicator else { return }
statusSwitch.isOn = !(status == .deactivating || status == .inactive)
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
if status == .inactive || status == .active {
busyIndicator.stopAnimating()
} else {
busyIndicator.startAnimating()
}
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
reset(animated: false)
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
statusSwitch.isEnabled = !editing
}
private func reset() {
statusSwitch.isOn = false
@objc private func switchToggled() {
onSwitchToggled?(statusSwitch.isOn)
}
private func update(from status: TunnelStatus?, animated: Bool) {
guard let status = status else {
reset(animated: animated)
return
}
statusSwitch.setOn(!(status == .deactivating || status == .inactive), animated: animated)
statusSwitch.isUserInteractionEnabled = (status == .inactive || status == .active)
if status == .inactive || status == .active {
busyIndicator.stopAnimating()
} else {
busyIndicator.startAnimating()
}
}
private func reset(animated: Bool) {
statusSwitch.setOn(false, animated: animated)
statusSwitch.isUserInteractionEnabled = false
busyIndicator.stopAnimating()
}
override func prepareForReuse() {
super.prepareForReuse()
reset()
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -15,18 +15,37 @@ class LogViewController: UIViewController {
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
if #available(iOS 13.0, *) {
let busyIndicator = UIActivityIndicatorView(style: .medium)
busyIndicator.hidesWhenStopped = true
return busyIndicator
} else {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
}
}()
let paragraphStyle: NSParagraphStyle = {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.setParagraphStyle(NSParagraphStyle.default)
paragraphStyle.lineHeightMultiple = 1.2
return paragraphStyle
}()
var isNextLineHighlighted = false
var logViewHelper: LogViewHelper?
var isFetchingLogEntries = false
private var updateLogEntriesTimer: Timer?
override func loadView() {
view = UIView()
view.backgroundColor = .white
if #available(iOS 13.0, *) {
view.backgroundColor = .systemBackground
} else {
view.backgroundColor = .white
}
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
@ -68,9 +87,26 @@ class LogViewController: UIViewController {
}
guard !fetchedLogEntries.isEmpty else { return }
let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
let text = fetchedLogEntries.reduce("") { $0 + $1.text() + "\n" }
let font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let richText = NSAttributedString(string: text, attributes: [.font: font])
let richText = NSMutableAttributedString()
let bodyFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
let captionFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.caption1)
for logEntry in fetchedLogEntries {
var bgColor: UIColor
var fgColor: UIColor
if #available(iOS 13.0, *) {
bgColor = self.isNextLineHighlighted ? .systemGray3 : .systemBackground
fgColor = .label
} else {
bgColor = self.isNextLineHighlighted ? UIColor(white: 0.88, alpha: 1.0) : UIColor.white
fgColor = .black
}
let timestampText = NSAttributedString(string: logEntry.timestamp + "\n", attributes: [.font: captionFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
let messageText = NSAttributedString(string: logEntry.message + "\n", attributes: [.font: bodyFont, .backgroundColor: bgColor, .foregroundColor: fgColor, .paragraphStyle: self.paragraphStyle])
richText.append(timestampText)
richText.append(messageText)
self.isNextLineHighlighted.toggle()
}
self.textView.textStorage.append(richText)
if isScrolledToEnd {
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -11,7 +11,11 @@ class MainViewController: UISplitViewController {
init() {
let detailVC = UIViewController()
detailVC.view.backgroundColor = .white
if #available(iOS 13.0, *) {
detailVC.view.backgroundColor = .systemBackground
} else {
detailVC.view.backgroundColor = .white
}
let detailNC = UINavigationController(rootViewController: detailVC)
let masterVC = TunnelsListTableViewController()
@ -42,22 +46,25 @@ class MainViewController: UISplitViewController {
TunnelsManager.create { [weak self] result in
guard let self = self else { return }
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
return
case .success(let tunnelsManager):
self.tunnelsManager = tunnelsManager
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
tunnelsManager.activationDelegate = self
self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil
}
let tunnelsManager: TunnelsManager = result.value!
self.tunnelsManager = tunnelsManager
self.tunnelsListVC?.setTunnelsManager(tunnelsManager: tunnelsManager)
tunnelsManager.activationDelegate = self
self.onTunnelsManagerReady?(tunnelsManager)
self.onTunnelsManagerReady = nil
}
}
func allTunnelNames() -> [String]? {
guard let tunnelsManager = self.tunnelsManager else { return nil }
return tunnelsManager.mapTunnels { $0.name }
}
}
extension MainViewController: TunnelsManagerActivationDelegate {
@ -85,19 +92,17 @@ extension MainViewController {
}
}
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool) {
func showTunnelDetailForTunnel(named tunnelName: String, animated: Bool, shouldToggleStatus: Bool) {
let showTunnelDetailBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
guard let self = self else { return }
guard let tunnelsListVC = self.tunnelsListVC else { return }
if let tunnel = tunnelsManager.tunnel(named: tunnelName) {
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
if let self = self {
if animated {
self.showDetailViewController(tunnelDetailNC, sender: self)
} else {
UIView.performWithoutAnimation {
self.showDetailViewController(tunnelDetailNC, sender: self)
}
tunnelsListVC.showTunnelDetail(for: tunnel, animated: false)
if shouldToggleStatus {
if tunnel.status == .inactive {
tunnelsManager.startActivation(of: tunnel)
} else if tunnel.status == .active {
tunnelsManager.startDeactivation(of: tunnel)
}
}
}
@ -108,6 +113,19 @@ extension MainViewController {
onTunnelsManagerReady = showTunnelDetailBlock
}
}
func importFromDisposableFile(url: URL) {
let importFromFileBlock: (TunnelsManager) -> Void = { [weak self] tunnelsManager in
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self) {
_ = FileManager.deleteFile(at: url)
}
}
if let tunnelsManager = tunnelsManager {
importFromFileBlock(tunnelsManager)
} else {
onTunnelsManagerReady = importFromFileBlock
}
}
}
extension MainViewController: UISplitViewControllerDelegate {

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import AVFoundation
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
import SystemConfiguration.CaptiveNetwork
@ -176,7 +176,11 @@ extension SSIDOptionEditTableViewController {
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = tr("tunnelOnDemandNoSSIDs")
cell.setTextColor(.gray)
if #available(iOS 13.0, *) {
cell.setTextColor(.secondaryLabel)
} else {
cell.setTextColor(.gray)
}
cell.setTextAlignment(.center)
return cell
}
@ -266,6 +270,10 @@ extension SSIDOptionEditTableViewController {
case .ssidOption:
let previousOption = selectedOption
selectedOption = ssidOptionFields[indexPath.row]
guard previousOption != selectedOption else {
tableView.deselectRow(at: indexPath, animated: true)
return
}
loadSections()
if previousOption == .anySSID {
let indexSet = IndexSet(1 ... 2)

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
import os.log
@ -11,6 +11,7 @@ class SettingsTableViewController: UITableViewController {
case goBackendVersion
case exportZipArchive
case viewLog
case donateLink
var localizedUIString: String {
switch self {
@ -18,12 +19,13 @@ class SettingsTableViewController: UITableViewController {
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
case .exportZipArchive: return tr("settingsExportZipButtonTitle")
case .viewLog: return tr("settingsViewLogButtonTitle")
case .donateLink: return tr("donateLink")
}
}
}
let settingsFieldsBySection: [[SettingsFields]] = [
[.iosAppVersion, .goBackendVersion],
[.iosAppVersion, .goBackendVersion, .donateLink],
[.exportZipArchive],
[.viewLog]
]
@ -144,8 +146,8 @@ extension SettingsTableViewController {
cell.copyableGesture = false
cell.key = field.localizedUIString
if field == .iosAppVersion {
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
var appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown version"
if let appBuild = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String {
appVersion += " (\(appBuild))"
}
cell.value = appVersion
@ -160,14 +162,23 @@ extension SettingsTableViewController {
self?.exportConfigurationsAsZipFile(sourceView: cell.button)
}
return cell
} else {
assert(field == .viewLog)
} else if field == .viewLog {
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
cell.buttonText = field.localizedUIString
cell.onTapped = { [weak self] in
self?.presentLogView()
}
return cell
} else if field == .donateLink {
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
cell.buttonText = field.localizedUIString
cell.onTapped = {
if let url = URL(string: "https://www.wireguard.com/donations/"), UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:])
}
}
return cell
}
fatalError()
}
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -120,7 +120,7 @@ class TunnelDetailTableViewController: UITableViewController {
let editVC = TunnelEditTableViewController(tunnelsManager: self.tunnelsManager, tunnel: self.tunnel)
editVC.delegate = self
let editNC = UINavigationController(rootViewController: editVC)
editNC.modalPresentationStyle = .formSheet
editNC.modalPresentationStyle = .fullScreen
self.present(editNC, animated: true)
}
}
@ -152,8 +152,8 @@ class TunnelDetailTableViewController: UITableViewController {
}
}!
let firstPeerSectionIndex = interfaceSectionIndex + 1
var interfaceFieldIsVisible = self.interfaceFieldIsVisible
var peerFieldIsVisible = self.peerFieldIsVisible
let interfaceFieldIsVisible = self.interfaceFieldIsVisible
let peerFieldIsVisible = self.peerFieldIsVisible
func handleSectionFieldsModified<T>(fields: [T], fieldIsVisible: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
for (index, field) in fields.enumerated() {
@ -343,10 +343,8 @@ extension TunnelDetailTableViewController {
text = tr("tunnelStatusWaiting")
}
cell.textLabel?.text = text
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak cell] in
cell?.switchView.isOn = !(status == .deactivating || status == .inactive)
cell?.switchView.isUserInteractionEnabled = (status == .inactive || status == .active)
}
cell.switchView.isOn = !(status == .deactivating || status == .inactive)
cell.switchView.isUserInteractionEnabled = (status == .inactive || status == .active)
cell.isEnabled = status == .active || status == .inactive
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
@ -127,10 +127,10 @@ class TunnelEditTableViewController: UITableViewController {
} else {
// We're adding a new tunnel
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: self)
} else {
let tunnel: TunnelContainer = result.value!
case .success(let tunnel):
self?.dismiss(animated: true, completion: nil)
self?.delegate?.tunnelSaved(tunnel: tunnel)
}
@ -214,7 +214,7 @@ extension TunnelEditTableViewController {
cell.onTapped = { [weak self] in
guard let self = self else { return }
self.tunnelViewModel.interfaceData[.privateKey] = Curve25519.generatePrivateKey().base64Key() ?? ""
self.tunnelViewModel.interfaceData[.privateKey] = PrivateKey().base64Key
if let privateKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .privateKey),
let publicKeyRow = self.interfaceFieldsBySection[indexPath.section].firstIndex(of: .publicKey) {
let privateKeyIndex = IndexPath(row: privateKeyRow, section: indexPath.section)
@ -266,7 +266,7 @@ extension TunnelEditTableViewController {
guard let self = self else { return }
let isAllowedIPsChanged = self.tunnelViewModel.updateDNSServersInAllowedIPsIfRequired(oldDNSServers: oldValue, newDNSServers: newValue)
if isAllowedIPsChanged {
let section = self.sections.firstIndex { if case .peer(_) = $0 { return true } else { return false } }
let section = self.sections.firstIndex { if case .peer = $0 { return true } else { return false } }
if let section = section, let row = self.peerFields.firstIndex(of: .allowedIPs) {
self.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import UIKit
import MobileCoreServices
@ -32,7 +32,12 @@ class TunnelsListTableViewController: UIViewController {
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
let busyIndicator: UIActivityIndicatorView
if #available(iOS 13.0, *) {
busyIndicator = UIActivityIndicatorView(style: .medium)
} else {
busyIndicator = UIActivityIndicatorView(style: .gray)
}
busyIndicator.hidesWhenStopped = true
return busyIndicator
}()
@ -46,7 +51,11 @@ class TunnelsListTableViewController: UIViewController {
override func loadView() {
view = UIView()
view.backgroundColor = .white
if #available(iOS 13.0, *) {
view.backgroundColor = .systemBackground
} else {
view.backgroundColor = .white
}
tableView.dataSource = self
tableView.delegate = self
@ -178,7 +187,7 @@ class TunnelsListTableViewController: UIViewController {
func presentViewControllerForTunnelCreation(tunnelsManager: TunnelsManager) {
let editVC = TunnelEditTableViewController(tunnelsManager: tunnelsManager)
let editNC = UINavigationController(rootViewController: editVC)
editNC.modalPresentationStyle = .formSheet
editNC.modalPresentationStyle = .fullScreen
present(editNC, animated: true)
}
@ -251,6 +260,24 @@ class TunnelsListTableViewController: UIViewController {
}
}
}
func showTunnelDetail(for tunnel: TunnelContainer, animated: Bool) {
guard let tunnelsManager = tunnelsManager else { return }
guard let splitViewController = splitViewController else { return }
guard let navController = navigationController else { return }
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
if splitViewController.isCollapsed && navController.viewControllers.count > 1 {
navController.setViewControllers([self, tunnelDetailNC], animated: animated)
} else {
splitViewController.showDetailViewController(tunnelDetailNC, sender: self, animated: animated)
}
detailDisplayedTunnel = tunnel
self.presentedViewController?.dismiss(animated: false, completion: nil)
}
}
extension TunnelsListTableViewController: UIDocumentPickerDelegate {
@ -264,9 +291,10 @@ extension TunnelsListTableViewController: QRScanViewControllerDelegate {
func addScannedQRCode(tunnelConfiguration: TunnelConfiguration, qrScanViewController: QRScanViewController,
completionHandler: (() -> Void)?) {
tunnelsManager?.add(tunnelConfiguration: tunnelConfiguration) { result in
if let error = result.error {
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: qrScanViewController, onDismissal: completionHandler)
} else {
case .success:
completionHandler?()
}
}
@ -308,12 +336,7 @@ extension TunnelsListTableViewController: UITableViewDelegate {
}
guard let tunnelsManager = tunnelsManager else { return }
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
tunnel: tunnel)
let tunnelDetailNC = UINavigationController(rootViewController: tunnelDetailVC)
tunnelDetailNC.restorationIdentifier = "DetailNC"
showDetailViewController(tunnelDetailNC, sender: self) // Shall get propagated up to the split-vc
detailDisplayedTunnel = tunnel
showTunnelDetail(for: tunnel, animated: true)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
@ -375,7 +398,11 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
(splitViewController.viewControllers[0] as? UINavigationController)?.popToRootViewController(animated: false)
} else {
let detailVC = UIViewController()
detailVC.view.backgroundColor = .white
if #available(iOS 13.0, *) {
detailVC.view.backgroundColor = .systemBackground
} else {
detailVC.view.backgroundColor = .white
}
let detailNC = UINavigationController(rootViewController: detailVC)
splitViewController.showDetailViewController(detailNC, sender: self)
}
@ -386,3 +413,15 @@ extension TunnelsListTableViewController: TunnelsManagerListDelegate {
}
}
}
extension UISplitViewController {
func showDetailViewController(_ viewController: UIViewController, sender: Any?, animated: Bool) {
if animated {
showDetailViewController(viewController, sender: sender)
} else {
UIView.performWithoutAnimation {
showDetailViewController(viewController, sender: sender)
}
}
}
}

View File

@ -0,0 +1,249 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Cocoa
import ServiceManagement
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var tunnelsManager: TunnelsManager?
var tunnelsTracker: TunnelsTracker?
var statusItemController: StatusItemController?
var manageTunnelsRootVC: ManageTunnelsRootViewController?
var manageTunnelsWindowObject: NSWindow?
var onAppDeactivation: (() -> Void)?
func applicationWillFinishLaunching(_ notification: Notification) {
// To workaround a possible AppKit bug that causes the main menu to become unresponsive sometimes
// (especially when launched through Xcode) if we call setActivationPolicy(.regular) in
// in applicationDidFinishLaunching, we set it to .prohibited here.
// Setting it to .regular would fix that problem too, but at this point, we don't know
// whether the app was launched at login or not, so we're not sure whether we should
// show the app icon in the dock or not.
NSApp.setActivationPolicy(.prohibited)
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
registerLoginItem(shouldLaunchAtLogin: true)
var isLaunchedAtLogin = false
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
isLaunchedAtLogin = LaunchedAtLoginDetector.isLaunchedAtLogin(openAppleEvent: appleEvent)
}
NSApp.mainMenu = MainMenu()
setDockIconAndMainMenuVisibility(isVisible: !isLaunchedAtLogin)
TunnelsManager.create { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
ErrorPresenter.showErrorAlert(error: error, from: nil)
case .success(let tunnelsManager):
let statusMenu = StatusMenu(tunnelsManager: tunnelsManager)
statusMenu.windowDelegate = self
let statusItemController = StatusItemController()
statusItemController.statusItem.menu = statusMenu
let tunnelsTracker = TunnelsTracker(tunnelsManager: tunnelsManager)
tunnelsTracker.statusMenu = statusMenu
tunnelsTracker.statusItemController = statusItemController
self.tunnelsManager = tunnelsManager
self.tunnelsTracker = tunnelsTracker
self.statusItemController = statusItemController
if !isLaunchedAtLogin {
self.showManageTunnelsWindow(completion: nil)
}
}
}
}
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows: Bool) -> Bool {
if let appleEvent = NSAppleEventManager.shared().currentAppleEvent {
if LaunchedAtLoginDetector.isReopenedByLoginItemHelper(reopenAppleEvent: appleEvent) {
return false
}
}
if hasVisibleWindows {
return true
}
showManageTunnelsWindow(completion: nil)
return false
}
@objc func confirmAndQuit() {
let alert = NSAlert()
alert.messageText = tr("macConfirmAndQuitAlertMessage")
if let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating {
alert.informativeText = tr(format: "macConfirmAndQuitInfoWithActiveTunnel (%@)", currentTunnel.name)
} else {
alert.informativeText = tr("macConfirmAndQuitAlertInfo")
}
alert.addButton(withTitle: tr("macConfirmAndQuitAlertCloseWindow"))
alert.addButton(withTitle: tr("macConfirmAndQuitAlertQuitWireGuard"))
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
manageWindow.orderFront(self)
alert.beginSheetModal(for: manageWindow) { response in
switch response {
case .alertFirstButtonReturn:
manageWindow.close()
case .alertSecondButtonReturn:
NSApp.terminate(nil)
default:
break
}
}
}
}
@objc func quit() {
if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
NSApp.activate(ignoringOtherApps: true)
manageWindow.orderFront(self)
return
}
registerLoginItem(shouldLaunchAtLogin: false)
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
NSApp.terminate(nil)
return
}
let alert = NSAlert()
alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
manageWindow.orderFront(self)
alert.beginSheetModal(for: manageWindow) { _ in
NSApp.terminate(nil)
}
} else {
alert.runModal()
NSApp.terminate(nil)
}
}
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
return .terminateNow
}
guard let appleEvent = NSAppleEventManager.shared().currentAppleEvent else {
return .terminateNow
}
guard MacAppStoreUpdateDetector.isUpdatingFromMacAppStore(quitAppleEvent: appleEvent) else {
return .terminateNow
}
let alert = NSAlert()
alert.messageText = tr("macAppStoreUpdatingAlertMessage")
if currentTunnel.isActivateOnDemandEnabled {
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithOnDemand (%@)", currentTunnel.name)
} else {
alert.informativeText = tr(format: "macAppStoreUpdatingAlertInfoWithoutOnDemand (%@)", currentTunnel.name)
}
NSApp.activate(ignoringOtherApps: true)
if let manageWindow = manageTunnelsWindowObject {
alert.beginSheetModal(for: manageWindow) { _ in }
} else {
alert.runModal()
}
return .terminateCancel
}
func applicationShouldTerminateAfterLastWindowClosed(_ application: NSApplication) -> Bool {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(200)) { [weak self] in
self?.setDockIconAndMainMenuVisibility(isVisible: false)
}
return false
}
private func setDockIconAndMainMenuVisibility(isVisible: Bool, completion: (() -> Void)? = nil) {
let currentActivationPolicy = NSApp.activationPolicy()
let newActivationPolicy: NSApplication.ActivationPolicy = isVisible ? .regular : .accessory
guard currentActivationPolicy != newActivationPolicy else {
if newActivationPolicy == .regular {
NSApp.activate(ignoringOtherApps: true)
}
completion?()
return
}
if newActivationPolicy == .regular && NSApp.isActive {
// To workaround a possible AppKit bug that causes the main menu to become unresponsive,
// we should deactivate the app first and then set the activation policy.
// NSApp.deactivate() doesn't always deactivate the app, so we instead use
// setActivationPolicy(.prohibited).
onAppDeactivation = {
NSApp.setActivationPolicy(.regular)
NSApp.activate(ignoringOtherApps: true)
completion?()
}
NSApp.setActivationPolicy(.prohibited)
} else {
NSApp.setActivationPolicy(newActivationPolicy)
if newActivationPolicy == .regular {
NSApp.activate(ignoringOtherApps: true)
}
completion?()
}
}
func applicationDidResignActive(_ notification: Notification) {
onAppDeactivation?()
onAppDeactivation = nil
}
}
extension AppDelegate {
@objc func aboutClicked() {
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
}
let appVersionString = [
tr(format: "macAppVersion (%@)", appVersion),
tr(format: "macGoBackendVersion (%@)", WIREGUARD_GO_VERSION)
].joined(separator: "\n")
NSApp.activate(ignoringOtherApps: true)
NSApp.orderFrontStandardAboutPanel(options: [
.applicationVersion: appVersionString,
.version: "",
.credits: ""
])
}
}
extension AppDelegate: StatusMenuWindowDelegate {
func showManageTunnelsWindow(completion: ((NSWindow?) -> Void)?) {
guard let tunnelsManager = tunnelsManager else {
completion?(nil)
return
}
if manageTunnelsWindowObject == nil {
manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
let window = NSWindow(contentViewController: manageTunnelsRootVC!)
window.title = tr("macWindowTitleManageTunnels")
window.setContentSize(NSSize(width: 800, height: 480))
window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size
manageTunnelsWindowObject = window
tunnelsTracker?.manageTunnelsRootVC = manageTunnelsRootVC
}
setDockIconAndMainMenuVisibility(isVisible: true) { [weak manageTunnelsWindowObject] in
manageTunnelsWindowObject?.makeKeyAndOrderFront(self)
completion?(manageTunnelsWindowObject)
}
}
}
@discardableResult
func registerLoginItem(shouldLaunchAtLogin: Bool) -> Bool {
let appId = Bundle.main.bundleIdentifier!
let helperBundleId = "\(appId).login-item-helper"
return SMLoginItemSetEnabled(helperBundleId as CFString, shouldLaunchAtLogin)
}

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2020 WireGuard LLC. All Rights Reserved.
import Cocoa
class Application: NSApplication {
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
override init() {
super.init()
appDelegate = AppDelegate() // Keep a strong reference to the app delegate
delegate = appDelegate // Set delegate before app.run() gets called in NSApplicationMain()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Some files were not shown because too many files have changed in this diff Show More