Compare commits

..

142 Commits

Author SHA1 Message Date
Jason A. Donenfeld 571349bb3d Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-12 10:32:06 +02:00
Roopesh Chander 98ebd55208 Log view: Don't use a global array to store log entries
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-10 17:57:36 +05:30
Roopesh Chander 83d0d34411 macOS: Log view: Stop updating the log once the log view is dismissed
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-10 15:42:39 +05:30
Jason A. Donenfeld 8c7c4b6792 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 10:49:48 +02:00
Jason A. Donenfeld 5b34f49166 wireguard-go-bridge: bump again for version file placement
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 10:43:24 +02:00
Roopesh Chander cef3957875 Swift 5 migration: Handle changes in Data's pointer interface
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-09 11:25:04 +05:30
Roopesh Chander d9e88c51bd Swift 5 migration: Fix switch warnings
We now get a warning when switching over enums from system
frameworks even when we handle all public cases because
there can be future cases that aren't handled.

When such a future case is introduced, we'll get a warning.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-09 11:25:04 +05:30
Jason A. Donenfeld db876647d6 wireguard-go-bridge: version bump to new tag
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-04-09 07:44:50 +02:00
Roopesh Chander 90eb45e287 Xcode: Move to Swift 5.0
No code changes were necessary

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-07 16:42:36 +05:30
Roopesh Chander 9f3d86723a macOS: Minor fix to export panel texts
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 17:59:42 +05:30
Roopesh Chander 11063d0f88 macOS: Tunnels list: Suppress alert buttons when removing tunnels is in progress
Also refactor the deletion alert into a separate helper class

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 17:53:41 +05:30
Roopesh Chander 557ee4390b TunnelsManager: When setting a config, also set isAvailable cache
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 00:26:06 +05:30
Roopesh Chander 9ce42d152d macOS: Tunnels list: Show the confirmation alert till removal completes
Fix tunnel selection during deletion

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-06 00:08:45 +05:30
Roopesh Chander 4c1b2e1258 TunnelsManager: Fix comparing tunnels with tunnelProviders in reload()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-05 13:43:08 +05:30
Roopesh Chander 3bd611aa7c TunnelsManager: Cache isTunnelConfigurationAvailableInKeychain
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-05 13:29:17 +05:30
Roopesh Chander adbe0b065e macOS: Attempt to remove keychain item only if verified
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-04 15:29:25 +05:30
Roopesh Chander 6015661beb macOS: Simplify reusing of the detail VC when applicable
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-04 12:24:32 +05:30
Roopesh Chander 6e6a6b88fb macOS: Hide other-user tunnels in the status menu
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-03 19:18:53 +05:30
Roopesh Chander 9690365dd4 macOS: Better handling of tunnels created by another user
Previously, the tunnels just got deleted.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-03 19:04:12 +05:30
Roopesh Chander 0299c3929e iOS: Log view: Make log text selectable
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-01 23:29:15 +05:30
Roopesh Chander 0043233872 macOS: Log view: Fix autoscroll to end of log
Looks like the tableview doesn't know how much to scroll to get to the
end when we use usesAutomaticRowHeights. So we wait for the tableview
to realize its frame has changed and then scroll to the bottom of the
frame explicitly.

Also, we keep track of whether the scroll view is scrolled to the end or
not every time scrolling happens, not just when we add log entries to
the table.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-04-01 23:07:48 +05:30
Roopesh Chander a74dd24578 macOS: Bring app to front before 'exiting with an active tunnel' alert
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-29 00:17:37 +05:30
Roopesh Chander dd9506ecee macOS: If a sheet is being shown, ignore quit and bring window to front
Otherwise, the 'exiting with an active tunnel' alert could get queued up
to be shown after the current sheet is dismissed.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-29 00:17:37 +05:30
Jason A. Donenfeld 0c2eb003a0 wireguard-go-bridge: update deps
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-28 15:55:53 +01:00
Roopesh Chander f83f159f97 macOS: Log view: No need to disable Close button
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 19:32:58 +05:30
Roopesh Chander 6175de0438 iOS: Ability to view the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 19:28:27 +05:30
Roopesh Chander bd61be52e6 iOS: Xcode: Minor project rearrangement
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 14:10:42 +05:30
Roopesh Chander 909f88be70 macOS: Ability to view the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-28 13:57:06 +05:30
Roopesh Chander b7c3bd0d8c Add LogViewHelper
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-27 17:55:52 +05:30
Roopesh Chander 4237ab4a6f macOS: Syntax highlighter: Free spans array
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 17:53:34 +05:30
Roopesh Chander 0fcaf6debb macOS: Hide exclude private IPs when PrivateKey / PublicKey is missing
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 16:00:45 +05:30
Roopesh Chander dbd5ea1ff0 macOS: Syntax highlighter: Swift can bridge c strings automatically
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 15:31:02 +05:30
Roopesh Chander 9afe230c10 macOS: On Add new, Exclude Private IPs should remain hidden
because there aren't any peers in the bootstrapped config.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-22 15:15:26 +05:30
Roopesh Chander 4bdfbb518e Xcode: iOS: Remove armv7 as 'Required device capabilities'
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-20 14:23:56 +05:30
Jason A. Donenfeld fbe101eabb macOS: Privacy notice is provided by system dialogs
So it really doesn't make sense to add our own. This causes several
popups when trying to add a tunnel, which is madness.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-20 04:24:23 +01:00
Jason A. Donenfeld cda3170970 macOS: Login item: Add a simple login item
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:15:38 -06:00
Jason A. Donenfeld a5e7c3906b Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:25:38 +01:00
Jason A. Donenfeld b21fdfed67 wireguard-go-bridge: do not use getdirentries64 on macos
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 21:23:46 +01:00
Roopesh Chander 5f8843e247 iOS: Delete confirmation prompt should be a question
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-19 21:23:46 +01:00
Roopesh Chander 7a3f65fd2f macOS: Add 'Deactivate' status menu item
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-19 21:23:46 +01:00
Jason A. Donenfeld dca0fb29f6 Version: CFBundleVersion must always increase for macOS app store
So we'll just start doing it like that, then.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-19 06:00:40 +01:00
Jason A. Donenfeld af9c800af8 Swiftlint: variable_name -> identifier_name
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 22:26:13 -06:00
Jason A. Donenfeld a9b925c69b Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 22:13:27 -06:00
Jason A. Donenfeld f93f9d62f4 macos: TunnelsList: set allowsEmptySelection after making initial selection
Otherwise we never get the event that the selection changed, so we don't
wind up showing anything in the details pane.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 22:13:27 -06:00
Roopesh Chander fc163fc9ff iOS: Consolidate all showConfirmationAlert()s into one place
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 14:54:05 -06:00
Roopesh Chander adc5a7cac2 iOS: Tunnels list: Ability to remove multiple tunnels at a time
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 14:54:05 -06:00
Roopesh Chander 0dd22ca45a iOS: Tunnel edit: Add missing enum values
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 14:54:05 -06:00
Roopesh Chander bca9fead5e macOS: ButtonedDetailViewController: Set min dimensions
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-19 01:28:52 +05:30
Jason A. Donenfeld 51822f722a ringlogger: document races
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 12:50:00 -06:00
Roopesh Chander 121d223229 macOS: Tunnels list: Double-click to activate / deactivate
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 12:03:41 +05:30
Roopesh Chander 439fb6bbac macOS: Tunnels list: Don't allow empty selection
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 12:03:41 +05:30
Roopesh Chander 9c8231dcf7 on-demand: macOS: Remove unused class ControlRow
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander 0440c4a33a on-demand: macOS: Integrate Ethernet and Wi-Fi controls in one row
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander 01be43aa7a on-demand: View model should account for isActivateOnDemandEnabled
This is needed to correctly handle NETunnelProviderManager's
isOnDemandEnabled property getting changed outside of the app.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander e29c6900e5 on-demand: macOS: Disable SSIDs field when adding a tunnel
It shouldn't be editable when the VPN prompt is shown.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander a334c25aff on-demand: iOS: Disable selection in SSID detail table view
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander f56b2ad968 on-demand: macOS: Remove unused class PopupRow
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander 503ac6c8a2 on-demand: macOS: Auto-complete SSIDs based on currently connected SSID
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander 5f30e021ef on-demand: iOS: Change wording for add-SSIDs rows
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander d748382fce on-demand: "Only selected SSIDs" -> "Only these SSIDs"
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:56 +01:00
Roopesh Chander 63299a2752 on-demand: macOS: Tunnel detail: List SSIDs
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander b7f8f74b56 on-demand: iOS: Only n SSIDs / Except m SSIDs
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 8e5a9215de on-demand: iOS: Show list of SSIDs in a separate screen
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 64925cab89 on-demand: iOS: SSIDs view: Always show the selected SSIDs section
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 062b4d4b16 on-demand: Remove ActivateOnDemandSetting type
The ActivateOnDemandOption type shall be used instead

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander d9bdc61fb9 on-demand: TunnelViewModel: Remove unused on-demand-related methods
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 0ae8d25134 on-demand: macOS: Tunnel detail: Show SSID info
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 574d8433b3 on-demand: iOS: Update on-demand info shown in tunnel edit view
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander bd339e2876 on-demand: ActivateOnDemandViewModel: Uniquify SSIDs list
And if SSIDs list is empty, fall back to .anySSID option

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander fff75adfe1 on-demand: macOS: Support SSIDs in on demand activation
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 01604dd8d1 on-demand: iOS: Tunnel detail: Show SSID info
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander bdeb89a9e5 on-demand: iOS: Add ability to add current SSID
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 36dc252512 on-demand: iOS: Xcode: Add ability to access current SSID
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 5941bf181c on-demand: iOS: Support for SSIDs
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 7a450089c0 on-demand: Introducing ActivateOnDemandViewModel
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 5d757982ba on-demand: Infrastructure for supporting SSID-based rules
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 3767a12983 on-demand: Simplify OS-specific code for interface type selection
Previously, the enum values themselves were different for iOS and macOS.
With this commit, the enum values are common, and only how they're handled
is specific to iOS and macOS.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 9795b0609a macOS: Localize tooltips
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander 0f98312d15 macOS: Tunnel detail: Make the Activate button part of the list view
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Roopesh Chander f81275812c macOS: Nullify observationToken on prepareForReuse()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-18 06:46:55 +01:00
Jason A. Donenfeld b2b5e0e379 TunnelName: sort correctly with numbers and capitals
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-18 06:46:55 +01:00
Jason A. Donenfeld a6f80135ef ringlogger: support mpsc for singlefile
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-17 08:51:27 +01:00
Roopesh Chander e23c221aff macOS: Tunnel detail: Activate / Deactivate is now a button
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-17 02:27:46 +05:30
Roopesh Chander 50bc994762 macOS: Tunnel detail: Show the status in the list view
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-17 02:27:46 +05:30
Roopesh Chander 3e05da4486 macOS: KeyValueImageRow class
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-17 02:27:46 +05:30
Jason A. Donenfeld c750f28c67 wireguard-go-bridge: update deps
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-12 10:45:14 +01:00
Jason A. Donenfeld f6c70500a7 wg-quick parser: trim \r as well
The influx of Windows users has already begun to infect our nice
project.

Reported-by: Cosku Bas <cosku.bas@gmail.com>
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-11 14:05:16 -06:00
Roopesh Chander 663923864c TunnelsManager: Don't restart if only on-demand setting has changed
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-11 13:20:21 +05:30
Roopesh Chander 9250780ffc macOS: Ability to remove multiple tunnels at a time
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-10 20:02:19 +05:30
Roopesh Chander 9bc17034dd TunnelsManager: Support for removing multiple tunnels at a time
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-10 19:43:27 +05:30
Roopesh Chander db6f0729c6 macOS: Generalize NoTunnelsDetailVC into a ButtonedDetailVC
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-10 19:22:33 +05:30
Jason A. Donenfeld 4503c11b0c wireguard-go-bridge: use system go installation
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-08 05:56:00 +01:00
Roopesh Chander fe4f8b666d Importing: Only the main thread shall access lastFileImportErrorText
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-05 16:11:57 +05:30
Roopesh Chander 90c0f7e92e Importing: Make use of lastError returned from TunnelsManager.addMultiple()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-05 16:11:41 +05:30
Roopesh Chander 3afcee04be TunnelsManager: addMultiple() should also return the last error
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-05 15:29:28 +05:30
Roopesh Chander 202e7a4890 Importing: Simplify TunnelImporter
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-03-04 14:13:49 +05:30
Jason A. Donenfeld d7b16ffb1f wireguard-go-bridge: use go modules
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-03-03 06:28:07 +01:00
Jason A. Donenfeld b1dabf5a00 wireguard-go-bridge: update to Go 1.12
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-27 06:24:56 +01:00
Roopesh Chander a389bd93cb Importing: macOS: Support importing of multiple files at a time
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-25 18:43:20 +05:30
Roopesh Chander b2a2110d8c Importing: Use case-insensitive comparison for zip extension
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-25 16:21:29 +05:30
Roopesh Chander 5ed28907ec iOS: Hack to restart active tunnel after adding a new tunnel
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-24 19:30:14 +05:30
Roopesh Chander ab6d714070 Importing: Show OS error when unable to open a .conf file
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-24 16:50:57 +05:30
Roopesh Chander d3df8734c2 macOS: Tunnel edit: Disable user interaction when OS VPN prompt is shown
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-24 16:06:37 +05:30
Roopesh Chander ea5996abe0 macOS: Tunnel edit: s/populateTextFields()/populateFields()/g;
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-24 15:14:35 +05:30
Roopesh Chander ce405f856e macOS: When programmatically selecting a tunnel, also scroll if required
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-22 18:18:53 +05:30
Roopesh Chander 98a967acc8 macOS: Replace NSSegmentedControl with NSPopUpButton and NSButton
Thereby avoiding the hacky way of showing the menus.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-22 17:59:41 +05:30
Roopesh Chander b01d09dfb5 Importing: Give a clearer error message on importing an invalid config
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-22 13:03:53 +05:30
Roopesh Chander 7a580e8941 macOS: Show 'quitting with active tunnel' only when appropriate
Not when logging off or when the machine's shutting down

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-22 13:03:53 +05:30
Roopesh Chander 39fb52a2e3 macOS: Fix removal of DNSes from AllowedIPs when DNS has changed
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-21 18:17:28 +05:30
Roopesh Chander 69a064d954 iOS: On changing DNS, update AllowedIPs with the current DNS servers
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-21 17:57:13 +05:30
Roopesh Chander eb684ef711 macOS: On saving, update AllowedIPs with the current DNS servers
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-21 17:57:13 +05:30
Roopesh Chander b0eff424f9 Importing: Better error message when .conf file is not readable
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-21 17:57:13 +05:30
Jason A. Donenfeld c195760b15 macOS: Specify crypto compliance
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-19 16:12:33 +01:00
Roopesh Chander ba3f0db92c TunnelViewModel: Remove DNS from AllowedIPs when unchecking 'Exclude private IPs'
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-16 19:57:31 +05:30
Roopesh Chander 5031a7db4c macOS: Exclude private IPs
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-16 18:25:17 +05:30
Roopesh Chander a355232e09 TunnelViewModel: Minor refactoring of exclude private IPs handling
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-16 17:35:33 +05:30
Jason A. Donenfeld 6f7214ff38 ConfTextStorage: lowercase only once
Also fix submodule regression.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-16 17:18:10 +05:30
Roopesh Chander 4c88f477a2 ConfTextStorage: Let's keep the AllowedIPs and DNS servers as strings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-16 01:20:11 +05:30
Roopesh Chander 2fb9d6af71 ConfTextStorage: Make fieldType an enum
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-16 00:26:49 +05:30
Jason A. Donenfeld 38ac66071c ConfTextStorage: keep track of single peer state for exclude private IPs
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-15 19:44:06 +01:00
Roopesh Chander 910fdfc321 macOS: Tunnel detail: Set min width/height
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-15 15:44:11 +05:30
Roopesh Chander c38a88988b macOS: Tunnels list: Use constant width for the table view
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-15 15:40:12 +05:30
Roopesh Chander cf51344444 .mobileconfig: fix lists
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-15 14:05:15 +05:30
Jason A. Donenfeld fda36b571d README: supports macOS
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-15 02:39:34 +01:00
Jason A. Donenfeld 1be3224d7a README: recursive cloning
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-15 02:37:02 +01:00
Jason A. Donenfeld ca088e9ddc README: Xcode has a lowercase 'c'
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-15 02:35:10 +01:00
Jason A. Donenfeld fcca2d4fec macOS: Show privacy notice on adding first tunnel
App store reviewers don't understand that this isn't a service.

Revert this as soon as they come to their senses.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-15 01:14:14 +01:00
Jason A. Donenfeld 58181a4d40 Version bump
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-14 23:07:06 +01:00
Jason A. Donenfeld cf816ede13 wireguard-go: bump for ARM64 ChaCha20
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-14 12:00:59 +01:00
Jason A. Donenfeld fbac282bdc .mobileconfig: fix formatting
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-13 14:31:02 +01:00
Jason A. Donenfeld 61f5e017c8 .mobileconfig: note keychain limitation
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
2019-02-13 14:04:54 +01:00
Roopesh Chander 4547e01283 Preshared key field in the detail view should just say 'enabled'
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-12 19:30:59 +05:30
Roopesh Chander 5792db22a6 Log migration of tunnel configuration
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-12 19:17:32 +05:30
Roopesh Chander 9d5aa1d8fa Document installing WireGuard tunnels using Configuration Profiles
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-12 19:14:53 +05:30
Roopesh Chander 6331b81b5d Migrate when we notice a new tunnel in reload()
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-12 17:43:40 +05:30
Roopesh Chander 77f929789c Don't migrate in asTunnelConfiguration()
It causes problems when installing a tunnel through a
Configuration Profile on macOS and activating it first through
Network Preferences.

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-12 17:37:27 +05:30
Roopesh Chander b5b72b309f Info.plist: Localize with InfoPlist.strings
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-11 16:08:55 +05:30
Roopesh Chander 966fa7909b macOS: Change keyboard shortcut for importing to Cmd+O
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-10 17:55:50 +05:30
Roopesh Chander 115059f2bb macOS: Adapt to the new applyConfiguration API
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-10 03:35:24 +05:30
Roopesh Chander e53c2d4d17 iOS: Rewrite applying runtime configuration
To make scrolling smoother while the fields are modified

Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-10 03:35:24 +05:30
Roopesh Chander 0a3a5ee900 Importing: Ignore case in matching file extensions inside zip files
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-09 19:42:50 +05:30
Roopesh Chander 7720307fc9 TunnelsManager: No need to access tunnelConfiguration on status change
Signed-off-by: Roopesh Chander <roop@roopc.net>
2019-02-09 19:42:50 +05:30
80 changed files with 4215 additions and 1201 deletions
-3
View File
@@ -1,3 +0,0 @@
[submodule "wireguard-go"]
path = wireguard-go
url = https://git.zx2c4.com/wireguard-go
+140
View File
@@ -0,0 +1,140 @@
# Installing WireGuard tunnels using Configuration Profiles
WireGuard configurations can be installed using Configuration Profiles
through .mobileconfig files.
### Top-level payload entries
A .mobileconfig file is a plist file in XML format. The top-level XML item is a top-level payload dictionary (dict). This payload dictionary should contain the following keys:
- `PayloadDisplayName` (string): The name of the configuration profile, visible when installing the profile
- `PayloadType` (string): Should be `Configuration`
- `PayloadVersion` (integer): Should be `1`
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the profile file.
If you install another .mobileconfig file with the same identifier, the new one
overwrites the old one.
- `PayloadUUID` (string): A randomly generated UUID for this payload
- `PayloadContent` (array): Should contain an array of payload dictionaries.
Each of these payload dictionaries can represent a WireGuard tunnel
configuration.
Here's an example .mobileconfig with the above fields filled in:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadDisplayName</key>
<string>WireGuard Demo Configuration Profile</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.your-org.wireguard.FCC9BF80-C540-44C1-B243-521FDD1B2905</string>
<key>PayloadUUID</key>
<string>F346AAF4-53A2-4FA1-ACA3-EEE74DBED029</string>
<key>PayloadContent</key>
<array>
<!-- An array of WireGuard configuration payload dictionaries -->
</array>
</dict>
</plist>
```
### WireGuard payload entries
Each WireGuard configuration payload dictionary should contain the following
keys:
- `PayloadDisplayName` (string): Should be `VPN`
- `PayloadType` (string): Should be `com.apple.vpn.managed`
- `PayloadVersion` (integer): Should be `1`
- `PayloadIdentifier` (string): A reverse-DNS style unique identifier for the WireGuard configuration profile.
- `PayloadUUID` (string): A randomly generated UUID for this payload
- `UserDefinedName` (string): The name of the WireGuard tunnel.
This name shall be used to represent the tunnel in the WireGuard app, and in the System UI for VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
- `VPNType` (string): Should be `VPN`
- `VPNSubType` (string): Should be set as the bundle identifier of the WireGuard app.
- iOS: `com.wireguard.ios`
- macOS: `com.wireguard.macos`
- `VendorConfig` (dict): Should be a dictionary with the following key:
- `WgQuickConfig` (string): Should be a WireGuard configuration in [wg-quick(8)] / [wg(8)] format.
The keys 'FwMark', 'Table', 'PreUp', 'PostUp', 'PreDown', 'PostDown' and 'SaveConfig' are not supported.
- `VPN` (dict): Should be a dictionary with the following keys:
- `RemoteAddress` (string): A non-empty string.
This string is displayed as the server name in the System UI for
VPNs (Settings > VPN on iOS, System Preferences > Network on macOS).
- `AuthenticationMethod` (string): Should be `Password`
Here's an example WireGuard configuration payload dictionary:
```xml
<!-- A WireGuard configuration payload dictionary -->
<dict>
<key>PayloadDisplayName</key>
<string>VPN</string>
<key>PayloadType</key>
<string>com.apple.vpn.managed</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadIdentifier</key>
<string>com.your-org.wireguard.demo-profile-1.demo-tunnel</string>
<key>PayloadUUID</key>
<string>44CDFE9F-4DC7-472A-956F-61C68055117C</string>
<key>UserDefinedName</key>
<string>Demo from MobileConfig file</string>
<key>VPNType</key>
<string>VPN</string>
<key>VPNSubType</key>
<string>com.wireguard.ios</string>
<key>VendorConfig</key>
<dict>
<key>WgQuickConfig</key>
<string>
[Interface]
PrivateKey = mInDaw06K0NgfULRObHJjkWD3ahUC8XC1tVjIf6W+Vo=
Address = 10.10.1.0/24
DNS = 1.1.1.1, 1.0.0.1
[Peer]
PublicKey = JRI8Xc0zKP9kXk8qP84NdUQA04h6DLfFbwJn4g+/PFs=
Endpoint = demo.wireguard.com:12912
AllowedIPs = 0.0.0.0/0
</string>
</dict>
<key>VPN</key>
<dict>
<key>RemoteAddress</key>
<string>demo.wireguard.com:12912</string>
<key>AuthenticationMethod</key>
<string>Password</string>
</dict>
</dict>
```
### Caveats
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
+7 -14
View File
@@ -1,21 +1,14 @@
# [WireGuard](https://www.wireguard.com/) for iOS # [WireGuard](https://www.wireguard.com/) for iOS and macOS
## Building ## Building
- Clone this repo: - Clone this repo recursively:
``` ```
$ git clone https://git.zx2c4.com/wireguard-ios $ git clone --recursive https://git.zx2c4.com/wireguard-ios
$ cd wireguard-ios $ cd wireguard-ios
``` ```
- Init and update submodule:
```
$ git submodule init
$ git submodule update
```
- Rename and populate developer team ID file: - Rename and populate developer team ID file:
``` ```
@@ -23,19 +16,19 @@ $ cp WireGuard/WireGuard/Config/Developer.xcconfig.template WireGuard/WireGuard/
$ vim WireGuard/WireGuard/Config/Developer.xcconfig $ vim WireGuard/WireGuard/Config/Developer.xcconfig
``` ```
- Install swiftlint: - Install swiftlint and go:
``` ```
$ brew install swiftlint $ brew install swiftlint go
``` ```
- Open project in XCode: - Open project in Xcode:
``` ```
$ open ./WireGuard/WireGuard.xcodeproj $ open ./WireGuard/WireGuard.xcodeproj
``` ```
- Flip switches, press buttons, and make whirling noises until XCode builds it. - Flip switches, press buttons, and make whirling noises until Xcode builds it.
## MIT License ## MIT License
+1 -1
View File
@@ -20,6 +20,6 @@ opt_in_rules:
- unneeded_parentheses_in_closure_argument - unneeded_parentheses_in_closure_argument
- unused_import - unused_import
- trailing_closure - trailing_closure
variable_name: identifier_name:
min_length: min_length:
warning: 0 warning: 0
+1 -9
View File
@@ -27,7 +27,7 @@ extension FileManager {
return sharedFolderURL return sharedFolderURL
} }
static var networkExtensionLogFileURL: URL? { static var logFileURL: URL? {
return sharedFolderURL?.appendingPathComponent("tunnel-log.bin") return sharedFolderURL?.appendingPathComponent("tunnel-log.bin")
} }
@@ -35,14 +35,6 @@ extension FileManager {
return sharedFolderURL?.appendingPathComponent("last-error.txt") return sharedFolderURL?.appendingPathComponent("last-error.txt")
} }
static var appLogFileURL: URL? {
guard let documentDirURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
wg_log(.error, message: "Cannot obtain app documents folder URL")
return nil
}
return documentDirURL.appendingPathComponent("app-log.bin")
}
static func deleteFile(at url: URL) -> Bool { static func deleteFile(at url: URL) -> Bool {
do { do {
try FileManager.default.removeItem(at: url) try FileManager.default.removeItem(at: url)
+8 -10
View File
@@ -12,10 +12,12 @@ public class Logger {
static var global: Logger? static var global: Logger?
var log: OpaquePointer var log: OpaquePointer
var tag: String
init(withFilePath filePath: String) throws { init(tagged tag: String, withFilePath filePath: String) throws {
guard let log = open_log(filePath) else { throw LoggerError.openFailure } guard let log = open_log(filePath) else { throw LoggerError.openFailure }
self.log = log self.log = log
self.tag = tag
} }
deinit { deinit {
@@ -23,17 +25,14 @@ public class Logger {
} }
func log(message: String) { func log(message: String) {
write_msg_to_log(log, message.trimmingCharacters(in: .newlines)) write_msg_to_log(log, tag, message.trimmingCharacters(in: .newlines))
} }
func writeLog(called ourTag: String, mergedWith otherLogFile: String, called otherTag: String, to targetFile: String) -> Bool { func writeLog(to targetFile: String) -> Bool {
guard let other = open_log(otherLogFile) else { return false } return write_log_to_file(targetFile, self.log) == 0
let ret = write_logs_to_file(targetFile, log, ourTag, other, otherTag)
close_log(other)
return ret == 0
} }
static func configureGlobal(withFilePath filePath: String?) { static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
if Logger.global != nil { if Logger.global != nil {
return return
} }
@@ -41,7 +40,7 @@ public class Logger {
os_log("Unable to determine log destination path. Log will not be saved to file.", log: OSLog.default, type: .error) os_log("Unable to determine log destination path. Log will not be saved to file.", log: OSLog.default, type: .error)
return return
} }
guard let logger = try? Logger(withFilePath: filePath) else { guard let logger = try? Logger(tagged: tag, withFilePath: filePath) else {
os_log("Unable to open log file for writing. Log will not be saved to file.", log: OSLog.default, type: .error) os_log("Unable to open log file for writing. Log will not be saved to file.", log: OSLog.default, type: .error)
return return
} }
@@ -52,7 +51,6 @@ public class Logger {
} }
let goBackendVersion = WIREGUARD_GO_VERSION let goBackendVersion = WIREGUARD_GO_VERSION
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)") Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
} }
} }
+79 -53
View File
@@ -6,6 +6,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <stdbool.h> #include <stdbool.h>
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
@@ -19,100 +21,124 @@
enum { enum {
MAX_LOG_LINE_LENGTH = 512, MAX_LOG_LINE_LENGTH = 512,
MAX_LINES = 1024, MAX_LINES = 2048,
MAGIC = 0xbeefbabeU MAGIC = 0xabadbeefU
}; };
struct log_line { struct log_line {
struct timeval tv; atomic_uint_fast64_t time_ns;
char line[MAX_LOG_LINE_LENGTH]; char line[MAX_LOG_LINE_LENGTH];
}; };
struct log { struct log {
struct { uint32_t first, len; } header; atomic_uint_fast32_t next_index;
struct log_line lines[MAX_LINES]; struct log_line lines[MAX_LINES];
uint32_t magic; uint32_t magic;
}; };
void write_msg_to_log(struct log *log, const char *msg) void write_msg_to_log(struct log *log, const char *tag, const char *msg)
{ {
struct log_line *line = &log->lines[(log->header.first + log->header.len) % MAX_LINES]; uint32_t index;
struct log_line *line;
struct timespec ts;
if (log->header.len == MAX_LINES) // Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
log->header.first = (log->header.first + 1) % MAX_LINES; clock_gettime(CLOCK_REALTIME, &ts);
else
++log->header.len;
gettimeofday(&line->tv, NULL); // Race: More than MAX_LINES writers and this will clash.
strncpy(line->line, msg, MAX_LOG_LINE_LENGTH - 1); index = atomic_fetch_add(&log->next_index, 1);
line->line[MAX_LOG_LINE_LENGTH - 1] = '\0'; line = &log->lines[index % MAX_LINES];
msync(&log->header, sizeof(log->header), MS_ASYNC); // Race: Before this line executes, we'll display old data after new data.
atomic_store(&line->time_ns, 0);
memset(line->line, 0, MAX_LOG_LINE_LENGTH);
snprintf(line->line, MAX_LOG_LINE_LENGTH, "[%s] %s", tag, msg);
atomic_store(&line->time_ns, ts.tv_sec * 1000000000ULL + ts.tv_nsec);
msync(&log->next_index, sizeof(log->next_index), MS_ASYNC);
msync(line, sizeof(*line), MS_ASYNC); msync(line, sizeof(*line), MS_ASYNC);
} }
static bool first_before_second(const struct log_line *line1, const struct log_line *line2) int write_log_to_file(const char *file_name, const struct log *input_log)
{ {
if (line1->tv.tv_sec <= line2->tv.tv_sec) struct log *log;
return true; uint32_t l, i;
if (line1->tv.tv_sec == line2->tv.tv_sec)
return line1->tv.tv_usec <= line2->tv.tv_usec;
return false;
}
int write_logs_to_file(const char *file_name, const struct log *log1, const char *tag1, const struct log *log2, const char *tag2)
{
uint32_t i1, i2, len1 = log1->header.len, len2 = log2->header.len;
FILE *file; FILE *file;
int ret; int ret;
if (len1 > MAX_LINES) log = malloc(sizeof(*log));
len1 = MAX_LINES; if (!log)
if (len2 > MAX_LINES) return -errno;
len2 = MAX_LINES; memcpy(log, input_log, sizeof(*log));
file = fopen(file_name, "w"); file = fopen(file_name, "w");
if (!file) if (!file) {
free(log);
return -errno; return -errno;
}
for (i1 = 0, i2 = 0;;) { for (l = 0, i = log->next_index; l < MAX_LINES; ++l, ++i) {
const struct log_line *line = &log->lines[i % MAX_LINES];
time_t seconds = line->time_ns / 1000000000ULL;
uint32_t useconds = (line->time_ns % 1000000000ULL) / 1000ULL;
struct tm tm; struct tm tm;
char buf[MAX_LOG_LINE_LENGTH];
const struct log_line *line1 = &log1->lines[(log1->header.first + i1) % MAX_LINES];
const struct log_line *line2 = &log2->lines[(log2->header.first + i2) % MAX_LINES];
const struct log_line *line;
const char *tag;
if (i1 < len1 && (i2 >= len2 || first_before_second(line1, line2))) { if (!line->time_ns)
line = line1; continue;
tag = tag1;
++i1;
} else if (i2 < len2 && (i1 >= len1 || first_before_second(line2, line1))) {
line = line2;
tag = tag2;
++i2;
} else {
break;
}
memcpy(buf, line->line, MAX_LOG_LINE_LENGTH); if (!localtime_r(&seconds, &tm))
buf[MAX_LOG_LINE_LENGTH - 1] = '\0';
if (!localtime_r(&line->tv.tv_sec, &tm))
goto err; goto err;
if (fprintf(file, "%04d-%02d-%02d %02d:%02d:%02d.%06d: [%s] %s\n",
if (fprintf(file, "%04d-%02d-%02d %02d:%02d:%02d.%06d: %s\n",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec, line->tv.tv_usec, tm.tm_hour, tm.tm_min, tm.tm_sec, useconds,
tag, buf) < 0) line->line) < 0)
goto err; goto err;
} }
errno = 0; errno = 0;
err: err:
ret = -errno; ret = -errno;
fclose(file); fclose(file);
free(log);
return ret; return ret;
} }
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*cb)(const char *, uint64_t, void *))
{
struct log *log;
uint32_t l, i = cursor;
log = malloc(sizeof(*log));
if (!log)
return cursor;
memcpy(log, input_log, sizeof(*log));
if (i == -1)
i = log->next_index;
for (l = 0; l < MAX_LINES; ++l, ++i) {
const struct log_line *line = &log->lines[i % MAX_LINES];
if (cursor != -1 && i % MAX_LINES == log->next_index % MAX_LINES)
break;
if (!line->time_ns) {
if (cursor == -1)
continue;
else
break;
}
cb(line->line, line->time_ns, ctx);
cursor = (i + 1) % MAX_LINES;
}
free(log);
return cursor;
}
struct log *open_log(const char *file_name) struct log *open_log(const char *file_name)
{ {
int fd; int fd;
+5 -2
View File
@@ -6,9 +6,12 @@
#ifndef RINGLOGGER_H #ifndef RINGLOGGER_H
#define RINGLOGGER_H #define RINGLOGGER_H
#include <stdint.h>
struct log; struct log;
void write_msg_to_log(struct log *log, const char *msg); void write_msg_to_log(struct log *log, const char *tag, const char *msg);
int write_logs_to_file(const char *file_name, const struct log *log1, const char *tag1, const struct log *log2, const char *tag2); int write_log_to_file(const char *file_name, const struct log *input_log);
uint32_t view_lines_from_cursor(const struct log *input_log, uint32_t cursor, void *ctx, void(*)(const char *, uint64_t, void *));
struct log *open_log(const char *file_name); struct log *open_log(const char *file_name);
void close_log(struct log *log); void close_log(struct log *log);
@@ -0,0 +1,63 @@
#include "ringlogger.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/wait.h>
static void forkwrite(void)
{
struct log *log = open_log("/tmp/test_log");
char c[512];
int i, base;
bool in_fork = !fork();
base = 10000 * in_fork;
for (i = 0; i < 1024; ++i) {
snprintf(c, 512, "bla bla bla %d", base + i);
write_msg_to_log(log, "HMM", c);
}
if (in_fork)
_exit(0);
wait(NULL);
write_log_to_file("/dev/stdout", log);
close_log(log);
}
static void writetext(const char *text)
{
struct log *log = open_log("/tmp/test_log");
write_msg_to_log(log, "TXT", text);
close_log(log);
}
static void show_line(const char *line, uint64_t time_ns)
{
printf("%" PRIu64 ": %s\n", time_ns, line);
}
static void follow(void)
{
uint32_t cursor = -1;
struct log *log = open_log("/tmp/test_log");
for (;;) {
cursor = view_lines_from_cursor(log, cursor, show_line);
usleep(1000 * 300);
}
}
int main(int argc, char *argv[])
{
if (!strcmp(argv[1], "fork"))
forkwrite();
else if (!strcmp(argv[1], "write"))
writetext(argv[2]);
else if (!strcmp(argv[1], "follow"))
follow();
return 0;
}
+32 -6
View File
@@ -13,8 +13,8 @@ extension Data {
return nil return nil
} }
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX)) var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX))
out.withUnsafeMutableBytes { outBytes in out.withUnsafeMutableInt8Bytes { outBytes in
self.withUnsafeBytes { inBytes in self.withUnsafeUInt8Bytes { inBytes in
key_to_hex(outBytes, inBytes) key_to_hex(outBytes, inBytes)
} }
} }
@@ -25,7 +25,7 @@ extension Data {
init?(hexKey hexString: String) { init?(hexKey hexString: String) {
self.init(repeating: 0, count: Int(WG_KEY_LEN)) self.init(repeating: 0, count: Int(WG_KEY_LEN))
if !self.withUnsafeMutableBytes { key_from_hex($0, hexString) } { if !self.withUnsafeMutableUInt8Bytes { key_from_hex($0, hexString) } {
return nil return nil
} }
} }
@@ -35,8 +35,8 @@ extension Data {
return nil return nil
} }
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64)) var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64))
out.withUnsafeMutableBytes { outBytes in out.withUnsafeMutableInt8Bytes { outBytes in
self.withUnsafeBytes { inBytes in self.withUnsafeUInt8Bytes { inBytes in
key_to_base64(outBytes, inBytes) key_to_base64(outBytes, inBytes)
} }
} }
@@ -47,8 +47,34 @@ extension Data {
init?(base64Key base64String: String) { init?(base64Key base64String: String) {
self.init(repeating: 0, count: Int(WG_KEY_LEN)) self.init(repeating: 0, count: Int(WG_KEY_LEN))
if !self.withUnsafeMutableBytes { key_from_base64($0, base64String) } { if !self.withUnsafeMutableUInt8Bytes { key_from_base64($0, base64String) } {
return nil return nil
} }
} }
} }
extension Data {
func withUnsafeUInt8Bytes<R>(_ body: (UnsafePointer<UInt8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: UInt8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
mutating func withUnsafeMutableUInt8Bytes<R>(_ body: (UnsafeMutablePointer<UInt8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: UInt8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
mutating func withUnsafeMutableInt8Bytes<R>(_ body: (UnsafeMutablePointer<Int8>) -> R) -> R {
assert(!isEmpty)
return self.withUnsafeMutableBytes { (ptr: UnsafeMutableRawBufferPointer) -> R in
let bytes = ptr.bindMemory(to: Int8.self)
return body(bytes.baseAddress!) // might crash if self.count == 0
}
}
}
+6
View File
@@ -36,6 +36,8 @@ extension Endpoint {
return "\(address):\(port)" return "\(address):\(port)"
case .ipv6(let address): case .ipv6(let address):
return "[\(address)]:\(port)" return "[\(address)]:\(port)"
@unknown default:
fatalError()
} }
} }
@@ -78,6 +80,8 @@ extension Endpoint {
return true return true
case .ipv6: case .ipv6:
return true return true
@unknown default:
fatalError()
} }
} }
@@ -89,6 +93,8 @@ extension Endpoint {
return nil return nil
case .ipv6: case .ipv6:
return nil return nil
@unknown default:
fatalError()
} }
} }
} }
@@ -34,17 +34,14 @@ extension NETunnelProviderProtocol {
} }
func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? { func asTunnelConfiguration(called name: String? = nil) -> TunnelConfiguration? {
migrateConfigurationIfNeeded(called: name ?? "unknown") if let passwordReference = passwordReference,
//TODO: in the case where migrateConfigurationIfNeeded is called by the network extension, let config = Keychain.openReference(called: passwordReference) {
// before the app has started, and when there is, in fact, configuration that needs to be return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
// put into the keychain, this will generate one new keychain item every time it is started, }
// until finally the app is open. Would it be possible to call saveToPreferences here? Or is if let oldConfig = providerConfiguration?["WgQuickConfig"] as? String {
// that generally not available to network extensions? In which case, what should our return try? TunnelConfiguration(fromWgQuickConfig: oldConfig, called: name)
// behavior be? }
return nil
guard let passwordReference = passwordReference else { return nil }
guard let config = Keychain.openReference(called: passwordReference) else { return nil }
return try? TunnelConfiguration(fromWgQuickConfig: config, called: name)
} }
func destroyConfigurationReference() { func destroyConfigurationReference() {
@@ -52,9 +49,9 @@ extension NETunnelProviderProtocol {
Keychain.deleteReference(called: ref) Keychain.deleteReference(called: ref)
} }
func verifyConfigurationReference() -> Data? { func verifyConfigurationReference() -> Bool {
guard let ref = passwordReference else { return nil } guard let ref = passwordReference else { return false }
return Keychain.verifyReference(called: ref) ? ref : nil return Keychain.verifyReference(called: ref)
} }
@discardableResult @discardableResult
@@ -66,6 +63,7 @@ extension NETunnelProviderProtocol {
guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false } guard let oldConfig = providerConfiguration?["WgQuickConfig"] as? String else { return false }
providerConfiguration = nil providerConfiguration = nil
guard passwordReference == nil else { return true } guard passwordReference == nil else { return true }
wg_log(.debug, message: "Migrating tunnel configuration '\(name)'")
passwordReference = Keychain.makeReference(containing: oldConfig, called: name) passwordReference = Keychain.makeReference(containing: oldConfig, called: name)
return true return true
} }
@@ -52,15 +52,15 @@ extension TunnelConfiguration {
trimmedLine = String(line) trimmedLine = String(line)
} }
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces) trimmedLine = trimmedLine.trimmingCharacters(in: .whitespacesAndNewlines)
let lowercasedLine = trimmedLine.lowercased() let lowercasedLine = trimmedLine.lowercased()
if !trimmedLine.isEmpty { if !trimmedLine.isEmpty {
if let equalsIndex = trimmedLine.firstIndex(of: "=") { if let equalsIndex = trimmedLine.firstIndex(of: "=") {
// Line contains an attribute // Line contains an attribute
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespaces) let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespacesAndNewlines)
let key = keyWithCase.lowercased() let key = keyWithCase.lowercased()
let value = trimmedLine[trimmedLine.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces) let value = trimmedLine[trimmedLine.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespacesAndNewlines)
let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"] let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"]
if let presentValue = attributes[key] { if let presentValue = attributes[key] {
if keysWithMultipleEntriesAllowed.contains(key) { if keysWithMultipleEntriesAllowed.contains(key) {
@@ -182,7 +182,7 @@ extension TunnelConfiguration {
} }
if let addressesString = attributes["address"] { if let addressesString = attributes["address"] {
var addresses = [IPAddressRange]() var addresses = [IPAddressRange]()
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespaces) { for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
guard let address = IPAddressRange(from: addressString) else { guard let address = IPAddressRange(from: addressString) else {
throw ParseError.interfaceHasInvalidAddress(addressString) throw ParseError.interfaceHasInvalidAddress(addressString)
} }
@@ -192,7 +192,7 @@ extension TunnelConfiguration {
} }
if let dnsString = attributes["dns"] { if let dnsString = attributes["dns"] {
var dnsServers = [DNSServer]() var dnsServers = [DNSServer]()
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespaces) { for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
guard let dnsServer = DNSServer(from: dnsServerString) else { guard let dnsServer = DNSServer(from: dnsServerString) else {
throw ParseError.interfaceHasInvalidDNS(dnsServerString) throw ParseError.interfaceHasInvalidDNS(dnsServerString)
} }
+214 -19
View File
@@ -41,12 +41,17 @@
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; }; 6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; }; 6B62E460220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */; };
6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; }; 6B653B86220DE2960050E69C /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FF4AC462120B9E0002C96EB /* NetworkExtension.framework */; };
6B6956362211DA80001B618A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B6956352211DA80001B618A /* main.m */; };
6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; }; 6B707D8421F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; }; 6B707D8621F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */; };
6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; }; 6BD5C97B220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; }; 6BD5C97C220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; }; 6BD5C97D220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6BD5C97E220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; }; 6BD5C97E220D1AE200784E08 /* key.c in Sources */ = {isa = PBXBuildFile; fileRef = 6BD5C979220D1AE100784E08 /* key.c */; };
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44C8222D55BB00B0FF04 /* TextCell.swift */; };
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */; };
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */; };
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */; };
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; }; 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */; };
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; }; 6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; }; 6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
@@ -54,6 +59,7 @@
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; }; 6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; };
6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; }; 6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; };
6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5EA59A223E58A8002B380A /* ButtonRow.swift */; };
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; }; 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; }; 6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; }; 6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
@@ -63,6 +69,9 @@
6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; }; 6F6899A62180447E0012E523 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; }; 6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; }; 6F693A562179E556008551C1 /* Endpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F693A552179E556008551C1 /* Endpoint.swift */; };
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6F70E20C221058DF008BDFB4 /* InfoPlist.strings */; };
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */ = {isa = PBXBuildFile; fileRef = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; }; 6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774DF217181B1006A79B3 /* MainViewController.swift */; };
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; }; 6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; }; 6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
@@ -73,13 +82,21 @@
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; }; 6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; }; 6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */; };
6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; }; 6F89E17C21F090CC00C97BB9 /* TunnelsTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */; };
6F8F0D7122258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */; };
6F8F0D7222258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */; };
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7322267AD2000E8335 /* ChevronCell.swift */; };
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */; };
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F907C9B224663A2003CED21 /* LogViewHelper.swift */; };
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; }; 6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */; };
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; }; 6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */; };
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; }; 6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */; };
6F919EDB218C65C50023B400 /* wireguard_doc_logo_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */; }; 6F919EDB218C65C50023B400 /* wireguard_doc_logo_64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */; };
6F919EDC218C65C50023B400 /* wireguard_doc_logo_320x320.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */; }; 6F919EDC218C65C50023B400 /* wireguard_doc_logo_320x320.png in Resources */ = {isa = PBXBuildFile; fileRef = 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */; };
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9B582721E8CD4300544D02 /* PopupRow.swift */; }; 6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */; };
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */; };
6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1017821C57DE600766195 /* MockTunnels.swift */; }; 6FB1017921C57DE600766195 /* MockTunnels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1017821C57DE600766195 /* MockTunnels.swift */; };
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */; };
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; }; 6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; };
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FB1BD6121D2607E00A991BF /* Assets.xcassets */; }; 6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6FB1BD6121D2607E00A991BF /* Assets.xcassets */; };
6FB1BD9921D4BFE700A991BF /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 6FB1BD9921D4BFE700A991BF /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -117,7 +134,7 @@
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; }; 6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; }; 6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.swift */; };
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */; }; 6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */; };
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; }; 6FB1BDCD21D50F5300A991BF /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */; };
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; }; 6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F4541A821C451D100994C13 /* TunnelStatus.swift */; };
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; }; 6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; }; 6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; };
@@ -138,11 +155,14 @@
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */; }; 6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */; };
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */; }; 6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */; };
6FBA104621D7EBFA0051C35F /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */; }; 6FBA104621D7EBFA0051C35F /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */; };
6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */; }; 6FCD99AA21E0E14700BA4C82 /* ButtonedDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */; };
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */; }; 6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */; };
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; }; 6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; };
6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */; }; 6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */; };
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; }; 6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */; };
6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; }; 6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; }; 6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; }; 6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
@@ -170,7 +190,7 @@
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; }; 6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; }; 6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; }; 6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; }; 6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */; };
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; }; 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@@ -182,6 +202,13 @@
remoteGlobalIDString = 6F5D0C19218352EF000F85AD; remoteGlobalIDString = 6F5D0C19218352EF000F85AD;
remoteInfo = WireGuardNetworkExtension; remoteInfo = WireGuardNetworkExtension;
}; };
6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
proxyType = 1;
remoteGlobalIDString = 6F70E22822106A2D008BDFB4;
remoteInfo = WireGuardmacOSLoginItemHelper;
};
6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = { 6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy; isa = PBXContainerItemProxy;
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */; containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
@@ -217,6 +244,17 @@
name = "Embed App Extensions"; name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = Contents/Library/LoginItems;
dstSubfolderSpec = 1;
files = (
6F70E23D22109E15008BDFB4 /* WireGuardLoginItemHelper.app in Embed Login Item Helper */,
);
name = "Embed Login Item Helper";
runOnlyForDeploymentPostprocessing = 0;
};
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */ = { 6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -252,9 +290,14 @@
6B586C52220CBA6D00427C51 /* Data+KeyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+KeyEncoding.swift"; sourceTree = "<group>"; }; 6B586C52220CBA6D00427C51 /* Data+KeyEncoding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+KeyEncoding.swift"; sourceTree = "<group>"; };
6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; }; 6B5C5E26220A48D30024272E /* Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keychain.swift; sourceTree = "<group>"; };
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; }; 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivateDataConfirmation.swift; sourceTree = "<group>"; };
6B6956352211DA80001B618A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; }; 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TunnelConfiguration+UapiConfig.swift"; sourceTree = "<group>"; };
6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; }; 6BD5C979220D1AE100784E08 /* key.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = key.c; sourceTree = "<group>"; };
6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; }; 6BD5C97A220D1AE200784E08 /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = "<group>"; };
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCell.swift; sourceTree = "<group>"; };
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextCell.swift; sourceTree = "<group>"; };
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteTunnelsConfirmationAlert.swift; sourceTree = "<group>"; };
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationAlertPresenter.swift; sourceTree = "<group>"; };
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; }; 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Reuse.swift"; sourceTree = "<group>"; };
6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; }; 6F4DD16A21DA558800690EAE /* TunnelListRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelListRow.swift; sourceTree = "<group>"; };
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; }; 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageTunnelsRootViewController.swift; sourceTree = "<group>"; };
@@ -266,6 +309,7 @@
6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; }; 6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = "<group>"; };
6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; }; 6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = "<group>"; };
6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; }; 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = "<group>"; };
6F5EA59A223E58A8002B380A /* ButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonRow.swift; sourceTree = "<group>"; };
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; }; 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = "<group>"; };
6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; }; 6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = "<group>"; };
6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; }; 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = "<group>"; };
@@ -277,6 +321,10 @@
6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; }; 6F6899A52180447E0012E523 /* x25519.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = x25519.c; sourceTree = "<group>"; };
6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; }; 6F6899A7218044FC0012E523 /* Curve25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Curve25519.swift; sourceTree = "<group>"; };
6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; }; 6F693A552179E556008551C1 /* Endpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Endpoint.swift; sourceTree = "<group>"; };
6F70E20D221058DF008BDFB4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = WireGuard/Base.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuardLoginItemHelper.app; sourceTree = BUILT_PRODUCTS_DIR; };
6F70E23222106A31008BDFB4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LoginItemHelper.entitlements; sourceTree = "<group>"; };
6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; }; 6F7774DF217181B1006A79B3 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 6F7774E0217181B1006A79B3 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; }; 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
@@ -287,13 +335,19 @@
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = "<group>"; }; 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelErrors.swift; sourceTree = "<group>"; };
6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = "<group>"; }; 6F89E17921EDEB0E00C97BB9 /* StatusItemController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemController.swift; sourceTree = "<group>"; };
6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = "<group>"; }; 6F89E17B21F090CC00C97BB9 /* TunnelsTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsTracker.swift; sourceTree = "<group>"; };
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandViewModel.swift; sourceTree = "<group>"; };
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChevronCell.swift; sourceTree = "<group>"; };
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionEditTableViewController.swift; sourceTree = "<group>"; };
6F907C9B224663A2003CED21 /* LogViewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewHelper.swift; sourceTree = "<group>"; };
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; }; 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; }; 6F919ED5218C65C50023B400 /* wireguard_doc_logo_22x29.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_22x29.png; sourceTree = "<group>"; };
6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; }; 6F919ED6218C65C50023B400 /* wireguard_doc_logo_44x58.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_44x58.png; sourceTree = "<group>"; };
6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_64x64.png; sourceTree = "<group>"; }; 6F919ED7218C65C50023B400 /* wireguard_doc_logo_64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_64x64.png; sourceTree = "<group>"; };
6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_320x320.png; sourceTree = "<group>"; }; 6F919ED8218C65C50023B400 /* wireguard_doc_logo_320x320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = wireguard_doc_logo_320x320.png; sourceTree = "<group>"; };
6F9B582721E8CD4300544D02 /* PopupRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupRow.swift; sourceTree = "<group>"; }; 6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSIDOptionDetailTableViewController.swift; sourceTree = "<group>"; };
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnusableTunnelDetailViewController.swift; sourceTree = "<group>"; };
6FB1017821C57DE600766195 /* MockTunnels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnels.swift; sourceTree = "<group>"; }; 6FB1017821C57DE600766195 /* MockTunnels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTunnels.swift; sourceTree = "<group>"; };
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDemandWiFiControls.swift; sourceTree = "<group>"; };
6FB1BD5D21D2607A00A991BF /* WireGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuard.app; sourceTree = BUILT_PRODUCTS_DIR; }; 6FB1BD5D21D2607A00A991BF /* WireGuard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WireGuard.app; sourceTree = BUILT_PRODUCTS_DIR; };
6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
6FB1BD6121D2607E00A991BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 6FB1BD6121D2607E00A991BF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -309,10 +363,13 @@
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelImporter.swift; sourceTree = "<group>"; }; 6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelImporter.swift; sourceTree = "<group>"; };
6FBA104121D6BC210051C35F /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; }; 6FBA104121D6BC210051C35F /* ErrorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorPresenter.swift; sourceTree = "<group>"; };
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; }; 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsListTableViewController.swift; sourceTree = "<group>"; };
6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoTunnelsDetailViewController.swift; sourceTree = "<group>"; }; 6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonedDetailViewController.swift; sourceTree = "<group>"; };
6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPanelPresenter.swift; sourceTree = "<group>"; }; 6FCD99AE21E0EA1700BA4C82 /* ImportPanelPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportPanelPresenter.swift; sourceTree = "<group>"; };
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditViewController.swift; sourceTree = "<group>"; }; 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelEditViewController.swift; sourceTree = "<group>"; };
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; }; 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; }; 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; }; 6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
@@ -340,7 +397,7 @@
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; }; 6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuard.entitlements; sourceTree = "<group>"; };
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; }; 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; };
6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; }; 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; };
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; }; 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandOption.swift; sourceTree = "<group>"; };
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; }; 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@@ -392,6 +449,9 @@
5F4541A521C4449E00994C13 /* ButtonCell.swift */, 5F4541A521C4449E00994C13 /* ButtonCell.swift */,
5F45419121C2D55800994C13 /* CheckmarkCell.swift */, 5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */, 5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */,
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */,
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */,
); );
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -405,6 +465,9 @@
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */, 6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */, 6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
6F7774DF217181B1006A79B3 /* MainViewController.swift */, 6F7774DF217181B1006A79B3 /* MainViewController.swift */,
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
); );
path = ViewController; path = ViewController;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -418,8 +481,11 @@
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */, 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */,
5F52D0BA21E3781B00283CEA /* ConfTextView.swift */, 5F52D0BA21E3781B00283CEA /* ConfTextView.swift */,
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */, 5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
6F9B582721E8CD4300544D02 /* PopupRow.swift */,
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */, 6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
6F5EA59A223E58A8002B380A /* ButtonRow.swift */,
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */,
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */,
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */,
); );
path = View; path = View;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -460,15 +526,27 @@
path = Crypto; path = Crypto;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */ = {
isa = PBXGroup;
children = (
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */,
6F70E23222106A31008BDFB4 /* Info.plist */,
6B6956352211DA80001B618A /* main.m */,
);
path = LoginItemHelper;
sourceTree = "<group>";
};
6F7774DD217181B1006A79B3 /* UI */ = { 6F7774DD217181B1006A79B3 /* UI */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6FB1BD5E21D2607A00A991BF /* macOS */, 6FB1BD5E21D2607A00A991BF /* macOS */,
6F7774DE217181B1006A79B3 /* iOS */, 6F7774DE217181B1006A79B3 /* iOS */,
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */, 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */,
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */, 6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */, 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */, 6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
6F907C9B224663A2003CED21 /* LogViewHelper.swift */,
); );
path = UI; path = UI;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -478,10 +556,11 @@
children = ( children = (
6FF4AC1E211EC472002C96EB /* Assets.xcassets */, 6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */, 6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
5F4541AC21C4720B00994C13 /* ViewController */,
5F4541A721C44F5B00994C13 /* View */, 5F4541A721C44F5B00994C13 /* View */,
5F4541AC21C4720B00994C13 /* ViewController */,
6F7774E0217181B1006A79B3 /* AppDelegate.swift */, 6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */, 6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */, 5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
6FF4AC23211EC472002C96EB /* Info.plist */, 6FF4AC23211EC472002C96EB /* Info.plist */,
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */, 6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
@@ -513,7 +592,7 @@
children = ( children = (
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */, 6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */, 6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */,
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */, 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */,
5F4541A821C451D100994C13 /* TunnelStatus.swift */, 5F4541A821C451D100994C13 /* TunnelStatus.swift */,
6FB1017821C57DE600766195 /* MockTunnels.swift */, 6FB1017821C57DE600766195 /* MockTunnels.swift */,
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */, 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */,
@@ -543,6 +622,7 @@
6FB1BD5E21D2607A00A991BF /* macOS */ = { 6FB1BD5E21D2607A00A991BF /* macOS */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */,
6F4DD16921DA556600690EAE /* View */, 6F4DD16921DA556600690EAE /* View */,
6FBA104421D7EA750051C35F /* ViewController */, 6FBA104421D7EA750051C35F /* ViewController */,
6FBA101321D613F30051C35F /* Application.swift */, 6FBA101321D613F30051C35F /* Application.swift */,
@@ -568,8 +648,10 @@
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */, 6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */,
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */, 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */,
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */, 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */,
6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */, 6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */,
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */, 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */,
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */,
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */,
); );
path = ViewController; path = ViewController;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -619,6 +701,7 @@
6FF4AC0B211EC46F002C96EB = { 6FF4AC0B211EC46F002C96EB = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */,
6FE1765421C90BBE002690EA /* Localizable.strings */, 6FE1765421C90BBE002690EA /* Localizable.strings */,
6F5D0C432183B4A4000F85AD /* Shared */, 6F5D0C432183B4A4000F85AD /* Shared */,
6FF4AC16211EC46F002C96EB /* WireGuard */, 6FF4AC16211EC46F002C96EB /* WireGuard */,
@@ -635,6 +718,7 @@
6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */, 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */,
6FB1BD5D21D2607A00A991BF /* WireGuard.app */, 6FB1BD5D21D2607A00A991BF /* WireGuard.app */,
6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */, 6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */,
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -721,6 +805,21 @@
productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */;
productType = "com.apple.product-type.app-extension"; productType = "com.apple.product-type.app-extension";
}; };
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */ = {
isa = PBXNativeTarget;
buildConfigurationList = 6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */;
buildPhases = (
6F70E22522106A2D008BDFB4 /* Sources */,
);
buildRules = (
);
dependencies = (
);
name = WireGuardmacOSLoginItemHelper;
productName = WireGuardmacOSLoginItemHelper;
productReference = 6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */;
productType = "com.apple.product-type.application";
};
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */ = { 6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */; buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */;
@@ -732,10 +831,12 @@
6FB1BD5A21D2607A00A991BF /* Frameworks */, 6FB1BD5A21D2607A00A991BF /* Frameworks */,
6FB1BD5B21D2607A00A991BF /* Resources */, 6FB1BD5B21D2607A00A991BF /* Resources */,
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */, 6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */,
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */,
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */, 6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */,
); );
name = WireGuardmacOS; name = WireGuardmacOS;
@@ -797,10 +898,19 @@
TargetAttributes = { TargetAttributes = {
6F5D0C19218352EF000F85AD = { 6F5D0C19218352EF000F85AD = {
CreatedOnToolsVersion = 10.0; CreatedOnToolsVersion = 10.0;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
};
6F70E22822106A2D008BDFB4 = {
CreatedOnToolsVersion = 10.1;
SystemCapabilities = {
com.apple.Sandbox = {
enabled = 1;
};
};
}; };
6FB1BD5C21D2607A00A991BF = { 6FB1BD5C21D2607A00A991BF = {
CreatedOnToolsVersion = 10.1; CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1020;
SystemCapabilities = { SystemCapabilities = {
com.apple.ApplicationGroups.Mac = { com.apple.ApplicationGroups.Mac = {
enabled = 1; enabled = 1;
@@ -815,6 +925,7 @@
}; };
6FB1BD9021D4BFE600A991BF = { 6FB1BD9021D4BFE600A991BF = {
CreatedOnToolsVersion = 10.1; CreatedOnToolsVersion = 10.1;
LastSwiftMigration = 1020;
SystemCapabilities = { SystemCapabilities = {
com.apple.ApplicationGroups.Mac = { com.apple.ApplicationGroups.Mac = {
enabled = 1; enabled = 1;
@@ -826,8 +937,11 @@
}; };
6FF4AC13211EC46F002C96EB = { 6FF4AC13211EC46F002C96EB = {
CreatedOnToolsVersion = 9.4.1; CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1000; LastSwiftMigration = 1020;
SystemCapabilities = { SystemCapabilities = {
com.apple.AccessWiFi = {
enabled = 1;
};
com.apple.ApplicationGroups.iOS = { com.apple.ApplicationGroups.iOS = {
enabled = 1; enabled = 1;
}; };
@@ -857,6 +971,7 @@
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */, 6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */,
6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */, 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */,
6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */, 6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */,
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
@@ -874,6 +989,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */, 6FB1BD6221D2607E00A991BF /* Assets.xcassets in Resources */,
6F70E20F221058E1008BDFB4 /* InfoPlist.strings in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -889,6 +1005,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */, 6F919ED9218C65C50023B400 /* wireguard_doc_logo_22x29.png in Resources */,
6F70E20E221058E1008BDFB4 /* InfoPlist.strings in Resources */,
6FE1765621C90BBE002690EA /* Localizable.strings in Resources */, 6FE1765621C90BBE002690EA /* Localizable.strings in Resources */,
6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */, 6FF4AC22211EC472002C96EB /* LaunchScreen.storyboard in Resources */,
6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */, 6F919EDA218C65C50023B400 /* wireguard_doc_logo_44x58.png in Resources */,
@@ -1114,13 +1231,22 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
6F70E22522106A2D008BDFB4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6B6956362211DA80001B618A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
6FB1BD5921D2607A00A991BF /* Sources */ = { 6FB1BD5921D2607A00A991BF /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
6FBA101521D613F90051C35F /* Application.swift in Sources */, 6FBA101521D613F90051C35F /* Application.swift in Sources */,
6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */, 6FB1BDCC21D50F5300A991BF /* TunnelsManager.swift in Sources */,
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandSetting.swift in Sources */, 6F8F0D7222258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */,
6FB1BDCD21D50F5300A991BF /* ActivateOnDemandOption.swift in Sources */,
6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */, 6FB1BDCE21D50F5300A991BF /* TunnelStatus.swift in Sources */,
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */, 6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */,
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */, 6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */,
@@ -1129,6 +1255,7 @@
6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */, 6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */,
6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */, 6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */,
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */, 6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */,
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */,
6B5C5E29220A48D30024272E /* Keychain.swift in Sources */, 6B5C5E29220A48D30024272E /* Keychain.swift in Sources */,
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */, 6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */,
6FB1BDD521D50F5300A991BF /* unzip.c in Sources */, 6FB1BDD521D50F5300A991BF /* unzip.c in Sources */,
@@ -1149,31 +1276,36 @@
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */, 6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */, 6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */, 6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */, 6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */,
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */, 6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */, 6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */, 6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */, 6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */, 6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */,
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */, 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */, 6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */, 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */, 6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */, 6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */, 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */,
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */, 6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */, 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */, 6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */,
5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */, 5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */,
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */, 6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */,
6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */, 6FCD99AA21E0E14700BA4C82 /* ButtonedDetailViewController.swift in Sources */,
6FB1BDC321D50F0300A991BF /* TunnelConfiguration.swift in Sources */, 6FB1BDC321D50F0300A991BF /* TunnelConfiguration.swift in Sources */,
6FB1BDC421D50F0300A991BF /* IPAddressRange.swift in Sources */, 6FB1BDC421D50F0300A991BF /* IPAddressRange.swift in Sources */,
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */, 6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */,
6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */, 6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */,
6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */, 6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */,
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */, 6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */, 6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */, 6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */,
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */, 6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */,
@@ -1212,18 +1344,23 @@
files = ( files = (
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */, 6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
6FF3527221C2616C0008484E /* ringlogger.c in Sources */, 6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
6FF3527321C2616C0008484E /* Logger.swift in Sources */, 6FF3527321C2616C0008484E /* Logger.swift in Sources */,
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */, 6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */, 6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */, 5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */, 5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */, 6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
6F8F0D7122258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */,
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */,
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */, 6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
6F693A562179E556008551C1 /* Endpoint.swift in Sources */, 6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */, 6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */, 5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */, 5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */,
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */, 6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
6F6899A62180447E0012E523 /* x25519.c in Sources */, 6F6899A62180447E0012E523 /* x25519.c in Sources */,
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */, 6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
@@ -1236,6 +1373,8 @@
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */, 5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */,
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */, 5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */, 6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */,
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */,
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */, 6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */, 6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */, 6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
@@ -1245,6 +1384,7 @@
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */, 6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */, 6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */, 6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */, 5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
5F9696B021CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift in Sources */, 5F9696B021CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */, 6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
@@ -1260,8 +1400,9 @@
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */, 5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */, 5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */, 6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */, 6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */,
5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */, 5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -1273,6 +1414,11 @@
target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */; target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */;
targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */; targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */;
}; };
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */;
targetProxy = 6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */;
};
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = { 6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = {
isa = PBXTargetDependency; isa = PBXTargetDependency;
target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */; target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */;
@@ -1291,6 +1437,14 @@
/* End PBXTargetDependency section */ /* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */ /* Begin PBXVariantGroup section */
6F70E20C221058DF008BDFB4 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
6F70E20D221058DF008BDFB4 /* Base */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
6FE1765421C90BBE002690EA /* Localizable.strings */ = { 6FE1765421C90BBE002690EA /* Localizable.strings */ = {
isa = PBXVariantGroup; isa = PBXVariantGroup;
children = ( children = (
@@ -1328,7 +1482,7 @@
PRODUCT_NAME = WireGuardNetworkExtension; PRODUCT_NAME = WireGuardNetworkExtension;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -1350,7 +1504,33 @@
PRODUCT_NAME = WireGuardNetworkExtension; PRODUCT_NAME = WireGuardNetworkExtension;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
};
name = Release;
};
6F70E23422106A31008BDFB4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
6F70E23522106A31008BDFB4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_ENTITLEMENTS = WireGuard/UI/macOS/LoginItemHelper/LoginItemHelper.entitlements;
CODE_SIGN_IDENTITY = "Mac Developer";
INFOPLIST_FILE = WireGuard/UI/macOS/Info.plist;
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS).login-item-helper";
PRODUCT_NAME = WireGuardLoginItemHelper;
SDKROOT = macosx;
SKIP_INSTALL = YES;
}; };
name = Release; name = Release;
}; };
@@ -1370,6 +1550,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
PRODUCT_NAME = WireGuard; PRODUCT_NAME = WireGuard;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -1389,6 +1570,7 @@
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
PRODUCT_NAME = WireGuard; PRODUCT_NAME = WireGuard;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -1410,6 +1592,7 @@
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -1431,6 +1614,7 @@
SDKROOT = macosx; SDKROOT = macosx;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -1607,6 +1791,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
PRODUCT_NAME = WireGuard; PRODUCT_NAME = WireGuard;
SWIFT_VERSION = 5.0;
}; };
name = Debug; name = Debug;
}; };
@@ -1623,6 +1808,7 @@
); );
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)"; PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
PRODUCT_NAME = WireGuard; PRODUCT_NAME = WireGuard;
SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;
}; };
@@ -1638,6 +1824,15 @@
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
6F70E23622106A31008BDFB4 /* Build configuration list for PBXNativeTarget "WireGuardmacOSLoginItemHelper" */ = {
isa = XCConfigurationList;
buildConfigurations = (
6F70E23422106A31008BDFB4 /* Debug */,
6F70E23522106A31008BDFB4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */ = { 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
// iOS permission prompts
NSCameraUsageDescription = "Camera is used for scanning QR codes for importing WireGuard configurations";
NSFaceIDUsageDescription = "Face ID is used for authenticating viewing and exporting of private keys";
@@ -13,6 +13,10 @@
"tunnelsListSettingsButtonTitle" = "Settings"; "tunnelsListSettingsButtonTitle" = "Settings";
"tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel"; "tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel";
"tunnelsListSwipeDeleteButtonTitle" = "Delete"; "tunnelsListSwipeDeleteButtonTitle" = "Delete";
"tunnelsListSelectButtonTitle" = "Select";
"tunnelsListSelectAllButtonTitle" = "Select All";
"tunnelsListDeleteButtonTitle" = "Delete";
"tunnelsListSelectedTitle (%d)" = "%d selected";
// Tunnels list menu // Tunnels list menu
@@ -23,11 +27,18 @@
// Tunnels list alerts // Tunnels list alerts
"alertImportedFromMultipleFilesTitle (%d)" = "Created %d tunnels";
"alertImportedFromMultipleFilesMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from imported files";
"alertImportedFromZipTitle (%d)" = "Created %d tunnels"; "alertImportedFromZipTitle (%d)" = "Created %d tunnels";
"alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive"; "alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive";
"alertUnableToImportTitle" = "Unable to import tunnel"; "alertBadConfigImportTitle" = "Unable to import tunnel";
"alertUnableToImportMessage" = "An error occured when importing the tunnel configuration."; "alertBadConfigImportMessage (%@)" = "The file %@ does not contain a valid WireGuard configuration";
"deleteTunnelsConfirmationAlertButtonTitle" = "Delete";
"deleteTunnelConfirmationAlertButtonMessage (%d)" = "Delete %d tunnel?";
"deleteTunnelsConfirmationAlertButtonMessage (%d)" = "Delete %d tunnels?";
// Tunnel detail and edit UI // Tunnel detail and edit UI
@@ -44,6 +55,14 @@
"tunnelStatusRestarting" = "Restarting"; "tunnelStatusRestarting" = "Restarting";
"tunnelStatusWaiting" = "Waiting"; "tunnelStatusWaiting" = "Waiting";
"macToggleStatusButtonActivate" = "Activate";
"macToggleStatusButtonActivating" = "Activating…";
"macToggleStatusButtonDeactivate" = "Deactivate";
"macToggleStatusButtonDeactivating" = "Deactivating…";
"macToggleStatusButtonReasserting" = "Reactivating…";
"macToggleStatusButtonRestarting" = "Restarting…";
"macToggleStatusButtonWaiting" = "Waiting…";
"tunnelSectionTitleInterface" = "Interface"; "tunnelSectionTitleInterface" = "Interface";
"tunnelInterfaceName" = "Name"; "tunnelInterfaceName" = "Name";
@@ -54,6 +73,7 @@
"tunnelInterfaceListenPort" = "Listen port"; "tunnelInterfaceListenPort" = "Listen port";
"tunnelInterfaceMTU" = "MTU"; "tunnelInterfaceMTU" = "MTU";
"tunnelInterfaceDNS" = "DNS servers"; "tunnelInterfaceDNS" = "DNS servers";
"tunnelInterfaceStatus" = "Status";
"tunnelSectionTitlePeer" = "Peer"; "tunnelSectionTitlePeer" = "Peer";
@@ -69,7 +89,28 @@
"tunnelSectionTitleOnDemand" = "On-Demand Activation"; "tunnelSectionTitleOnDemand" = "On-Demand Activation";
"tunnelOnDemandKey" = "Activate on demand"; "tunnelOnDemandCellular" = "Cellular";
"tunnelOnDemandEthernet" = "Ethernet";
"tunnelOnDemandWiFi" = "Wi-Fi";
"tunnelOnDemandSSIDsKey" = "SSIDs";
"tunnelOnDemandAnySSID" = "Any SSID";
"tunnelOnDemandOnlyTheseSSIDs" = "Only these SSIDs";
"tunnelOnDemandExceptTheseSSIDs" = "Except these SSIDs";
"tunnelOnDemandOnlySSID (%d)" = "Only %d SSID";
"tunnelOnDemandOnlySSIDs (%d)" = "Only %d SSIDs";
"tunnelOnDemandExceptSSID (%d)" = "Except %d SSID";
"tunnelOnDemandExceptSSIDs (%d)" = "Except %d SSIDs";
"tunnelOnDemandSSIDOptionDescriptionMac (%1$@: %2$@)" = "%1$@: %2$@";
"tunnelOnDemandSSIDViewTitle" = "SSIDs";
"tunnelOnDemandSectionTitleSelectedSSIDs" = "SSIDs";
"tunnelOnDemandNoSSIDs" = "No SSIDs";
"tunnelOnDemandSectionTitleAddSSIDs" = "Add SSIDs";
"tunnelOnDemandAddMessageAddConnectedSSID (%@)" = "Add connected: %@";
"tunnelOnDemandAddMessageAddNewSSID" = "Add new";
"tunnelOnDemandKey" = "On demand";
"tunnelOnDemandOptionOff" = "Off"; "tunnelOnDemandOptionOff" = "Off";
"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only"; "tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular"; "tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
@@ -111,6 +152,8 @@
"tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours"; "tunnelHandshakeTimestampHours hh:mm:ss (%@)" = "%@ hours";
"tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes"; "tunnelHandshakeTimestampMinutes mm:ss (%@)" = "%@ minutes";
"tunnelPeerPresharedKeyEnabled" = "enabled";
// Error alerts while creating / editing a tunnel configuration // Error alerts while creating / editing a tunnel configuration
/* Alert title for error in the interface data */ /* Alert title for error in the interface data */
@@ -166,17 +209,18 @@
"settingsSectionTitleExportConfigurations" = "Export configurations"; "settingsSectionTitleExportConfigurations" = "Export configurations";
"settingsExportZipButtonTitle" = "Export zip archive"; "settingsExportZipButtonTitle" = "Export zip archive";
"settingsSectionTitleTunnelLog" = "Tunnel log"; "settingsSectionTitleTunnelLog" = "Log";
"settingsExportLogFileButtonTitle" = "Export log file"; "settingsViewLogButtonTitle" = "View log";
// Settings alerts // Log view
"logViewTitle" = "Log";
// Log alerts
"alertUnableToRemovePreviousLogTitle" = "Log export failed"; "alertUnableToRemovePreviousLogTitle" = "Log export failed";
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared"; "alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
"alertUnableToFindExtensionLogPathTitle" = "Log export failed";
"alertUnableToFindExtensionLogPathMessage" = "Unable to determine extension log path";
"alertUnableToWriteLogTitle" = "Log export failed"; "alertUnableToWriteLogTitle" = "Log export failed";
"alertUnableToWriteLogMessage" = "Unable to write logs to file"; "alertUnableToWriteLogMessage" = "Unable to write logs to file";
@@ -197,6 +241,11 @@
"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive"; "alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in zip archive";
"alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive."; "alertNoTunnelsInImportedZipArchiveMessage" = "No .conf tunnel files were found inside the zip archive.";
// Conf import error alerts
"alertCantOpenInputConfFileTitle" = "Unable to import from file";
"alertCantOpenInputConfFileMessage (%@)" = "The file %@ could not be read.";
// Tunnel management error alerts // Tunnel management error alerts
"alertTunnelActivationFailureTitle" = "Activation failure"; "alertTunnelActivationFailureTitle" = "Activation failure";
@@ -251,7 +300,7 @@
"macMenuManageTunnels" = "Manage tunnels"; "macMenuManageTunnels" = "Manage tunnels";
"macMenuImportTunnels" = "Import tunnel(s) from file…"; "macMenuImportTunnels" = "Import tunnel(s) from file…";
"macMenuAddEmptyTunnel" = "Add empty tunnel…"; "macMenuAddEmptyTunnel" = "Add empty tunnel…";
"macMenuExportLog" = "Export log to file…"; "macMenuViewLog" = "View log";
"macMenuExportTunnels" = "Export tunnels to zip…"; "macMenuExportTunnels" = "Export tunnels to zip…";
"macMenuAbout" = "About WireGuard"; "macMenuAbout" = "About WireGuard";
"macMenuQuit" = "Quit"; "macMenuQuit" = "Quit";
@@ -261,23 +310,28 @@
"macWindowTitleManageTunnels" = "Manage WireGuard Tunnels"; "macWindowTitleManageTunnels" = "Manage WireGuard Tunnels";
"macDeleteTunnelConfirmationAlertMessage (%@)" = "Are you sure you want to delete %@?"; "macDeleteTunnelConfirmationAlertMessage (%@)" = "Are you sure you want to delete %@?";
"macDeleteMultipleTunnelsConfirmationAlertMessage (%d)" = "Are you sure you want to delete %d tunnels?";
"macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action."; "macDeleteTunnelConfirmationAlertInfo" = "You cannot undo this action.";
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete"; "macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel"; "macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Deleting…";
"macButtonImportTunnels" = "Import tunnel(s) from file"; "macButtonImportTunnels" = "Import tunnel(s) from file";
"macSheetButtonImport" = "Import"; "macSheetButtonImport" = "Import";
"macNameFieldExportLog" = "Export log to"; "macNameFieldExportLog" = "Save log to:";
"macSheetButtonExportLog" = "Save"; "macSheetButtonExportLog" = "Save";
"macNameFieldExportZip" = "Export tunnels to"; "macNameFieldExportZip" = "Export tunnels to:";
"macSheetButtonExportZip" = "Save"; "macSheetButtonExportZip" = "Save";
"macButtonDeleteTunnels (%d)" = "Delete %d tunnels";
// Mac detail/edit view fields // Mac detail/edit view fields
"macFieldKey (%@)" = "%@:"; "macFieldKey (%@)" = "%@:";
"macFieldOnDemand" = "On-Demand:"; "macFieldOnDemand" = "On-Demand:";
"macFieldOnDemandSSIDs" = "SSIDs:";
// Mac status display // Mac status display
@@ -331,3 +385,23 @@
"macAppExitingWithActiveTunnelMessage" = "WireGuard is exiting with an active tunnel"; "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."; "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
"macToolTipEditTunnel" = "Edit tunnel (⌘E)";
"macToolTipToggleStatus" = "Toggle status (⌘T)";
// Mac log view
"macLogColumnTitleTime" = "Time";
"macLogColumnTitleLogMessage" = "Log message";
"macLogButtonTitleClose" = "Close";
"macLogButtonTitleSave" = "Save…";
// Mac unusable tunnel view
"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";
+2 -2
View File
@@ -1,2 +1,2 @@
VERSION_NAME = 0.0.20190207 VERSION_NAME = 0.0.20190409
VERSION_ID = 2 VERSION_ID = 7
+3 -3
View File
@@ -9,7 +9,7 @@ struct Curve25519 {
static func generatePrivateKey() -> Data { static func generatePrivateKey() -> Data {
var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength) var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
privateKey.withUnsafeMutableBytes { bytes in privateKey.withUnsafeMutableUInt8Bytes { bytes in
curve25519_generate_private_key(bytes) curve25519_generate_private_key(bytes)
} }
assert(privateKey.count == TunnelConfiguration.keyLength) assert(privateKey.count == TunnelConfiguration.keyLength)
@@ -19,8 +19,8 @@ struct Curve25519 {
static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data { static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data {
assert(privateKey.count == TunnelConfiguration.keyLength) assert(privateKey.count == TunnelConfiguration.keyLength)
var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength) var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
privateKey.withUnsafeBytes { privateKeyBytes in privateKey.withUnsafeUInt8Bytes { privateKeyBytes in
publicKey.withUnsafeMutableBytes { bytes in publicKey.withUnsafeMutableUInt8Bytes { bytes in
curve25519_derive_public_key(bytes, privateKeyBytes) curve25519_derive_public_key(bytes, privateKeyBytes)
} }
} }
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import NetworkExtension
enum ActivateOnDemandOption: Equatable {
case off
case wiFiInterfaceOnly(ActivateOnDemandSSIDOption)
case nonWiFiInterfaceOnly
case anyInterface(ActivateOnDemandSSIDOption)
}
#if os(iOS)
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .cellular
#elseif os(macOS)
private let nonWiFiInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
#else
#error("Unimplemented")
#endif
enum ActivateOnDemandSSIDOption: Equatable {
case anySSID
case onlySpecificSSIDs([String])
case exceptSpecificSSIDs([String])
}
extension ActivateOnDemandOption {
func apply(on tunnelProviderManager: NETunnelProviderManager) {
let rules: [NEOnDemandRule]?
switch self {
case .off:
rules = nil
case .wiFiInterfaceOnly(let ssidOption):
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleDisconnect(interfaceType: nonWiFiInterfaceType)]
case .nonWiFiInterfaceOnly:
rules = [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType), NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
case .anyInterface(let ssidOption):
if case .anySSID = ssidOption {
rules = [NEOnDemandRuleConnect(interfaceType: .any)]
} else {
rules = ssidOnDemandRules(option: ssidOption) + [NEOnDemandRuleConnect(interfaceType: nonWiFiInterfaceType)]
}
}
tunnelProviderManager.onDemandRules = rules
tunnelProviderManager.isOnDemandEnabled = self != .off
}
init(from tunnelProviderManager: NETunnelProviderManager) {
let rules = tunnelProviderManager.onDemandRules ?? []
let activateOnDemandOption: ActivateOnDemandOption
switch rules.count {
case 0:
activateOnDemandOption = .off
case 1:
let rule = rules[0]
precondition(rule.action == .connect)
activateOnDemandOption = .anyInterface(.anySSID)
case 2:
let connectRule = rules.first(where: { $0.action == .connect })!
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == nonWiFiInterfaceType {
activateOnDemandOption = .wiFiInterfaceOnly(.anySSID)
} else if connectRule.interfaceTypeMatch == nonWiFiInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
activateOnDemandOption = .nonWiFiInterfaceOnly
} else {
fatalError("Unexpected onDemandRules set on tunnel provider manager")
}
case 3:
let ssidRule = rules.first(where: { $0.interfaceTypeMatch == .wiFi && $0.ssidMatch != nil })!
let nonWiFiRule = rules.first(where: { $0.interfaceTypeMatch == nonWiFiInterfaceType })!
let ssids = ssidRule.ssidMatch!
switch (ssidRule.action, nonWiFiRule.action) {
case (.connect, .connect):
activateOnDemandOption = .anyInterface(.onlySpecificSSIDs(ssids))
case (.connect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.onlySpecificSSIDs(ssids))
case (.disconnect, .connect):
activateOnDemandOption = .anyInterface(.exceptSpecificSSIDs(ssids))
case (.disconnect, .disconnect):
activateOnDemandOption = .wiFiInterfaceOnly(.exceptSpecificSSIDs(ssids))
default:
fatalError("Unexpected SSID onDemandRules set on tunnel provider manager")
}
default:
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
}
self = activateOnDemandOption
}
}
private extension NEOnDemandRuleConnect {
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
self.init()
interfaceTypeMatch = interfaceType
ssidMatch = ssids
}
}
private extension NEOnDemandRuleDisconnect {
convenience init(interfaceType: NEOnDemandRuleInterfaceType, ssids: [String]? = nil) {
self.init()
interfaceTypeMatch = interfaceType
ssidMatch = ssids
}
}
private func ssidOnDemandRules(option: ActivateOnDemandSSIDOption) -> [NEOnDemandRule] {
switch option {
case .anySSID:
return [NEOnDemandRuleConnect(interfaceType: .wiFi)]
case .onlySpecificSSIDs(let ssids):
assert(!ssids.isEmpty)
return [NEOnDemandRuleConnect(interfaceType: .wiFi, ssids: ssids),
NEOnDemandRuleDisconnect(interfaceType: .wiFi)]
case .exceptSpecificSSIDs(let ssids):
return [NEOnDemandRuleDisconnect(interfaceType: .wiFi, ssids: ssids),
NEOnDemandRuleConnect(interfaceType: .wiFi)]
}
}
@@ -1,109 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import NetworkExtension
struct ActivateOnDemandSetting {
var isActivateOnDemandEnabled: Bool
var activateOnDemandOption: ActivateOnDemandOption
}
enum ActivateOnDemandOption {
case none // Valid only when isActivateOnDemandEnabled is false
case useOnDemandOverWiFiOnly
#if os(iOS)
case useOnDemandOverWiFiOrCellular
case useOnDemandOverCellularOnly
#elseif os(macOS)
case useOnDemandOverWiFiOrEthernet
case useOnDemandOverEthernetOnly
#else
#error("Unimplemented")
#endif
}
extension ActivateOnDemandSetting {
func apply(on tunnelProviderManager: NETunnelProviderManager) {
tunnelProviderManager.isOnDemandEnabled = isActivateOnDemandEnabled
let rules: [NEOnDemandRule]?
let connectRule = NEOnDemandRuleConnect()
let disconnectRule = NEOnDemandRuleDisconnect()
switch activateOnDemandOption {
case .none:
rules = nil
#if os(iOS)
case .useOnDemandOverWiFiOrCellular:
rules = [connectRule]
case .useOnDemandOverWiFiOnly:
connectRule.interfaceTypeMatch = .wiFi
disconnectRule.interfaceTypeMatch = .cellular
rules = [connectRule, disconnectRule]
case .useOnDemandOverCellularOnly:
connectRule.interfaceTypeMatch = .cellular
disconnectRule.interfaceTypeMatch = .wiFi
rules = [connectRule, disconnectRule]
#elseif os(macOS)
case .useOnDemandOverWiFiOrEthernet:
rules = [connectRule]
case .useOnDemandOverWiFiOnly:
connectRule.interfaceTypeMatch = .wiFi
disconnectRule.interfaceTypeMatch = .ethernet
rules = [connectRule, disconnectRule]
case .useOnDemandOverEthernetOnly:
connectRule.interfaceTypeMatch = .ethernet
disconnectRule.interfaceTypeMatch = .wiFi
rules = [connectRule, disconnectRule]
#else
#error("Unimplemented")
#endif
}
tunnelProviderManager.onDemandRules = rules
}
init(from tunnelProviderManager: NETunnelProviderManager) {
let rules = tunnelProviderManager.onDemandRules ?? []
#if os(iOS)
let otherInterfaceType: NEOnDemandRuleInterfaceType = .cellular
let useWiFiOrOtherOption: ActivateOnDemandOption = .useOnDemandOverWiFiOrCellular
let useOtherOnlyOption: ActivateOnDemandOption = .useOnDemandOverCellularOnly
#elseif os(macOS)
let otherInterfaceType: NEOnDemandRuleInterfaceType = .ethernet
let useWiFiOrOtherOption: ActivateOnDemandOption = .useOnDemandOverWiFiOrEthernet
let useOtherOnlyOption: ActivateOnDemandOption = .useOnDemandOverEthernetOnly
#else
#error("Unimplemented")
#endif
let activateOnDemandOption: ActivateOnDemandOption
switch rules.count {
case 0:
activateOnDemandOption = .none
case 1:
let rule = rules[0]
precondition(rule.action == .connect)
activateOnDemandOption = useWiFiOrOtherOption
case 2:
let connectRule = rules.first(where: { $0.action == .connect })!
let disconnectRule = rules.first(where: { $0.action == .disconnect })!
if connectRule.interfaceTypeMatch == .wiFi && disconnectRule.interfaceTypeMatch == otherInterfaceType {
activateOnDemandOption = .useOnDemandOverWiFiOnly
} else if connectRule.interfaceTypeMatch == otherInterfaceType && disconnectRule.interfaceTypeMatch == .wiFi {
activateOnDemandOption = useOtherOnlyOption
} else {
fatalError("Unexpected onDemandRules set on tunnel provider manager")
}
default:
fatalError("Unexpected number of onDemandRules set on tunnel provider manager")
}
self.activateOnDemandOption = activateOnDemandOption
if activateOnDemandOption == .none {
isActivateOnDemandEnabled = false
} else {
isActivateOnDemandEnabled = tunnelProviderManager.isOnDemandEnabled
}
}
}
extension ActivateOnDemandSetting {
static var defaultSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: false, activateOnDemandOption: .none)
}
@@ -27,6 +27,8 @@ import NetworkExtension
self = .reasserting self = .reasserting
case .invalid: case .invalid:
self = .inactive self = .inactive
@unknown default:
fatalError()
} }
} }
} }
@@ -54,6 +56,8 @@ extension NEVPNStatus: CustomDebugStringConvertible {
case .disconnecting: return "disconnecting" case .disconnecting: return "disconnecting"
case .reasserting: return "reasserting" case .reasserting: return "reasserting"
case .invalid: return "invalid" case .invalid: return "invalid"
@unknown default:
fatalError()
} }
} }
} }
+119 -36
View File
@@ -28,7 +28,7 @@ class TunnelsManager {
private var configurationsObservationToken: AnyObject? private var configurationsObservationToken: AnyObject?
init(tunnelProviders: [NETunnelProviderManager]) { init(tunnelProviders: [NETunnelProviderManager]) {
tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { $0.name < $1.name } tunnels = tunnelProviders.map { TunnelContainer(tunnel: $0) }.sorted { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
startObservingTunnelStatuses() startObservingTunnelStatuses()
startObservingTunnelConfigurations() startObservingTunnelConfigurations()
} }
@@ -47,11 +47,18 @@ class TunnelsManager {
var tunnelManagers = managers ?? [] var tunnelManagers = managers ?? []
var refs: Set<Data> = [] var refs: Set<Data> = []
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() { for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
if proto?.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") ?? false { if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
tunnelManager.saveToPreferences { _ in } tunnelManager.saveToPreferences { _ in }
} }
if let ref = proto?.verifyConfigurationReference() { #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
#else
#error("Unimplemented")
#endif
if let ref = passwordRef {
refs.insert(ref) refs.insert(ref)
} else { } else {
tunnelManager.removeFromPreferences { _ in } tunnelManager.removeFromPreferences { _ in }
@@ -71,28 +78,33 @@ class TunnelsManager {
let loadedTunnelProviders = managers ?? [] let loadedTunnelProviders = managers ?? []
for (index, currentTunnel) in self.tunnels.enumerated().reversed() { for (index, currentTunnel) in self.tunnels.enumerated().reversed() {
if !loadedTunnelProviders.contains(where: { $0.tunnelConfiguration == currentTunnel.tunnelConfiguration }) { if !loadedTunnelProviders.contains(where: { $0.isEquivalentTo(currentTunnel) }) {
// Tunnel was deleted outside the app // Tunnel was deleted outside the app
self.tunnels.remove(at: index) self.tunnels.remove(at: index)
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel) self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel)
} }
} }
for loadedTunnelProvider in loadedTunnelProviders { for loadedTunnelProvider in loadedTunnelProviders {
if let matchingTunnel = self.tunnels.first(where: { $0.tunnelConfiguration == loadedTunnelProvider.tunnelConfiguration }) { if let matchingTunnel = self.tunnels.first(where: { loadedTunnelProvider.isEquivalentTo($0) }) {
matchingTunnel.tunnelProvider = loadedTunnelProvider matchingTunnel.tunnelProvider = loadedTunnelProvider
matchingTunnel.refreshStatus() matchingTunnel.refreshStatus()
} else { } else {
// Tunnel was added outside the app // Tunnel was added outside the app
if let proto = loadedTunnelProvider.protocolConfiguration as? NETunnelProviderProtocol {
if proto.migrateConfigurationIfNeeded(called: loadedTunnelProvider.localizedDescription ?? "unknown") {
loadedTunnelProvider.saveToPreferences { _ in }
}
}
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider) let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
self.tunnels.append(tunnel) self.tunnels.append(tunnel)
self.tunnels.sort { $0.name < $1.name } self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!) self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
} }
} }
} }
} }
func add(tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting = ActivateOnDemandSetting.defaultSetting, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) { func add(tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption = .off, completionHandler: @escaping (WireGuardResult<TunnelContainer>) -> Void) {
let tunnelName = tunnelConfiguration.name ?? "" let tunnelName = tunnelConfiguration.name ?? ""
if tunnelName.isEmpty { if tunnelName.isEmpty {
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty)) completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
@@ -108,7 +120,9 @@ class TunnelsManager {
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration) tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
tunnelProviderManager.isEnabled = true tunnelProviderManager.isEnabled = true
activateOnDemandSetting.apply(on: tunnelProviderManager) onDemandOption.apply(on: tunnelProviderManager)
let activeTunnel = tunnels.first { $0.status == .active || $0.status == .activating }
tunnelProviderManager.saveToPreferences { [weak self] error in tunnelProviderManager.saveToPreferences { [weak self] error in
guard error == nil else { guard error == nil else {
@@ -120,32 +134,47 @@ class TunnelsManager {
guard let self = self else { return } guard let self = self else { return }
#if os(iOS)
// HACK: In iOS, adding a tunnel causes deactivation of any currently active tunnel.
// This is an ugly hack to reactivate the tunnel that has been deactivated like that.
if let activeTunnel = activeTunnel {
if activeTunnel.status == .inactive || activeTunnel.status == .deactivating {
self.startActivation(of: activeTunnel)
}
if activeTunnel.status == .active || activeTunnel.status == .activating {
activeTunnel.status = .restarting
}
}
#endif
let tunnel = TunnelContainer(tunnel: tunnelProviderManager) let tunnel = TunnelContainer(tunnel: tunnelProviderManager)
self.tunnels.append(tunnel) self.tunnels.append(tunnel)
self.tunnels.sort { $0.name < $1.name } self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!) self.tunnelsListDelegate?.tunnelAdded(at: self.tunnels.firstIndex(of: tunnel)!)
completionHandler(.success(tunnel)) completionHandler(.success(tunnel))
} }
} }
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) { func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, completionHandler: completionHandler) addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, lastError: nil, completionHandler: completionHandler)
} }
private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, completionHandler: @escaping (UInt) -> Void) { private func addMultiple(tunnelConfigurations: ArraySlice<TunnelConfiguration>, numberSuccessful: UInt, lastError: TunnelsManagerError?, completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
guard let head = tunnelConfigurations.first else { guard let head = tunnelConfigurations.first else {
completionHandler(numberSuccessful) completionHandler(numberSuccessful, lastError)
return return
} }
let tail = tunnelConfigurations.dropFirst() let tail = tunnelConfigurations.dropFirst()
add(tunnelConfiguration: head) { [weak self, tail] result in add(tunnelConfiguration: head) { [weak self, tail] result in
DispatchQueue.main.async { DispatchQueue.main.async {
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful + (result.isSuccess ? 1 : 0), completionHandler: completionHandler) let numberSuccessful = numberSuccessful + (result.isSuccess ? 1 : 0)
let lastError = lastError ?? (result.error as? TunnelsManagerError)
self?.addMultiple(tunnelConfigurations: tail, numberSuccessful: numberSuccessful, lastError: lastError, completionHandler: completionHandler)
} }
} }
} }
func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, activateOnDemandSetting: ActivateOnDemandSetting, completionHandler: @escaping (TunnelsManagerError?) -> Void) { func modify(tunnel: TunnelContainer, tunnelConfiguration: TunnelConfiguration, onDemandOption: ActivateOnDemandOption, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
let tunnelName = tunnelConfiguration.name ?? "" let tunnelName = tunnelConfiguration.name ?? ""
if tunnelName.isEmpty { if tunnelName.isEmpty {
completionHandler(TunnelsManagerError.tunnelNameEmpty) completionHandler(TunnelsManagerError.tunnelNameEmpty)
@@ -162,11 +191,15 @@ class TunnelsManager {
tunnel.name = tunnelName tunnel.name = tunnelName
} }
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration) var isTunnelConfigurationChanged = false
if tunnelProviderManager.tunnelConfiguration != tunnelConfiguration {
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
isTunnelConfigurationChanged = true
}
tunnelProviderManager.isEnabled = true tunnelProviderManager.isEnabled = true
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && onDemandOption != .off
activateOnDemandSetting.apply(on: tunnelProviderManager) onDemandOption.apply(on: tunnelProviderManager)
tunnelProviderManager.saveToPreferences { [weak self] error in tunnelProviderManager.saveToPreferences { [weak self] error in
guard error == nil else { guard error == nil else {
@@ -178,16 +211,18 @@ class TunnelsManager {
guard let self = self else { return } guard let self = self else { return }
if isNameChanged { if isNameChanged {
let oldIndex = self.tunnels.firstIndex(of: tunnel)! let oldIndex = self.tunnels.firstIndex(of: tunnel)!
self.tunnels.sort { $0.name < $1.name } self.tunnels.sort { TunnelsManager.tunnelNameIsLessThan($0.name, $1.name) }
let newIndex = self.tunnels.firstIndex(of: tunnel)! let newIndex = self.tunnels.firstIndex(of: tunnel)!
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex) self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
} }
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!) self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting { if isTunnelConfigurationChanged {
// Turn off the tunnel, and then turn it back on, so the changes are made effective if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
tunnel.status = .restarting // Turn off the tunnel, and then turn it back on, so the changes are made effective
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel() tunnel.status = .restarting
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
}
} }
if isActivatingOnDemand { if isActivatingOnDemand {
@@ -210,7 +245,9 @@ class TunnelsManager {
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) { func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
let tunnelProviderManager = tunnel.tunnelProvider let tunnelProviderManager = tunnel.tunnelProvider
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() if tunnel.isTunnelConfigurationAvailableInKeychain {
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
}
tunnelProviderManager.removeFromPreferences { [weak self] error in tunnelProviderManager.removeFromPreferences { [weak self] error in
guard error == nil else { guard error == nil else {
@@ -218,8 +255,7 @@ class TunnelsManager {
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!)) completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!))
return return
} }
if let self = self { if let self = self, let index = self.tunnels.firstIndex(of: tunnel) {
let index = self.tunnels.firstIndex(of: tunnel)!
self.tunnels.remove(at: index) self.tunnels.remove(at: index)
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel) self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
} }
@@ -227,6 +263,27 @@ class TunnelsManager {
} }
} }
func removeMultiple(tunnels: [TunnelContainer], completionHandler: @escaping (TunnelsManagerError?) -> Void) {
removeMultiple(tunnels: ArraySlice(tunnels), completionHandler: completionHandler)
}
private func removeMultiple(tunnels: ArraySlice<TunnelContainer>, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
guard let head = tunnels.first else {
completionHandler(nil)
return
}
let tail = tunnels.dropFirst()
remove(tunnel: head) { [weak self, tail] error in
DispatchQueue.main.async {
if let error = error {
completionHandler(error)
} else {
self?.removeMultiple(tunnels: tail, completionHandler: completionHandler)
}
}
}
}
func numberOfTunnels() -> Int { func numberOfTunnels() -> Int {
return tunnels.count return tunnels.count
} }
@@ -313,11 +370,7 @@ class TunnelsManager {
guard let self = self, guard let self = self,
let session = statusChangeNotification.object as? NETunnelProviderSession, let session = statusChangeNotification.object as? NETunnelProviderSession,
let tunnelProvider = session.manager as? NETunnelProviderManager, let tunnelProvider = session.manager as? NETunnelProviderManager,
let tunnelConfiguration = tunnelProvider.tunnelConfiguration, let tunnel = self.tunnels.first(where: { $0.tunnelProvider == tunnelProvider }) else { return }
let tunnel = self.tunnels.first(where: { $0.tunnelConfiguration == tunnelConfiguration }) else { return }
if tunnel.tunnelProvider != tunnelProvider {
return
}
wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'") wg_log(.debug, message: "Tunnel '\(tunnel.name)' connection status changed to '\(tunnel.tunnelProvider.connection.status)'")
@@ -356,6 +409,9 @@ class TunnelsManager {
} }
} }
static func tunnelNameIsLessThan(_ a: String, _ b: String) -> Bool {
return a.compare(b, options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive, .numeric]) == .orderedAscending
}
} }
private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? { private func lastErrorTextFromNetworkExtension(for tunnel: TunnelContainer) -> (title: String, message: String)? {
@@ -408,8 +464,12 @@ class TunnelContainer: NSObject {
return tunnelProvider.tunnelConfiguration return tunnelProvider.tunnelConfiguration
} }
var activateOnDemandSetting: ActivateOnDemandSetting { var isTunnelConfigurationAvailableInKeychain: Bool {
return ActivateOnDemandSetting(from: tunnelProvider) return tunnelProvider.isTunnelConfigurationAvailableInKeychain
}
var onDemandOption: ActivateOnDemandOption {
return ActivateOnDemandOption(from: tunnelProvider)
} }
init(tunnel: NETunnelProviderManager) { init(tunnel: NETunnelProviderManager) {
@@ -426,7 +486,7 @@ class TunnelContainer: NSObject {
completionHandler(tunnelConfiguration) completionHandler(tunnelConfiguration)
return return
} }
guard nil != (try? session.sendProviderMessage(Data(bytes: [ 0 ]), responseHandler: { guard nil != (try? session.sendProviderMessage(Data([ UInt8(0) ]), responseHandler: {
guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else { guard self.status != .inactive, let data = $0, let base = self.tunnelConfiguration, let settings = String(data: data, encoding: .utf8) else {
completionHandler(self.tunnelConfiguration) completionHandler(self.tunnelConfiguration)
return return
@@ -439,7 +499,7 @@ class TunnelContainer: NSObject {
} }
func refreshStatus() { func refreshStatus() {
if (status == .restarting) && (tunnelProvider.connection.status == .disconnected || tunnelProvider.connection.status == .disconnecting) { if status == .restarting {
return return
} }
status = TunnelStatus(from: tunnelProvider.connection.status) status = TunnelStatus(from: tunnelProvider.connection.status)
@@ -520,7 +580,18 @@ class TunnelContainer: NSObject {
} }
extension NETunnelProviderManager { extension NETunnelProviderManager {
private static var cachedIsConfigAvailableInKeychainKey: UInt8 = 0
private static var cachedConfigKey: 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? { var tunnelConfiguration: TunnelConfiguration? {
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration { if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
return cached return cached
@@ -536,5 +607,17 @@ extension NETunnelProviderManager {
protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration) protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration)
localizedDescription = tunnelConfiguration.name localizedDescription = tunnelConfiguration.name
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 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
}
} }
} }
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Foundation
class ActivateOnDemandViewModel {
enum OnDemandField {
case onDemand
case nonWiFiInterface
case wiFiInterface
case ssid
var localizedUIString: String {
switch self {
case .onDemand:
return tr("tunnelOnDemandKey")
case .nonWiFiInterface:
#if os(iOS)
return tr("tunnelOnDemandCellular")
#elseif os(macOS)
return tr("tunnelOnDemandEthernet")
#else
#error("Unimplemented")
#endif
case .wiFiInterface: return tr("tunnelOnDemandWiFi")
case .ssid: return tr("tunnelOnDemandSSIDsKey")
}
}
}
enum OnDemandSSIDOption {
case anySSID
case onlySpecificSSIDs
case exceptSpecificSSIDs
var localizedUIString: String {
switch self {
case .anySSID: return tr("tunnelOnDemandAnySSID")
case .onlySpecificSSIDs: return tr("tunnelOnDemandOnlyTheseSSIDs")
case .exceptSpecificSSIDs: return tr("tunnelOnDemandExceptTheseSSIDs")
}
}
}
var isNonWiFiInterfaceEnabled = false
var isWiFiInterfaceEnabled = false
var selectedSSIDs = [String]()
var ssidOption: OnDemandSSIDOption = .anySSID
}
extension ActivateOnDemandViewModel {
convenience init(tunnel: TunnelContainer) {
self.init()
if tunnel.isActivateOnDemandEnabled {
switch tunnel.onDemandOption {
case .off:
break
case .wiFiInterfaceOnly(let onDemandSSIDOption):
isWiFiInterfaceEnabled = true
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
case .nonWiFiInterfaceOnly:
isNonWiFiInterfaceEnabled = true
case .anyInterface(let onDemandSSIDOption):
isWiFiInterfaceEnabled = true
isNonWiFiInterfaceEnabled = true
(ssidOption, selectedSSIDs) = ssidViewModel(from: onDemandSSIDOption)
}
}
}
func toOnDemandOption() -> ActivateOnDemandOption {
switch (isWiFiInterfaceEnabled, isNonWiFiInterfaceEnabled) {
case (false, false):
return .off
case (false, true):
return .nonWiFiInterfaceOnly
case (true, false):
return .wiFiInterfaceOnly(toSSIDOption())
case (true, true):
return .anyInterface(toSSIDOption())
}
}
}
extension ActivateOnDemandViewModel {
func isEnabled(field: OnDemandField) -> Bool {
switch field {
case .nonWiFiInterface:
return isNonWiFiInterfaceEnabled
case .wiFiInterface:
return isWiFiInterfaceEnabled
default:
return false
}
}
func setEnabled(field: OnDemandField, isEnabled: Bool) {
switch field {
case .nonWiFiInterface:
isNonWiFiInterfaceEnabled = isEnabled
case .wiFiInterface:
isWiFiInterfaceEnabled = isEnabled
default:
break
}
}
}
extension ActivateOnDemandViewModel {
var localizedInterfaceDescription: String {
switch (isWiFiInterfaceEnabled, isNonWiFiInterfaceEnabled) {
case (false, false):
return tr("tunnelOnDemandOptionOff")
case (true, false):
return tr("tunnelOnDemandOptionWiFiOnly")
case (false, true):
#if os(iOS)
return tr("tunnelOnDemandOptionCellularOnly")
#elseif os(macOS)
return tr("tunnelOnDemandOptionEthernetOnly")
#else
#error("Unimplemented")
#endif
case (true, true):
#if os(iOS)
return tr("tunnelOnDemandOptionWiFiOrCellular")
#elseif os(macOS)
return tr("tunnelOnDemandOptionWiFiOrEthernet")
#else
#error("Unimplemented")
#endif
}
}
var localizedSSIDDescription: String {
guard isWiFiInterfaceEnabled else { return "" }
switch ssidOption {
case .anySSID: return tr("tunnelOnDemandAnySSID")
case .onlySpecificSSIDs:
if selectedSSIDs.count == 1 {
return tr(format: "tunnelOnDemandOnlySSID (%d)", selectedSSIDs.count)
} else {
return tr(format: "tunnelOnDemandOnlySSIDs (%d)", selectedSSIDs.count)
}
case .exceptSpecificSSIDs:
if selectedSSIDs.count == 1 {
return tr(format: "tunnelOnDemandExceptSSID (%d)", selectedSSIDs.count)
} else {
return tr(format: "tunnelOnDemandExceptSSIDs (%d)", selectedSSIDs.count)
}
}
}
func fixSSIDOption() {
selectedSSIDs = uniquifiedNonEmptySelectedSSIDs()
if selectedSSIDs.isEmpty {
ssidOption = .anySSID
}
}
}
private extension ActivateOnDemandViewModel {
func ssidViewModel(from ssidOption: ActivateOnDemandSSIDOption) -> (OnDemandSSIDOption, [String]) {
switch ssidOption {
case .anySSID:
return (.anySSID, [])
case .onlySpecificSSIDs(let ssids):
return (.onlySpecificSSIDs, ssids)
case .exceptSpecificSSIDs(let ssids):
return (.exceptSpecificSSIDs, ssids)
}
}
func toSSIDOption() -> ActivateOnDemandSSIDOption {
switch ssidOption {
case .anySSID:
return .anySSID
case .onlySpecificSSIDs:
let ssids = uniquifiedNonEmptySelectedSSIDs()
return ssids.isEmpty ? .anySSID : .onlySpecificSSIDs(selectedSSIDs)
case .exceptSpecificSSIDs:
let ssids = uniquifiedNonEmptySelectedSSIDs()
return ssids.isEmpty ? .anySSID : .exceptSpecificSSIDs(selectedSSIDs)
}
}
func uniquifiedNonEmptySelectedSSIDs() -> [String] {
let nonEmptySSIDs = selectedSSIDs.filter { !$0.isEmpty }
var seenSSIDs = Set<String>()
var uniquified = [String]()
for ssid in nonEmptySSIDs {
guard !seenSSIDs.contains(ssid) else { continue }
uniquified.append(ssid)
seenSSIDs.insert(ssid)
}
return uniquified
}
}
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Foundation
public class LogViewHelper {
var log: OpaquePointer
var cursor: UInt32 = UINT32_MAX
static let formatOptions: ISO8601DateFormatter.Options = [
.withYear, .withMonth, .withDay, .withTime,
.withDashSeparatorInDate, .withColonSeparatorInTime, .withSpaceBetweenDateAndTime,
.withFractionalSeconds
]
struct LogEntry {
let timestamp: String
let message: String
func text() -> String {
return timestamp + " " + message
}
}
class LogEntries {
var entries: [LogEntry] = []
}
init?(logFilePath: String?) {
guard let logFilePath = logFilePath else { return nil }
guard let log = open_log(logFilePath) else { return nil }
self.log = log
}
deinit {
close_log(self.log)
}
func fetchLogEntriesSinceLastFetch(completion: @escaping ([LogViewHelper.LogEntry]) -> Void) {
var logEntries = LogEntries()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else { return }
let newCursor = view_lines_from_cursor(self.log, self.cursor, &logEntries) { cStr, timestamp, ctx in
let message = cStr != nil ? String(cString: cStr!) : ""
let date = Date(timeIntervalSince1970: Double(timestamp) / 1000000000)
let dateString = ISO8601DateFormatter.string(from: date, timeZone: TimeZone.current, formatOptions: LogViewHelper.formatOptions)
if let logEntries = ctx?.bindMemory(to: LogEntries.self, capacity: 1) {
logEntries.pointee.entries.append(LogEntry(timestamp: dateString, message: message))
}
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.cursor = newCursor
completion(logEntries.entries)
}
}
}
}
+69 -28
View File
@@ -4,40 +4,81 @@
import Foundation import Foundation
class TunnelImporter { class TunnelImporter {
static func importFromFile(url: URL, into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) { static func importFromFile(urls: [URL], into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) {
if url.pathExtension == "zip" { guard !urls.isEmpty else {
ZipImporter.importConfigFiles(from: url) { result in completionHandler?()
if let error = result.error { return
errorPresenterType.showErrorAlert(error: error, from: sourceVC) }
return let dispatchGroup = DispatchGroup()
var configs = [TunnelConfiguration?]()
var lastFileImportErrorText: (title: String, message: String)?
for url in urls {
if url.pathExtension.lowercased() == "zip" {
dispatchGroup.enter()
ZipImporter.importConfigFiles(from: url) { result in
if let error = result.error {
lastFileImportErrorText = error.alertText
}
if let configsInZip = result.value {
configs.append(contentsOf: configsInZip)
}
dispatchGroup.leave()
} }
let configs = result.value! } else { /* if it is not a zip, we assume it is a conf */
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful in let fileName = url.lastPathComponent
if numberSuccessful == configs.count { let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
completionHandler?() dispatchGroup.enter()
DispatchQueue.global(qos: .userInitiated).async {
let fileContents: String
do {
fileContents = try String(contentsOf: url)
} catch let error {
DispatchQueue.main.async {
if let cocoaError = error as? CocoaError, cocoaError.isFileError {
lastFileImportErrorText = (title: tr("alertCantOpenInputConfFileTitle"), message: error.localizedDescription)
} else {
lastFileImportErrorText = (title: tr("alertCantOpenInputConfFileTitle"), message: tr(format: "alertCantOpenInputConfFileMessage (%@)", fileName))
}
configs.append(nil)
dispatchGroup.leave()
}
return return
} }
let title = tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful) let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
let message = tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count) DispatchQueue.main.async {
errorPresenterType.showErrorAlert(title: title, message: message, from: sourceVC, onPresented: completionHandler) if tunnelConfiguration == nil {
} lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
} }
} else /* if (url.pathExtension == "conf") -- we assume everything else is a conf */ { configs.append(tunnelConfiguration)
let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines) dispatchGroup.leave()
if let fileContents = try? String(contentsOf: url),
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName) {
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration) { result in
if let error = result.error {
errorPresenterType.showErrorAlert(error: error, from: sourceVC, onPresented: completionHandler)
} else {
completionHandler?()
} }
} }
} else { }
errorPresenterType.showErrorAlert(title: tr("alertUnableToImportTitle"), message: tr("alertUnableToImportMessage"), }
from: sourceVC, onPresented: completionHandler) dispatchGroup.notify(queue: .main) {
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful, lastAddError in
if !configs.isEmpty && numberSuccessful == configs.count {
completionHandler?()
return
}
let alertText: (title: String, message: String)?
if urls.count == 1 {
if urls.first!.pathExtension.lowercased() == "zip" && !configs.isEmpty {
alertText = (title: tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful),
message: tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count))
} else {
alertText = lastFileImportErrorText ?? lastAddError?.alertText
}
} else {
alertText = (title: tr(format: "alertImportedFromMultipleFilesTitle (%d)", numberSuccessful),
message: tr(format: "alertImportedFromMultipleFilesMessage (%1$d of %2$d)", numberSuccessful, configs.count))
}
if let alertText = alertText {
errorPresenterType.showErrorAlert(title: alertText.title, message: alertText.message, from: sourceVC, onPresented: completionHandler)
} else {
completionHandler?()
}
} }
} }
} }
} }
+78 -100
View File
@@ -14,6 +14,8 @@ class TunnelViewModel {
case listenPort case listenPort
case mtu case mtu
case dns case dns
case status
case toggleStatus
var localizedUIString: String { var localizedUIString: String {
switch self { switch self {
@@ -25,6 +27,8 @@ class TunnelViewModel {
case .listenPort: return tr("tunnelInterfaceListenPort") case .listenPort: return tr("tunnelInterfaceListenPort")
case .mtu: return tr("tunnelInterfaceMTU") case .mtu: return tr("tunnelInterfaceMTU")
case .dns: return tr("tunnelInterfaceDNS") case .dns: return tr("tunnelInterfaceDNS")
case .status: return tr("tunnelInterfaceStatus")
case .toggleStatus: return ""
} }
} }
} }
@@ -67,17 +71,17 @@ class TunnelViewModel {
static let keyLengthInBase64 = 44 static let keyLengthInBase64 = 44
struct ChangeHandlers { struct Changes {
enum FieldChange { enum FieldChange: Equatable {
case added case added
case removed case removed
case modified case modified(newValue: String)
} }
var interfaceChanged: ([InterfaceField: FieldChange]) -> Void var interfaceChanges: [InterfaceField: FieldChange]
var peerChangedAt: (Int, [PeerField: FieldChange]) -> Void var peerChanges: [(peerIndex: Int, changes: [PeerField: FieldChange])]
var peersRemovedAt: ([Int]) -> Void var peersRemovedIndices: [Int]
var peersInsertedAt: ([Int]) -> Void var peersInsertedIndices: [Int]
} }
class InterfaceData { class InterfaceData {
@@ -217,12 +221,12 @@ class TunnelViewModel {
} }
} }
func applyConfiguration(other: InterfaceConfiguration, otherName: String, changeHandler: ([InterfaceField: ChangeHandlers.FieldChange]) -> Void) { func applyConfiguration(other: InterfaceConfiguration, otherName: String) -> [InterfaceField: Changes.FieldChange] {
if scratchpad.isEmpty { if scratchpad.isEmpty {
populateScratchpad() populateScratchpad()
} }
let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName) let otherScratchPad = InterfaceData.createScratchPad(from: other, name: otherName)
var changes = [InterfaceField: ChangeHandlers.FieldChange]() var changes = [InterfaceField: Changes.FieldChange]()
for field in InterfaceField.allCases { for field in InterfaceField.allCases {
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") { switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
case ("", ""): case ("", ""):
@@ -233,14 +237,12 @@ class TunnelViewModel {
changes[field] = .removed changes[field] = .removed
case (let this, let other): case (let this, let other):
if this != other { if this != other {
changes[field] = .modified changes[field] = .modified(newValue: other)
} }
} }
} }
scratchpad = otherScratchPad scratchpad = otherScratchPad
if !changes.isEmpty { return changes
changeHandler(changes)
}
} }
} }
@@ -403,50 +405,60 @@ class TunnelViewModel {
"193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4" "193.0.0.0/8", "194.0.0.0/7", "196.0.0.0/6", "200.0.0.0/5", "208.0.0.0/4"
] ]
static func excludePrivateIPsFieldStates(isSinglePeer: Bool, allowedIPs: Set<String>) -> (shouldAllowExcludePrivateIPsControl: Bool, excludePrivateIPsValue: Bool) {
guard isSinglePeer else {
return (shouldAllowExcludePrivateIPsControl: false, excludePrivateIPsValue: false)
}
let allowedIPStrings = Set<String>(allowedIPs)
if allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) {
return (shouldAllowExcludePrivateIPsControl: true, excludePrivateIPsValue: false)
} else if allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) {
return (shouldAllowExcludePrivateIPsControl: true, excludePrivateIPsValue: true)
} else {
return (shouldAllowExcludePrivateIPsControl: false, excludePrivateIPsValue: false)
}
}
func updateExcludePrivateIPsFieldState() { func updateExcludePrivateIPsFieldState() {
if scratchpad.isEmpty { if scratchpad.isEmpty {
populateScratchpad() populateScratchpad()
} }
let allowedIPStrings = Set<String>(scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)) let allowedIPStrings = Set<String>(scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: numberOfPeers == 1, allowedIPs: allowedIPStrings)
shouldStronglyRecommendDNS = allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) || allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) shouldStronglyRecommendDNS = allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) || allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String)
guard numberOfPeers == 1 else { }
shouldAllowExcludePrivateIPsControl = false
excludePrivateIPsValue = false static func normalizedIPAddressRangeStrings(_ list: [String]) -> [String] {
return return list.compactMap { IPAddressRange(from: $0) }.map { $0.stringRepresentation }
} }
if allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) {
shouldAllowExcludePrivateIPsControl = true static func modifiedAllowedIPs(currentAllowedIPs: [String], excludePrivateIPs: Bool, dnsServers: [String], oldDNSServers: [String]?) -> [String] {
excludePrivateIPsValue = false let normalizedDNSServers = normalizedIPAddressRangeStrings(dnsServers)
} else if allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) { let normalizedOldDNSServers = oldDNSServers == nil ? normalizedDNSServers : normalizedIPAddressRangeStrings(oldDNSServers!)
shouldAllowExcludePrivateIPsControl = true let ipv6Addresses = normalizedIPAddressRangeStrings(currentAllowedIPs.filter { $0.contains(":") })
excludePrivateIPsValue = true if excludePrivateIPs {
return ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + normalizedDNSServers
} else { } else {
shouldAllowExcludePrivateIPsControl = false return ipv6Addresses.filter { !normalizedOldDNSServers.contains($0) } + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
excludePrivateIPsValue = false
} }
} }
func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String) { func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String, oldDNSServers: String? = nil) {
let allowedIPStrings = scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines) let allowedIPStrings = scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
let dnsServerStrings = dnsServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines) let dnsServerStrings = dnsServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") } let oldDNSServerStrings = oldDNSServers?.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
let modifiedAllowedIPStrings: [String] let modifiedAllowedIPStrings = TunnelViewModel.PeerData.modifiedAllowedIPs(currentAllowedIPs: allowedIPStrings, excludePrivateIPs: isOn, dnsServers: dnsServerStrings, oldDNSServers: oldDNSServerStrings)
if isOn {
modifiedAllowedIPStrings = ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
} else {
modifiedAllowedIPStrings = ipv6Addresses + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
}
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ") scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
validatedConfiguration = nil validatedConfiguration = nil
excludePrivateIPsValue = isOn excludePrivateIPsValue = isOn
} }
func applyConfiguration(other: PeerConfiguration, peerIndex: Int, changeHandler: (Int, [PeerField: ChangeHandlers.FieldChange]) -> Void) { func applyConfiguration(other: PeerConfiguration) -> [PeerField: Changes.FieldChange] {
if scratchpad.isEmpty { if scratchpad.isEmpty {
populateScratchpad() populateScratchpad()
} }
let otherScratchPad = PeerData.createScratchPad(from: other) let otherScratchPad = PeerData.createScratchPad(from: other)
var changes = [PeerField: ChangeHandlers.FieldChange]() var changes = [PeerField: Changes.FieldChange]()
for field in PeerField.allCases { for field in PeerField.allCases {
switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") { switch (scratchpad[field] ?? "", otherScratchPad[field] ?? "") {
case ("", ""): case ("", ""):
@@ -457,14 +469,12 @@ class TunnelViewModel {
changes[field] = .removed changes[field] = .removed
case (let this, let other): case (let this, let other):
if this != other { if this != other {
changes[field] = .modified changes[field] = .modified(newValue: other)
} }
} }
} }
scratchpad = otherScratchPad scratchpad = otherScratchPad
if !changes.isEmpty { return changes
changeHandler(peerIndex, changes)
}
} }
} }
@@ -519,6 +529,17 @@ class TunnelViewModel {
} }
} }
func updateDNSServersInAllowedIPsIfRequired(oldDNSServers: String, newDNSServers: String) -> Bool {
guard peersData.count == 1, let firstPeer = peersData.first else { return false }
guard firstPeer.shouldAllowExcludePrivateIPsControl && firstPeer.excludePrivateIPsValue else { return false }
let allowedIPStrings = firstPeer[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
let oldDNSServerStrings = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(oldDNSServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
let newDNSServerStrings = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(newDNSServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
let updatedAllowedIPStrings = allowedIPStrings.filter { !oldDNSServerStrings.contains($0) } + newDNSServerStrings
firstPeer[.allowedIPs] = updatedAllowedIPStrings.joined(separator: ", ")
return true
}
func save() -> SaveResult<TunnelConfiguration> { func save() -> SaveResult<TunnelConfiguration> {
let interfaceSaveResult = interfaceData.save() let interfaceSaveResult = interfaceData.save()
let peerSaveResults = peersData.map { $0.save() } // Save all, to help mark erroring fields in red let peerSaveResults = peersData.map { $0.save() } // Save all, to help mark erroring fields in red
@@ -548,21 +569,28 @@ class TunnelViewModel {
} }
} }
func applyConfiguration(other: TunnelConfiguration, changeHandlers: ChangeHandlers) { func asWgQuickConfig() -> String? {
let saveResult = save()
if case .saved(let tunnelConfiguration) = saveResult {
return tunnelConfiguration.asWgQuickConfig()
}
return nil
}
@discardableResult
func applyConfiguration(other: TunnelConfiguration) -> Changes {
// Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering. // Replaces current data with data from other TunnelConfiguration, ignoring any changes in peer ordering.
// Change handler callbacks are processed in the following order, which is designed to work with both the
// UITableView way (modify - delete - insert) and the NSTableView way (indices are based on past operations):
// - interfaceChanged
// - peerChangedAt
// - peersRemovedAt
// - peersInsertedAt
interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "", changeHandler: changeHandlers.interfaceChanged) let interfaceChanges = interfaceData.applyConfiguration(other: other.interface, otherName: other.name ?? "")
var peerChanges = [(peerIndex: Int, changes: [PeerField: Changes.FieldChange])]()
for otherPeer in other.peers { for otherPeer in other.peers {
if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) { if let peersDataIndex = peersData.firstIndex(where: { $0.publicKey == otherPeer.publicKey }) {
let peerData = peersData[peersDataIndex] let peerData = peersData[peersDataIndex]
peerData.applyConfiguration(other: otherPeer, peerIndex: peersDataIndex, changeHandler: changeHandlers.peerChangedAt) let changes = peerData.applyConfiguration(other: otherPeer)
if !changes.isEmpty {
peerChanges.append((peerIndex: peersDataIndex, changes: changes))
}
} }
} }
@@ -573,9 +601,6 @@ class TunnelViewModel {
peersData.remove(at: index) peersData.remove(at: index)
} }
} }
if !removedPeerIndices.isEmpty {
changeHandlers.peersRemovedAt(removedPeerIndices)
}
var addedPeerIndices = [Int]() var addedPeerIndices = [Int]()
for otherPeer in other.peers { for otherPeer in other.peers {
@@ -586,61 +611,14 @@ class TunnelViewModel {
peersData.append(peerData) peersData.append(peerData)
} }
} }
if !addedPeerIndices.isEmpty {
changeHandlers.peersInsertedAt(addedPeerIndices)
}
for (index, peer) in peersData.enumerated() { for (index, peer) in peersData.enumerated() {
peer.index = index peer.index = index
peer.numberOfPeers = peersData.count peer.numberOfPeers = peersData.count
peer.updateExcludePrivateIPsFieldState() peer.updateExcludePrivateIPsFieldState()
} }
}
}
extension TunnelViewModel { return Changes(interfaceChanges: interfaceChanges, peerChanges: peerChanges, peersRemovedIndices: removedPeerIndices, peersInsertedIndices: addedPeerIndices)
static func activateOnDemandOptionText(for activateOnDemandOption: ActivateOnDemandOption) -> String {
switch activateOnDemandOption {
case .none:
return tr("tunnelOnDemandOptionOff")
case .useOnDemandOverWiFiOnly:
return tr("tunnelOnDemandOptionWiFiOnly")
#if os(iOS)
case .useOnDemandOverWiFiOrCellular:
return tr("tunnelOnDemandOptionWiFiOrCellular")
case .useOnDemandOverCellularOnly:
return tr("tunnelOnDemandOptionCellularOnly")
#elseif os(macOS)
case .useOnDemandOverWiFiOrEthernet:
return tr("tunnelOnDemandOptionWiFiOrEthernet")
case .useOnDemandOverEthernetOnly:
return tr("tunnelOnDemandOptionEthernetOnly")
#else
#error("Unimplemented")
#endif
}
}
static func activateOnDemandDetailText(for activateOnDemandSetting: ActivateOnDemandSetting?) -> String {
if let activateOnDemandSetting = activateOnDemandSetting {
if activateOnDemandSetting.isActivateOnDemandEnabled {
return TunnelViewModel.activateOnDemandOptionText(for: activateOnDemandSetting.activateOnDemandOption)
} else {
return TunnelViewModel.activateOnDemandOptionText(for: .none)
}
} else {
return TunnelViewModel.activateOnDemandOptionText(for: .none)
}
}
static func defaultActivateOnDemandOption() -> ActivateOnDemandOption {
#if os(iOS)
return .useOnDemandOverWiFiOrCellular
#elseif os(macOS)
return .useOnDemandOverWiFiOrEthernet
#else
#error("Unimplemented")
#endif
} }
} }
+2 -2
View File
@@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var mainVC: MainViewController? var mainVC: MainViewController?
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path) Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
let window = UIWindow(frame: UIScreen.main.bounds) let window = UIWindow(frame: UIScreen.main.bounds)
window.backgroundColor = .white window.backgroundColor = .white
@@ -28,7 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
guard let tunnelsManager = mainVC?.tunnelsManager else { return true } guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) { TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
_ = FileManager.deleteFile(at: url) _ = FileManager.deleteFile(at: url)
} }
return true return true
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class ConfirmationAlertPresenter {
static func showConfirmationAlert(message: String, buttonTitle: String, from sourceObject: AnyObject, presentingVC: UIViewController, onConfirmed: @escaping (() -> Void)) {
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
onConfirmed()
}
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
alert.addAction(destroyAction)
alert.addAction(cancelAction)
if let sourceView = sourceObject as? UIView {
alert.popoverPresentationController?.sourceView = sourceView
alert.popoverPresentationController?.sourceRect = sourceView.bounds
} else if let sourceBarButtonItem = sourceObject as? UIBarButtonItem {
alert.popoverPresentationController?.barButtonItem = sourceBarButtonItem
}
presentingVC.present(alert, animated: true, completion: nil)
}
}
+2 -3
View File
@@ -77,12 +77,11 @@
<key>LSSupportsOpeningDocumentsInPlace</key> <key>LSSupportsOpeningDocumentsInPlace</key>
<false/> <false/>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Camera is used for scanning QR codes for importing WireGuard configurations</string> <string>Localized</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array> <array>
<string>armv7</string>
</array> </array>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
@@ -123,7 +122,7 @@
</dict> </dict>
</array> </array>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Face ID is used for authenticating viewing and exporting of private keys</string> <string>Localized</string>
<key>com.wireguard.ios.app_group_id</key> <key>com.wireguard.ios.app_group_id</key>
<string>group.$(APP_ID_IOS)</string> <string>group.$(APP_ID_IOS)</string>
</dict> </dict>
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class ChevronCell: UITableViewCell {
var message: String {
get { return textLabel?.text ?? "" }
set(value) { textLabel?.text = value }
}
var detailMessage: String {
get { return detailTextLabel?.text ?? "" }
set(value) { detailTextLabel?.text = value }
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .value1, reuseIdentifier: reuseIdentifier)
accessoryType = .disclosureIndicator
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
message = ""
}
}
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class EditableTextCell: UITableViewCell {
var message: String {
get { return valueTextField.text ?? "" }
set(value) { valueTextField.text = value }
}
let valueTextField: UITextField = {
let valueTextField = UITextField()
valueTextField.textAlignment = .left
valueTextField.isEnabled = true
valueTextField.font = UIFont.preferredFont(forTextStyle: .body)
valueTextField.adjustsFontForContentSizeCategory = true
valueTextField.autocapitalizationType = .none
valueTextField.autocorrectionType = .no
valueTextField.spellCheckingType = .no
return valueTextField
}()
var onValueBeingEdited: ((String) -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
valueTextField.delegate = self
contentView.addSubview(valueTextField)
valueTextField.translatesAutoresizingMaskIntoConstraints = false
let bottomAnchorConstraint = contentView.layoutMarginsGuide.bottomAnchor.constraint(equalToSystemSpacingBelow: valueTextField.bottomAnchor, multiplier: 1)
bottomAnchorConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
valueTextField.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.layoutMarginsGuide.leadingAnchor, multiplier: 1),
contentView.layoutMarginsGuide.trailingAnchor.constraint(equalToSystemSpacingAfter: valueTextField.trailingAnchor, multiplier: 1),
valueTextField.topAnchor.constraint(equalToSystemSpacingBelow: contentView.layoutMarginsGuide.topAnchor, multiplier: 1),
bottomAnchorConstraint
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func beginEditing() {
valueTextField.becomeFirstResponder()
}
override func prepareForReuse() {
super.prepareForReuse()
message = ""
}
}
extension EditableTextCell: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let onValueBeingEdited = onValueBeingEdited {
let modifiedText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
onValueBeingEdited(modifiedText)
}
return true
}
}
@@ -68,7 +68,7 @@ class KeyValueCell: UITableViewCell {
var isStackedVertically = false var isStackedVertically = false
var contentSizeBasedConstraints = [NSLayoutConstraint]() var contentSizeBasedConstraints = [NSLayoutConstraint]()
var onValueChanged: ((String) -> Void)? var onValueChanged: ((String, String) -> Void)?
var onValueBeingEdited: ((String) -> Void)? var onValueBeingEdited: ((String) -> Void)?
var observationToken: AnyObject? var observationToken: AnyObject?
@@ -206,7 +206,7 @@ extension KeyValueCell: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) { func textFieldDidEndEditing(_ textField: UITextField) {
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
guard isModified else { return } guard isModified else { return }
onValueChanged?(textField.text ?? "") onValueChanged?(textFieldValueOnBeginEditing, textField.text ?? "")
} }
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class TextCell: UITableViewCell {
var message: String {
get { return textLabel?.text ?? "" }
set(value) { textLabel!.text = value }
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setTextColor(_ color: UIColor) {
textLabel?.textColor = color
}
func setTextAlignment(_ alignment: NSTextAlignment) {
textLabel?.textAlignment = alignment
}
override func prepareForReuse() {
super.prepareForReuse()
message = ""
setTextColor(.black)
setTextAlignment(.left)
}
}
@@ -98,6 +98,11 @@ class TunnelListCell: UITableViewCell {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
statusSwitch.isEnabled = !editing
}
private func reset() { private func reset() {
statusSwitch.isOn = false statusSwitch.isOn = false
statusSwitch.isUserInteractionEnabled = false statusSwitch.isUserInteractionEnabled = false
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class LogViewController: UIViewController {
let textView: UITextView = {
let textView = UITextView()
textView.isEditable = false
textView.isSelectable = true
textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
textView.adjustsFontForContentSizeCategory = true
return textView
}()
let busyIndicator: UIActivityIndicatorView = {
let busyIndicator = UIActivityIndicatorView(style: .gray)
busyIndicator.hidesWhenStopped = true
return busyIndicator
}()
var logViewHelper: LogViewHelper?
var isFetchingLogEntries = false
private var updateLogEntriesTimer: Timer?
override func loadView() {
view = UIView()
view.backgroundColor = .white
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.topAnchor),
textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
view.addSubview(busyIndicator)
busyIndicator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
busyIndicator.startAnimating()
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
startUpdatingLogEntries()
}
override func viewDidLoad() {
title = tr("logViewTitle")
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped(sender:)))
}
func updateLogEntries() {
guard !isFetchingLogEntries else { return }
isFetchingLogEntries = true
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
guard let self = self else { return }
defer {
self.isFetchingLogEntries = false
}
if self.busyIndicator.isAnimating {
self.busyIndicator.stopAnimating()
}
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])
self.textView.textStorage.append(richText)
if isScrolledToEnd {
let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
self.textView.scrollRangeToVisible(endOfCurrentText)
}
}
}
func startUpdatingLogEntries() {
updateLogEntries()
updateLogEntriesTimer?.invalidate()
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
self?.updateLogEntries()
}
updateLogEntriesTimer = timer
RunLoop.main.add(timer, forMode: .common)
}
@objc func saveTapped(sender: AnyObject) {
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
DispatchQueue.global(qos: .userInitiated).async {
if FileManager.default.fileExists(atPath: destinationURL.path) {
let isDeleted = FileManager.deleteFile(at: destinationURL)
if !isDeleted {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
return
}
}
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
DispatchQueue.main.async {
guard isWritten else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
return
}
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
if let sender = sender as? UIBarButtonItem {
activityVC.popoverPresentationController?.barButtonItem = sender
}
activityVC.completionWithItemsHandler = { _, _, _, _ in
// Remove the exported log file after the activity has completed
_ = FileManager.deleteFile(at: destinationURL)
}
self.present(activityVC, animated: true)
}
}
}
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
class SSIDOptionDetailTableViewController: UITableViewController {
let selectedSSIDs: [String]
init(title: String, ssids: [String]) {
selectedSSIDs = ssids
super.init(style: .grouped)
self.title = title
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableView.automaticDimension
tableView.allowsSelection = false
tableView.register(TextCell.self)
}
}
extension SSIDOptionDetailTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedSSIDs.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return tr("tunnelOnDemandSectionTitleSelectedSSIDs")
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = selectedSSIDs[indexPath.row]
return cell
}
}
@@ -0,0 +1,295 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import UIKit
import SystemConfiguration.CaptiveNetwork
protocol SSIDOptionEditTableViewControllerDelegate: class {
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String])
}
class SSIDOptionEditTableViewController: UITableViewController {
private enum Section {
case ssidOption
case selectedSSIDs
case addSSIDs
}
private enum AddSSIDRow {
case addConnectedSSID(connectedSSID: String)
case addNewSSID
}
weak var delegate: SSIDOptionEditTableViewControllerDelegate?
private var sections = [Section]()
private var addSSIDRows = [AddSSIDRow]()
let ssidOptionFields: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
.anySSID,
.onlySpecificSSIDs,
.exceptSpecificSSIDs
]
var selectedOption: ActivateOnDemandViewModel.OnDemandSSIDOption
var selectedSSIDs: [String]
var connectedSSID: String?
init(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
selectedOption = option
selectedSSIDs = ssids
super.init(style: .grouped)
connectedSSID = getConnectedSSID()
loadSections()
loadAddSSIDRows()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = tr("tunnelOnDemandSSIDViewTitle")
tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableView.automaticDimension
tableView.register(CheckmarkCell.self)
tableView.register(EditableTextCell.self)
tableView.register(TextCell.self)
tableView.isEditing = true
tableView.allowsSelectionDuringEditing = true
}
func loadSections() {
sections.removeAll()
sections.append(.ssidOption)
if selectedOption != .anySSID {
sections.append(.selectedSSIDs)
sections.append(.addSSIDs)
}
}
func loadAddSSIDRows() {
addSSIDRows.removeAll()
if let connectedSSID = connectedSSID {
if !selectedSSIDs.contains(connectedSSID) {
addSSIDRows.append(.addConnectedSSID(connectedSSID: connectedSSID))
}
}
addSSIDRows.append(.addNewSSID)
}
func updateTableViewAddSSIDRows() {
guard let addSSIDSection = sections.firstIndex(of: .addSSIDs) else { return }
let numberOfAddSSIDRows = addSSIDRows.count
let numberOfAddSSIDRowsInTableView = tableView.numberOfRows(inSection: addSSIDSection)
switch (numberOfAddSSIDRowsInTableView, numberOfAddSSIDRows) {
case (1, 2):
tableView.insertRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
case (2, 1):
tableView.deleteRows(at: [IndexPath(row: 0, section: addSSIDSection)], with: .automatic)
default:
break
}
}
override func viewWillDisappear(_ animated: Bool) {
delegate?.ssidOptionSaved(option: selectedOption, ssids: selectedSSIDs)
}
}
extension SSIDOptionEditTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch sections[section] {
case .ssidOption:
return ssidOptionFields.count
case .selectedSSIDs:
return selectedSSIDs.isEmpty ? 1 : selectedSSIDs.count
case .addSSIDs:
return addSSIDRows.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch sections[indexPath.section] {
case .ssidOption:
return ssidOptionCell(for: tableView, at: indexPath)
case .selectedSSIDs:
if !selectedSSIDs.isEmpty {
return selectedSSIDCell(for: tableView, at: indexPath)
} else {
return noSSIDsCell(for: tableView, at: indexPath)
}
case .addSSIDs:
return addSSIDCell(for: tableView, at: indexPath)
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
switch sections[indexPath.section] {
case .ssidOption:
return false
case .selectedSSIDs:
return !selectedSSIDs.isEmpty
case .addSSIDs:
return true
}
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
switch sections[indexPath.section] {
case .ssidOption:
return .none
case .selectedSSIDs:
return .delete
case .addSSIDs:
return .insert
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch sections[section] {
case .ssidOption:
return nil
case .selectedSSIDs:
return tr("tunnelOnDemandSectionTitleSelectedSSIDs")
case .addSSIDs:
return tr("tunnelOnDemandSectionTitleAddSSIDs")
}
}
private func ssidOptionCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let field = ssidOptionFields[indexPath.row]
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = field.localizedUIString
cell.isChecked = selectedOption == field
cell.isEditing = false
return cell
}
private func noSSIDsCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = tr("tunnelOnDemandNoSSIDs")
cell.setTextColor(.gray)
cell.setTextAlignment(.center)
return cell
}
private func selectedSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: EditableTextCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = selectedSSIDs[indexPath.row]
cell.isEditing = true
cell.onValueBeingEdited = { [weak self, weak cell] text in
guard let self = self, let cell = cell else { return }
if let row = self.tableView.indexPath(for: cell)?.row {
self.selectedSSIDs[row] = text
self.loadAddSSIDRows()
self.updateTableViewAddSSIDRows()
}
}
return cell
}
private func addSSIDCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: TextCell = tableView.dequeueReusableCell(for: indexPath)
switch addSSIDRows[indexPath.row] {
case .addConnectedSSID:
cell.message = tr(format: "tunnelOnDemandAddMessageAddConnectedSSID (%@)", connectedSSID!)
case .addNewSSID:
cell.message = tr("tunnelOnDemandAddMessageAddNewSSID")
}
cell.isEditing = true
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch sections[indexPath.section] {
case .ssidOption:
assertionFailure()
case .selectedSSIDs:
assert(editingStyle == .delete)
selectedSSIDs.remove(at: indexPath.row)
if !selectedSSIDs.isEmpty {
tableView.deleteRows(at: [indexPath], with: .automatic)
} else {
tableView.reloadRows(at: [indexPath], with: .automatic)
}
loadAddSSIDRows()
updateTableViewAddSSIDRows()
case .addSSIDs:
assert(editingStyle == .insert)
let newSSID: String
switch addSSIDRows[indexPath.row] {
case .addConnectedSSID(let connectedSSID):
newSSID = connectedSSID
case .addNewSSID:
newSSID = ""
}
selectedSSIDs.append(newSSID)
loadSections()
let selectedSSIDsSection = sections.firstIndex(of: .selectedSSIDs)!
let indexPath = IndexPath(row: selectedSSIDs.count - 1, section: selectedSSIDsSection)
if selectedSSIDs.count == 1 {
tableView.reloadRows(at: [indexPath], with: .automatic)
} else {
tableView.insertRows(at: [indexPath], with: .automatic)
}
loadAddSSIDRows()
updateTableViewAddSSIDRows()
if newSSID.isEmpty {
if let selectedSSIDCell = tableView.cellForRow(at: indexPath) as? EditableTextCell {
selectedSSIDCell.beginEditing()
}
}
}
}
}
extension SSIDOptionEditTableViewController {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
switch sections[indexPath.section] {
case .ssidOption:
return indexPath
case .selectedSSIDs, .addSSIDs:
return nil
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch sections[indexPath.section] {
case .ssidOption:
let previousOption = selectedOption
selectedOption = ssidOptionFields[indexPath.row]
loadSections()
if previousOption == .anySSID {
let indexSet = IndexSet(1 ... 2)
tableView.insertSections(indexSet, with: .fade)
}
if selectedOption == .anySSID {
let indexSet = IndexSet(1 ... 2)
tableView.deleteSections(indexSet, with: .fade)
}
tableView.reloadSections(IndexSet(integer: indexPath.section), with: .none)
case .selectedSSIDs, .addSSIDs:
assertionFailure()
}
}
}
private func getConnectedSSID() -> String? {
guard let supportedInterfaces = CNCopySupportedInterfaces() as? [CFString] else { return nil }
for interface in supportedInterfaces {
if let networkInfo = CNCopyCurrentNetworkInfo(interface) {
if let ssid = (networkInfo as NSDictionary)[kCNNetworkInfoKeySSID as String] as? String {
return !ssid.isEmpty ? ssid : nil
}
}
}
return nil
}
@@ -10,14 +10,14 @@ class SettingsTableViewController: UITableViewController {
case iosAppVersion case iosAppVersion
case goBackendVersion case goBackendVersion
case exportZipArchive case exportZipArchive
case exportLogFile case viewLog
var localizedUIString: String { var localizedUIString: String {
switch self { switch self {
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS") case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend") case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
case .exportZipArchive: return tr("settingsExportZipButtonTitle") case .exportZipArchive: return tr("settingsExportZipButtonTitle")
case .exportLogFile: return tr("settingsExportLogFileButtonTitle") case .viewLog: return tr("settingsViewLogButtonTitle")
} }
} }
} }
@@ -25,7 +25,7 @@ class SettingsTableViewController: UITableViewController {
let settingsFieldsBySection: [[SettingsFields]] = [ let settingsFieldsBySection: [[SettingsFields]] = [
[.iosAppVersion, .goBackendVersion], [.iosAppVersion, .goBackendVersion],
[.exportZipArchive], [.exportZipArchive],
[.exportLogFile] [.viewLog]
] ]
let tunnelsManager: TunnelsManager? let tunnelsManager: TunnelsManager?
@@ -108,46 +108,10 @@ class SettingsTableViewController: UITableViewController {
} }
} }
func exportLogForLastActivatedTunnel(sourceView: UIView) { func presentLogView() {
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } let logVC = LogViewController()
navigationController?.pushViewController(logVC, animated: true)
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
DispatchQueue.global(qos: .userInitiated).async {
if FileManager.default.fileExists(atPath: destinationURL.path) {
let isDeleted = FileManager.deleteFile(at: destinationURL)
if !isDeleted {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
return
}
}
guard let networkExtensionLogFilePath = FileManager.networkExtensionLogFileURL?.path else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToFindExtensionLogPathTitle"), message: tr("alertUnableToFindExtensionLogPathMessage"), from: self)
return
}
let isWritten = Logger.global?.writeLog(called: "APP", mergedWith: networkExtensionLogFilePath, called: "NET", to: destinationURL.path) ?? false
DispatchQueue.main.async {
guard isWritten else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
return
}
let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = sourceView
activityVC.popoverPresentationController?.sourceRect = sourceView.bounds
activityVC.completionWithItemsHandler = { _, _, _, _ in
// Remove the exported log file after the activity has completed
_ = FileManager.deleteFile(at: destinationURL)
}
self.present(activityVC, animated: true)
}
}
} }
} }
@@ -197,11 +161,11 @@ extension SettingsTableViewController {
} }
return cell return cell
} else { } else {
assert(field == .exportLogFile) assert(field == .viewLog)
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath) let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
cell.buttonText = field.localizedUIString cell.buttonText = field.localizedUIString
cell.onTapped = { [weak self] in cell.onTapped = { [weak self] in
self?.exportLogForLastActivatedTunnel(sourceView: cell.button) self?.presentLogView()
} }
return cell return cell
} }
@@ -24,21 +24,28 @@ class TunnelDetailTableViewController: UITableViewController {
.rxBytes, .txBytes, .lastHandshakeTime .rxBytes, .txBytes, .lastHandshakeTime
] ]
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
.onDemand, .ssid
]
let tunnelsManager: TunnelsManager let tunnelsManager: TunnelsManager
let tunnel: TunnelContainer let tunnel: TunnelContainer
var tunnelViewModel: TunnelViewModel var tunnelViewModel: TunnelViewModel
var onDemandViewModel: ActivateOnDemandViewModel
private var sections = [Section]() private var sections = [Section]()
private var interfaceFieldIsVisible = [Bool]() private var interfaceFieldIsVisible = [Bool]()
private var peerFieldIsVisible = [[Bool]]() private var peerFieldIsVisible = [[Bool]]()
private var statusObservationToken: AnyObject? private var statusObservationToken: AnyObject?
private var onDemandObservationToken: AnyObject?
private var reloadRuntimeConfigurationTimer: Timer? private var reloadRuntimeConfigurationTimer: Timer?
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) { init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
self.tunnel = tunnel self.tunnel = tunnel
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration) tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
super.init(style: .grouped) super.init(style: .grouped)
loadSections() loadSections()
loadVisibleFields() loadVisibleFields()
@@ -51,6 +58,11 @@ class TunnelDetailTableViewController: UITableViewController {
self.stopUpdatingRuntimeConfiguration() self.stopUpdatingRuntimeConfiguration()
} }
} }
onDemandObservationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak self] tunnel, _ in
// Handle On-Demand getting turned on/off outside of the app
self?.onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
self?.updateActivateOnDemandFields()
}
} }
required init?(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
@@ -64,10 +76,10 @@ class TunnelDetailTableViewController: UITableViewController {
tableView.estimatedRowHeight = 44 tableView.estimatedRowHeight = 44
tableView.rowHeight = UITableView.automaticDimension tableView.rowHeight = UITableView.automaticDimension
tableView.allowsSelection = false
tableView.register(SwitchCell.self) tableView.register(SwitchCell.self)
tableView.register(KeyValueCell.self) tableView.register(KeyValueCell.self)
tableView.register(ButtonCell.self) tableView.register(ButtonCell.self)
tableView.register(ChevronCell.self)
restorationIdentifier = "TunnelDetailVC:\(tunnel.name)" restorationIdentifier = "TunnelDetailVC:\(tunnel.name)"
} }
@@ -113,21 +125,6 @@ class TunnelDetailTableViewController: UITableViewController {
} }
} }
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
onConfirmed()
}
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
alert.addAction(destroyAction)
alert.addAction(cancelAction)
alert.popoverPresentationController?.sourceView = sourceView
alert.popoverPresentationController?.sourceRect = sourceView.bounds
present(alert, animated: true, completion: nil)
}
func startUpdatingRuntimeConfiguration() { func startUpdatingRuntimeConfiguration() {
reloadRuntimeConfiguration() reloadRuntimeConfiguration()
reloadRuntimeConfigurationTimer?.invalidate() reloadRuntimeConfigurationTimer?.invalidate()
@@ -158,16 +155,21 @@ class TunnelDetailTableViewController: UITableViewController {
var interfaceFieldIsVisible = self.interfaceFieldIsVisible var interfaceFieldIsVisible = self.interfaceFieldIsVisible
var peerFieldIsVisible = self.peerFieldIsVisible var peerFieldIsVisible = self.peerFieldIsVisible
func sectionChanged<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], tableView: UITableView, section: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) { func handleSectionFieldsModified<T>(fields: [T], fieldIsVisible: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
for (index, field) in fields.enumerated() {
guard let change = changes[field] else { continue }
if case .modified(let newValue) = change {
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
let indexPath = IndexPath(row: row, section: section)
if let cell = tableView.cellForRow(at: indexPath) as? KeyValueCell {
cell.value = newValue
}
}
}
}
func handleSectionRowsInsertedOrRemoved<T>(fields: [T], fieldIsVisible fieldIsVisibleInput: [Bool], section: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
var fieldIsVisible = fieldIsVisibleInput var fieldIsVisible = fieldIsVisibleInput
var modifiedIndexPaths = [IndexPath]()
for (index, field) in fields.enumerated() where changes[field] == .modified {
let row = fieldIsVisible[0 ..< index].filter { $0 }.count
modifiedIndexPaths.append(IndexPath(row: row, section: section))
}
if !modifiedIndexPaths.isEmpty {
tableView.reloadRows(at: modifiedIndexPaths, with: .none)
}
var removedIndexPaths = [IndexPath]() var removedIndexPaths = [IndexPath]()
for (index, field) in fields.enumerated().reversed() where changes[field] == .removed { for (index, field) in fields.enumerated().reversed() where changes[field] == .removed {
@@ -190,30 +192,44 @@ class TunnelDetailTableViewController: UITableViewController {
} }
} }
let changeHandlers = TunnelViewModel.ChangeHandlers( let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
interfaceChanged: { changes in
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
tableView: tableView, section: interfaceSectionIndex, changes: changes)
},
peerChangedAt: { peerIndex, changes in
sectionChanged(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex],
tableView: tableView, section: firstPeerSectionIndex + peerIndex, changes: changes)
},
peersRemovedAt: { peerIndices in
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
tableView.deleteSections(IndexSet(sectionIndices), with: .automatic)
},
peersInsertedAt: { peerIndices in
let sectionIndices = peerIndices.map { firstPeerSectionIndex + $0 }
tableView.insertSections(IndexSet(sectionIndices), with: .automatic)
}
)
tableView.beginUpdates() if !changes.interfaceChanges.isEmpty {
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers) handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible,
self.loadSections() section: interfaceSectionIndex, changes: changes.interfaceChanges)
self.loadVisibleFields() }
tableView.endUpdates() for (peerIndex, peerChanges) in changes.peerChanges {
handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
}
let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
let peersRemovedSectionIndices = changes.peersRemovedIndices.map { firstPeerSectionIndex + $0 }
let peersInsertedSectionIndices = changes.peersInsertedIndices.map { firstPeerSectionIndex + $0 }
if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !peersRemovedSectionIndices.isEmpty || !peersInsertedSectionIndices.isEmpty {
tableView.beginUpdates()
if isAnyInterfaceFieldAddedOrRemoved {
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields, fieldIsVisible: interfaceFieldIsVisible, section: interfaceSectionIndex, changes: changes.interfaceChanges)
}
if isAnyPeerFieldAddedOrRemoved {
for (peerIndex, peerChanges) in changes.peerChanges {
handleSectionRowsInsertedOrRemoved(fields: TunnelDetailTableViewController.peerFields, fieldIsVisible: peerFieldIsVisible[peerIndex], section: firstPeerSectionIndex + peerIndex, changes: peerChanges)
}
}
if !peersRemovedSectionIndices.isEmpty {
tableView.deleteSections(IndexSet(peersRemovedSectionIndices), with: .automatic)
}
if !peersInsertedSectionIndices.isEmpty {
tableView.insertSections(IndexSet(peersInsertedSectionIndices), with: .automatic)
}
self.loadSections()
self.loadVisibleFields()
tableView.endUpdates()
} else {
self.loadSections()
self.loadVisibleFields()
}
} }
private func reloadRuntimeConfiguration() { private func reloadRuntimeConfiguration() {
@@ -223,11 +239,27 @@ class TunnelDetailTableViewController: UITableViewController {
self.applyTunnelConfiguration(tunnelConfiguration: tunnelConfiguration) self.applyTunnelConfiguration(tunnelConfiguration: tunnelConfiguration)
} }
} }
private func updateActivateOnDemandFields() {
guard let onDemandSection = sections.firstIndex(where: { if case .onDemand = $0 { return true } else { return false } }) else { return }
let numberOfTableViewOnDemandRows = tableView.numberOfRows(inSection: onDemandSection)
let ssidRowIndexPath = IndexPath(row: 1, section: onDemandSection)
switch (numberOfTableViewOnDemandRows, onDemandViewModel.isWiFiInterfaceEnabled) {
case (1, true):
tableView.insertRows(at: [ssidRowIndexPath], with: .automatic)
case (2, false):
tableView.deleteRows(at: [ssidRowIndexPath], with: .automatic)
default:
break
}
tableView.reloadSections(IndexSet(integer: onDemandSection), with: .automatic)
}
} }
extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate { extension TunnelDetailTableViewController: TunnelEditTableViewControllerDelegate {
func tunnelSaved(tunnel: TunnelContainer) { func tunnelSaved(tunnel: TunnelContainer) {
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration) tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
loadSections() loadSections()
loadVisibleFields() loadVisibleFields()
title = tunnel.name title = tunnel.name
@@ -253,7 +285,7 @@ extension TunnelDetailTableViewController {
case .peer(let peerIndex, _): case .peer(let peerIndex, _):
return peerFieldIsVisible[peerIndex].filter { $0 }.count return peerFieldIsVisible[peerIndex].filter { $0 }.count
case .onDemand: case .onDemand:
return 1 return onDemandViewModel.isWiFiInterfaceEnabled ? 2 : 1
case .delete: case .delete:
return 1 return 1
} }
@@ -351,6 +383,8 @@ extension TunnelDetailTableViewController {
cell.key = field.localizedUIString cell.key = field.localizedUIString
if field == .persistentKeepAlive { if field == .persistentKeepAlive {
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field]) cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
} else if field == .preSharedKey {
cell.value = tr("tunnelPeerPresharedKeyEnabled")
} else { } else {
cell.value = peerData[field] cell.value = peerData[field]
} }
@@ -358,13 +392,26 @@ extension TunnelDetailTableViewController {
} }
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath) let field = TunnelDetailTableViewController.onDemandFields[indexPath.row]
cell.key = tr("tunnelOnDemandKey") if field == .onDemand {
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting) let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
cell.observationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in cell.key = field.localizedUIString
cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting) cell.value = onDemandViewModel.localizedInterfaceDescription
return cell
} else {
assert(field == .ssid)
if onDemandViewModel.ssidOption == .anySSID {
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
cell.key = field.localizedUIString
cell.value = onDemandViewModel.ssidOption.localizedUIString
return cell
} else {
let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = field.localizedUIString
cell.detailMessage = onDemandViewModel.localizedSSIDDescription
return cell
}
} }
return cell
} }
private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { private func deleteConfigurationCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
@@ -373,7 +420,9 @@ extension TunnelDetailTableViewController {
cell.hasDestructiveAction = true cell.hasDestructiveAction = true
cell.onTapped = { [weak self] in cell.onTapped = { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.showConfirmationAlert(message: tr("deleteTunnelConfirmationAlertMessage"), buttonTitle: tr("deleteTunnelConfirmationAlertButtonTitle"), from: cell) { [weak self] in ConfirmationAlertPresenter.showConfirmationAlert(message: tr("deleteTunnelConfirmationAlertMessage"),
buttonTitle: tr("deleteTunnelConfirmationAlertButtonTitle"),
from: cell, presentingVC: self) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
self.tunnelsManager.remove(tunnel: self.tunnel) { error in self.tunnelsManager.remove(tunnel: self.tunnel) { error in
if error != nil { if error != nil {
@@ -387,3 +436,22 @@ extension TunnelDetailTableViewController {
} }
} }
extension TunnelDetailTableViewController {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if case .onDemand = sections[indexPath.section],
case .ssid = TunnelDetailTableViewController.onDemandFields[indexPath.row] {
return indexPath
}
return nil
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if case .onDemand = sections[indexPath.section],
case .ssid = TunnelDetailTableViewController.onDemandFields[indexPath.row] {
let ssidDetailVC = SSIDOptionDetailTableViewController(title: onDemandViewModel.ssidOption.localizedUIString, ssids: onDemandViewModel.selectedSSIDs)
navigationController?.pushViewController(ssidDetailVC, animated: true)
}
tableView.deselectRow(at: indexPath, animated: true)
}
}
@@ -43,16 +43,16 @@ class TunnelEditTableViewController: UITableViewController {
.deletePeer .deletePeer
] ]
let activateOnDemandOptions: [ActivateOnDemandOption] = [ let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
.useOnDemandOverWiFiOrCellular, .nonWiFiInterface,
.useOnDemandOverWiFiOnly, .wiFiInterface,
.useOnDemandOverCellularOnly .ssid
] ]
let tunnelsManager: TunnelsManager let tunnelsManager: TunnelsManager
let tunnel: TunnelContainer? let tunnel: TunnelContainer?
let tunnelViewModel: TunnelViewModel let tunnelViewModel: TunnelViewModel
var activateOnDemandSetting: ActivateOnDemandSetting var onDemandViewModel: ActivateOnDemandViewModel
private var sections = [Section]() private var sections = [Section]()
// Use this initializer to edit an existing tunnel. // Use this initializer to edit an existing tunnel.
@@ -60,7 +60,7 @@ class TunnelEditTableViewController: UITableViewController {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
self.tunnel = tunnel self.tunnel = tunnel
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration) tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
activateOnDemandSetting = tunnel.activateOnDemandSetting onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
super.init(style: .grouped) super.init(style: .grouped)
loadSections() loadSections()
} }
@@ -70,7 +70,7 @@ class TunnelEditTableViewController: UITableViewController {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
tunnel = nil tunnel = nil
tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil) tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil)
activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting onDemandViewModel = ActivateOnDemandViewModel()
super.init(style: .grouped) super.init(style: .grouped)
loadSections() loadSections()
} }
@@ -92,7 +92,7 @@ class TunnelEditTableViewController: UITableViewController {
tableView.register(TunnelEditEditableKeyValueCell.self) tableView.register(TunnelEditEditableKeyValueCell.self)
tableView.register(ButtonCell.self) tableView.register(ButtonCell.self)
tableView.register(SwitchCell.self) tableView.register(SwitchCell.self)
tableView.register(CheckmarkCell.self) tableView.register(ChevronCell.self)
} }
private func loadSections() { private func loadSections() {
@@ -113,9 +113,10 @@ class TunnelEditTableViewController: UITableViewController {
ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self) ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
tableView.reloadData() // Highlight erroring fields tableView.reloadData() // Highlight erroring fields
case .saved(let tunnelConfiguration): case .saved(let tunnelConfiguration):
let onDemandOption = onDemandViewModel.toOnDemandOption()
if let tunnel = tunnel { if let tunnel = tunnel {
// We're modifying an existing tunnel // We're modifying an existing tunnel
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: activateOnDemandSetting) { [weak self] error in tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
if let error = error { if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
} else { } else {
@@ -125,7 +126,7 @@ class TunnelEditTableViewController: UITableViewController {
} }
} else { } else {
// We're adding a new tunnel // We're adding a new tunnel
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: activateOnDemandSetting) { [weak self] result in tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
if let error = result.error { if let error = result.error {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
} else { } else {
@@ -161,10 +162,10 @@ extension TunnelEditTableViewController {
case .addPeer: case .addPeer:
return 1 return 1
case .onDemand: case .onDemand:
if activateOnDemandSetting.isActivateOnDemandEnabled { if onDemandViewModel.isWiFiInterfaceEnabled {
return 4 return 3
} else { } else {
return 1 return 2
} }
} }
} }
@@ -250,6 +251,8 @@ extension TunnelEditTableViewController {
cell.keyboardType = .numberPad cell.keyboardType = .numberPad
case .publicKey, .generateKeyPair: case .publicKey, .generateKeyPair:
cell.keyboardType = .default cell.keyboardType = .default
case .status, .toggleStatus:
fatalError("Unexpected interface field")
} }
cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field)) cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field))
@@ -259,8 +262,18 @@ extension TunnelEditTableViewController {
cell.onValueBeingEdited = { [weak self] value in cell.onValueBeingEdited = { [weak self] value in
self?.tunnelViewModel.interfaceData[field] = value self?.tunnelViewModel.interfaceData[field] = value
} }
cell.onValueChanged = { [weak self] oldValue, newValue in
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 } }
if let section = section, let row = self.peerFields.firstIndex(of: .allowedIPs) {
self.tableView.reloadRows(at: [IndexPath(row: row, section: section)], with: .none)
}
}
}
} else { } else {
cell.onValueChanged = { [weak self] value in cell.onValueChanged = { [weak self] _, value in
self?.tunnelViewModel.interfaceData[field] = value self?.tunnelViewModel.interfaceData[field] = value
} }
} }
@@ -298,7 +311,9 @@ extension TunnelEditTableViewController {
cell.hasDestructiveAction = true cell.hasDestructiveAction = true
cell.onTapped = { [weak self, weak peerData] in cell.onTapped = { [weak self, weak peerData] in
guard let self = self, let peerData = peerData else { return } guard let self = self, let peerData = peerData else { return }
self.showConfirmationAlert(message: tr("deletePeerConfirmationAlertMessage"), buttonTitle: tr("deletePeerConfirmationAlertButtonTitle"), from: cell) { [weak self] in ConfirmationAlertPresenter.showConfirmationAlert(message: tr("deletePeerConfirmationAlertMessage"),
buttonTitle: tr("deletePeerConfirmationAlertButtonTitle"),
from: cell, presentingVC: self) { [weak self] in
guard let self = self else { return } guard let self = self else { return }
let removedSectionIndices = self.deletePeer(peer: peerData) let removedSectionIndices = self.deletePeer(peer: peerData)
let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl) let shouldShowExcludePrivateIPs = (self.tunnelViewModel.peersData.count == 1 && self.tunnelViewModel.peersData[0].shouldAllowExcludePrivateIPsControl)
@@ -380,7 +395,7 @@ extension TunnelEditTableViewController {
tableView.reloadRows(at: [IndexPath(row: dnsRow, section: firstInterfaceSection + interfaceSubSection)], with: .none) tableView.reloadRows(at: [IndexPath(row: dnsRow, section: firstInterfaceSection + interfaceSubSection)], with: .none)
} }
} else { } else {
cell.onValueChanged = { [weak peerData] value in cell.onValueChanged = { [weak peerData] _, value in
peerData?[field] = value peerData?[field] = value
} }
} }
@@ -409,36 +424,29 @@ extension TunnelEditTableViewController {
} }
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell { private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 { let field = onDemandFields[indexPath.row]
if indexPath.row < 2 {
let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath) let cell: SwitchCell = tableView.dequeueReusableCell(for: indexPath)
cell.message = tr("tunnelOnDemandKey") cell.message = field.localizedUIString
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled cell.isOn = onDemandViewModel.isEnabled(field: field)
cell.onSwitchToggled = { [weak self] isOn in cell.onSwitchToggled = { [weak self] isOn in
guard let self = self else { return } guard let self = self else { return }
guard isOn != self.activateOnDemandSetting.isActivateOnDemandEnabled else { return } self.onDemandViewModel.setEnabled(field: field, isEnabled: isOn)
self.activateOnDemandSetting.isActivateOnDemandEnabled = isOn
self.loadSections()
let section = self.sections.firstIndex { $0 == .onDemand }! let section = self.sections.firstIndex { $0 == .onDemand }!
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: section) } let indexPath = IndexPath(row: 2, section: section)
if isOn { if field == .wiFiInterface {
if self.activateOnDemandSetting.activateOnDemandOption == .none { if isOn {
self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption() tableView.insertRows(at: [indexPath], with: .fade)
} else {
tableView.deleteRows(at: [indexPath], with: .fade)
} }
self.tableView.insertRows(at: indexPaths, with: .fade)
} else {
self.tableView.deleteRows(at: indexPaths, with: .fade)
} }
} }
return cell return cell
} else { } else {
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath) let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
let rowOption = activateOnDemandOptions[indexPath.row - 1] cell.message = field.localizedUIString
let selectedOption = activateOnDemandSetting.activateOnDemandOption cell.detailMessage = onDemandViewModel.localizedSSIDDescription
assert(selectedOption != .none)
cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
cell.isChecked = selectedOption == rowOption
return cell return cell
} }
} }
@@ -455,26 +463,11 @@ extension TunnelEditTableViewController {
loadSections() loadSections()
return IndexSet(integer: interfaceFieldsBySection.count + peer.index) return IndexSet(integer: interfaceFieldsBySection.count + peer.index)
} }
func showConfirmationAlert(message: String, buttonTitle: String, from sourceView: UIView, onConfirmed: @escaping (() -> Void)) {
let destroyAction = UIAlertAction(title: buttonTitle, style: .destructive) { _ in
onConfirmed()
}
let cancelAction = UIAlertAction(title: tr("actionCancel"), style: .cancel)
let alert = UIAlertController(title: "", message: message, preferredStyle: .actionSheet)
alert.addAction(destroyAction)
alert.addAction(cancelAction)
alert.popoverPresentationController?.sourceView = sourceView
alert.popoverPresentationController?.sourceRect = sourceView.bounds
present(alert, animated: true, completion: nil)
}
} }
extension TunnelEditTableViewController { extension TunnelEditTableViewController {
override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
if case .onDemand = sections[indexPath.section], indexPath.row > 0 { if case .onDemand = sections[indexPath.section], indexPath.row == 2 {
return indexPath return indexPath
} else { } else {
return nil return nil
@@ -484,16 +477,27 @@ extension TunnelEditTableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
switch sections[indexPath.section] { switch sections[indexPath.section] {
case .onDemand: case .onDemand:
let option = activateOnDemandOptions[indexPath.row - 1] assert(indexPath.row == 2)
assert(option != .none) tableView.deselectRow(at: indexPath, animated: true)
activateOnDemandSetting.activateOnDemandOption = option let ssidOptionVC = SSIDOptionEditTableViewController(option: onDemandViewModel.ssidOption, ssids: onDemandViewModel.selectedSSIDs)
ssidOptionVC.delegate = self
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) } navigationController?.pushViewController(ssidOptionVC, animated: true)
UIView.performWithoutAnimation {
tableView.reloadRows(at: indexPaths, with: .none)
}
default: default:
assertionFailure() assertionFailure()
} }
} }
} }
extension TunnelEditTableViewController: SSIDOptionEditTableViewControllerDelegate {
func ssidOptionSaved(option: ActivateOnDemandViewModel.OnDemandSSIDOption, ssids: [String]) {
onDemandViewModel.selectedSSIDs = ssids
onDemandViewModel.ssidOption = option
onDemandViewModel.fixSSIDOption()
if let onDemandSection = sections.firstIndex(where: { $0 == .onDemand }) {
if let ssidRowIndex = onDemandFields.firstIndex(of: .ssid) {
let indexPath = IndexPath(row: ssidRowIndex, section: onDemandSection)
tableView.reloadRows(at: [indexPath], with: .none)
}
}
}
}
@@ -9,6 +9,12 @@ class TunnelsListTableViewController: UIViewController {
var tunnelsManager: TunnelsManager? var tunnelsManager: TunnelsManager?
enum TableState: Equatable {
case normal
case rowSwiped
case multiSelect(selectionCount: Int)
}
let tableView: UITableView = { let tableView: UITableView = {
let tableView = UITableView(frame: CGRect.zero, style: .plain) let tableView = UITableView(frame: CGRect.zero, style: .plain)
tableView.estimatedRowHeight = 60 tableView.estimatedRowHeight = 60
@@ -32,6 +38,11 @@ class TunnelsListTableViewController: UIViewController {
}() }()
var detailDisplayedTunnel: TunnelContainer? var detailDisplayedTunnel: TunnelContainer?
var tableState: TableState = .normal {
didSet {
handleTableStateChange()
}
}
override func loadView() { override func loadView() {
view = UIView() view = UIView()
@@ -74,13 +85,39 @@ class TunnelsListTableViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
title = tr("tunnelsListTitle") tableState = .normal
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSettingsButtonTitle"), style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
restorationIdentifier = "TunnelsListVC" restorationIdentifier = "TunnelsListVC"
} }
func handleTableStateChange() {
switch tableState {
case .normal:
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:)))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSettingsButtonTitle"), style: .plain, target: self, action: #selector(settingsButtonTapped(sender:)))
case .rowSwiped:
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSelectButtonTitle"), style: .plain, target: self, action: #selector(selectButtonTapped))
case .multiSelect(let selectionCount):
if selectionCount > 0 {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListDeleteButtonTitle"), style: .plain, target: self, action: #selector(deleteButtonTapped(sender:)))
} else {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonTapped))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: tr("tunnelsListSelectAllButtonTitle"), style: .plain, target: self, action: #selector(selectAllButtonTapped))
}
}
if case .multiSelect(let selectionCount) = tableState, selectionCount > 0 {
navigationItem.title = tr(format: "tunnelsListSelectedTitle (%d)", selectionCount)
} else {
navigationItem.title = tr("tunnelsListTitle")
}
if case .multiSelect = tableState {
tableView.allowsMultipleSelectionDuringEditing = true
} else {
tableView.allowsMultipleSelectionDuringEditing = false
}
}
func setTunnelsManager(tunnelsManager: TunnelsManager) { func setTunnelsManager(tunnelsManager: TunnelsManager) {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
tunnelsManager.tunnelsListDelegate = self tunnelsManager.tunnelsListDelegate = self
@@ -159,14 +196,67 @@ class TunnelsListTableViewController: UIViewController {
scanQRCodeNC.modalPresentationStyle = .fullScreen scanQRCodeNC.modalPresentationStyle = .fullScreen
present(scanQRCodeNC, animated: true) present(scanQRCodeNC, animated: true)
} }
@objc func selectButtonTapped() {
let shouldCancelSwipe = tableState == .rowSwiped
tableState = .multiSelect(selectionCount: 0)
if shouldCancelSwipe {
tableView.setEditing(false, animated: false)
}
tableView.setEditing(true, animated: true)
}
@objc func doneButtonTapped() {
tableState = .normal
tableView.setEditing(false, animated: true)
}
@objc func selectAllButtonTapped() {
guard tableView.isEditing else { return }
guard let tunnelsManager = tunnelsManager else { return }
for index in 0 ..< tunnelsManager.numberOfTunnels() {
tableView.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none)
}
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
}
@objc func cancelButtonTapped() {
tableState = .normal
tableView.setEditing(false, animated: true)
}
@objc func deleteButtonTapped(sender: AnyObject?) {
guard let sender = sender as? UIBarButtonItem else { return }
guard let tunnelsManager = tunnelsManager else { return }
let selectedTunnelIndices = tableView.indexPathsForSelectedRows?.map { $0.row } ?? []
let selectedTunnels = selectedTunnelIndices.compactMap { tunnelIndex in
tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() ? tunnelsManager.tunnel(at: tunnelIndex) : nil
}
guard !selectedTunnels.isEmpty else { return }
let message = selectedTunnels.count == 1 ?
tr(format: "deleteTunnelConfirmationAlertButtonMessage (%d)", selectedTunnels.count) :
tr(format: "deleteTunnelsConfirmationAlertButtonMessage (%d)", selectedTunnels.count)
let title = tr("deleteTunnelsConfirmationAlertButtonTitle")
ConfirmationAlertPresenter.showConfirmationAlert(message: message, buttonTitle: title,
from: sender, presentingVC: self) { [weak self] in
self?.tunnelsManager?.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
guard let self = self else { return }
if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self)
return
}
self.tableState = .normal
self.tableView.setEditing(false, animated: true)
}
}
}
} }
extension TunnelsListTableViewController: UIDocumentPickerDelegate { extension TunnelsListTableViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let tunnelsManager = tunnelsManager else { return } guard let tunnelsManager = tunnelsManager else { return }
urls.forEach { url in TunnelImporter.importFromFile(urls: urls, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
}
} }
} }
@@ -212,6 +302,10 @@ extension TunnelsListTableViewController: UITableViewDataSource {
extension TunnelsListTableViewController: UITableViewDelegate { extension TunnelsListTableViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard !tableView.isEditing else {
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
return
}
guard let tunnelsManager = tunnelsManager else { return } guard let tunnelsManager = tunnelsManager else { return }
let tunnel = tunnelsManager.tunnel(at: indexPath.row) let tunnel = tunnelsManager.tunnel(at: indexPath.row)
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
@@ -222,6 +316,13 @@ extension TunnelsListTableViewController: UITableViewDelegate {
detailDisplayedTunnel = tunnel detailDisplayedTunnel = tunnel
} }
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
guard !tableView.isEditing else {
tableState = .multiSelect(selectionCount: tableView.indexPathsForSelectedRows?.count ?? 0)
return
}
}
func tableView(_ tableView: UITableView, func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in
@@ -238,6 +339,18 @@ extension TunnelsListTableViewController: UITableViewDelegate {
} }
return UISwipeActionsConfiguration(actions: [deleteAction]) return UISwipeActionsConfiguration(actions: [deleteAction])
} }
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
if tableState == .normal {
tableState = .rowSwiped
}
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
if tableState == .rowSwiped {
tableState = .normal
}
}
} }
extension TunnelsListTableViewController: TunnelsManagerListDelegate { extension TunnelsListTableViewController: TunnelsManagerListDelegate {
@@ -6,6 +6,8 @@
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>
</array> </array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>group.$(APP_ID_IOS)</string> <string>group.$(APP_ID_IOS)</string>
+24 -12
View File
@@ -2,6 +2,7 @@
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. // Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa import Cocoa
import ServiceManagement
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
@@ -12,10 +13,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
var manageTunnelsRootVC: ManageTunnelsRootViewController? var manageTunnelsRootVC: ManageTunnelsRootViewController?
var manageTunnelsWindowObject: NSWindow? var manageTunnelsWindowObject: NSWindow?
var isTerminationAlertShown = false
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
Logger.configureGlobal(withFilePath: FileManager.appLogFileURL?.path) Logger.configureGlobal(tagged: "APP", withFilePath: FileManager.logFileURL?.path)
registerLoginItem(shouldLaunchAtLogin: true)
TunnelsManager.create { [weak self] result in TunnelsManager.create { [weak self] result in
guard let self = self else { return } guard let self = self else { return }
@@ -42,25 +43,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
} }
} }
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { @objc func quit() {
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else { if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
return .terminateNow NSApp.activate(ignoringOtherApps: true)
manageWindow.orderFront(self)
return
} }
if isTerminationAlertShown { registerLoginItem(shouldLaunchAtLogin: false)
return .terminateNow guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
NSApp.terminate(nil)
return
} }
let alert = NSAlert() let alert = NSAlert()
alert.messageText = tr("macAppExitingWithActiveTunnelMessage") alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo") alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
if let window = manageTunnelsWindowObject { NSApp.activate(ignoringOtherApps: true)
alert.beginSheetModal(for: window) { [weak self] _ in if let manageWindow = manageTunnelsWindowObject {
self?.isTerminationAlertShown = true manageWindow.orderFront(self)
alert.beginSheetModal(for: manageWindow) { _ in
NSApp.terminate(nil) NSApp.terminate(nil)
} }
return .terminateCancel
} else { } else {
alert.runModal() alert.runModal()
return .terminateNow NSApp.terminate(nil)
} }
} }
} }
@@ -79,3 +84,10 @@ extension AppDelegate: StatusMenuWindowDelegate {
return manageTunnelsWindowObject! return manageTunnelsWindowObject!
} }
} }
@discardableResult
func registerLoginItem(shouldLaunchAtLogin: Bool) -> Bool {
let appId = Bundle.main.bundleIdentifier!
let helperBundleId = "\(appId).login-item-helper"
return SMLoginItemSetEnabled(helperBundleId as CFString, shouldLaunchAtLogin)
}
@@ -14,7 +14,7 @@ class Application: NSApplication {
"Z": #selector(UndoActionRespondable.redo(_:)), "Z": #selector(UndoActionRespondable.redo(_:)),
"w": #selector(NSWindow.performClose(_:)), "w": #selector(NSWindow.performClose(_:)),
"m": #selector(NSWindow.performMiniaturize(_:)), "m": #selector(NSWindow.performMiniaturize(_:)),
"q": #selector(NSApplication.terminate(_:)) "q": #selector(AppDelegate.quit)
] ]
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
@@ -9,11 +9,11 @@ class ImportPanelPresenter {
let openPanel = NSOpenPanel() let openPanel = NSOpenPanel()
openPanel.prompt = tr("macSheetButtonImport") openPanel.prompt = tr("macSheetButtonImport")
openPanel.allowedFileTypes = ["conf", "zip"] openPanel.allowedFileTypes = ["conf", "zip"]
openPanel.allowsMultipleSelection = true
openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
guard let tunnelsManager = tunnelsManager else { return } guard let tunnelsManager = tunnelsManager else { return }
guard response == .OK else { return } guard response == .OK else { return }
guard let url = openPanel.url else { return } TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
} }
} }
} }
+2
View File
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMultipleInstancesProhibited</key> <key>LSMultipleInstancesProhibited</key>
<true/> <true/>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>LSBackgroundOnly</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSURL *bundleURL = [NSBundle.mainBundle bundleURL];
// From <path>/WireGuard.app/Contents/Library/LoginItems/WireGuardLoginItemHelper.app, derive <path>/WireGuard.app
for (int i = 0; i < 4; ++i)
bundleURL = [bundleURL URLByDeletingLastPathComponent];
[NSWorkspace.sharedWorkspace launchApplication:[bundleURL path]];
return 0;
}
+18 -3
View File
@@ -13,6 +13,7 @@ class StatusMenu: NSMenu {
var statusMenuItem: NSMenuItem? var statusMenuItem: NSMenuItem?
var networksMenuItem: NSMenuItem? var networksMenuItem: NSMenuItem?
var deactivateMenuItem: NSMenuItem?
var firstTunnelMenuItemIndex = 0 var firstTunnelMenuItemIndex = 0
var numberOfTunnelMenuItems = 0 var numberOfTunnelMenuItems = 0
@@ -53,16 +54,22 @@ class StatusMenu: NSMenu {
networksMenuItem.isEnabled = false networksMenuItem.isEnabled = false
networksMenuItem.isHidden = true networksMenuItem.isHidden = true
addItem(networksMenuItem) addItem(networksMenuItem)
let deactivateMenuItem = NSMenuItem(title: tr("macToggleStatusButtonDeactivate"), action: #selector(deactivateClicked), keyEquivalent: "")
deactivateMenuItem.target = self
deactivateMenuItem.isHidden = true
addItem(deactivateMenuItem)
self.statusMenuItem = statusMenuItem self.statusMenuItem = statusMenuItem
self.networksMenuItem = networksMenuItem self.networksMenuItem = networksMenuItem
self.deactivateMenuItem = deactivateMenuItem
} }
func updateStatusMenuItems(with tunnel: TunnelContainer?) { func updateStatusMenuItems(with tunnel: TunnelContainer?) {
guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem else { return } guard let statusMenuItem = statusMenuItem, let networksMenuItem = networksMenuItem, let deactivateMenuItem = deactivateMenuItem else { return }
guard let tunnel = tunnel else { guard let tunnel = tunnel else {
statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive")) statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
networksMenuItem.title = "" networksMenuItem.title = ""
networksMenuItem.isHidden = true networksMenuItem.isHidden = true
deactivateMenuItem.isHidden = true
return return
} }
var statusText: String var statusText: String
@@ -98,6 +105,7 @@ class StatusMenu: NSMenu {
} }
networksMenuItem.isHidden = false networksMenuItem.isHidden = false
} }
deactivateMenuItem.isHidden = tunnel.status != .active
} }
func addTunnelMenuItems() -> Bool { func addTunnelMenuItems() -> Bool {
@@ -122,11 +130,17 @@ class StatusMenu: NSMenu {
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "") let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
aboutItem.target = self aboutItem.target = self
addItem(aboutItem) addItem(aboutItem)
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(NSApplication.terminate), keyEquivalent: "") let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
quitItem.target = NSApp quitItem.target = NSApp.delegate
addItem(quitItem) addItem(quitItem)
} }
@objc func deactivateClicked() {
if let currentTunnel = currentTunnel {
tunnelsManager.startDeactivation(of: currentTunnel)
}
}
@objc func tunnelClicked(sender: AnyObject) { @objc func tunnelClicked(sender: AnyObject) {
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return } guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
if tunnelMenuItem.state == .off { if tunnelMenuItem.state == .off {
@@ -170,6 +184,7 @@ extension StatusMenu {
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) { func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:))) let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
menuItem.target = self menuItem.target = self
menuItem.isHidden = !tunnel.isTunnelConfigurationAvailableInKeychain
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex) insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
if numberOfTunnelMenuItems == 0 { if numberOfTunnelMenuItems == 0 {
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1) insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class ButtonRow: NSView {
let button: NSButton = {
let button = NSButton()
button.title = ""
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
var buttonTitle: String {
get { return button.title }
set(value) { button.title = value }
}
var isButtonEnabled: Bool {
get { return button.isEnabled }
set(value) { button.isEnabled = value }
}
var buttonToolTip: String {
get { return button.toolTip ?? "" }
set(value) { button.toolTip = value }
}
var onButtonClicked: (() -> Void)?
var observationToken: AnyObject?
override var intrinsicContentSize: NSSize {
return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height)
}
init() {
super.init(frame: CGRect.zero)
button.target = self
button.action = #selector(buttonClicked)
addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 155),
button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
])
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func buttonClicked() {
onButtonClicked?()
}
override func prepareForReuse() {
buttonTitle = ""
buttonToolTip = ""
onButtonClicked = nil
observationToken = nil
}
}
@@ -6,7 +6,6 @@ import Cocoa
private let fontSize: CGFloat = 15 private let fontSize: CGFloat = 15
class ConfTextStorage: NSTextStorage { class ConfTextStorage: NSTextStorage {
let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize)) let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
private let boldFont = NSFont.boldSystemFont(ofSize: fontSize) private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask) private lazy var italicFont = NSFontManager.shared.convert(defaultFont, toHaveTrait: .italicFontMask)
@@ -17,6 +16,11 @@ class ConfTextStorage: NSTextStorage {
private(set) var hasError = false private(set) var hasError = false
private(set) var privateKeyString: String? private(set) var privateKeyString: String?
private(set) var hasOnePeer: Bool = false
private(set) var lastOnePeerAllowedIPs = [String]()
private(set) var lastOnePeerDNSServers = [String]()
private(set) var lastOnePeerHasPublicKey = false
override init() { override init() {
backingStore = NSMutableAttributedString(string: "") backingStore = NSMutableAttributedString(string: "")
super.init() super.init()
@@ -81,6 +85,55 @@ class ConfTextStorage: NSTextStorage {
endEditing() endEditing()
} }
func resetLastPeer() {
hasOnePeer = false
lastOnePeerAllowedIPs = []
lastOnePeerDNSServers = []
lastOnePeerHasPublicKey = false
}
func evaluateExcludePrivateIPs(highlightSpans: UnsafePointer<highlight_span>) {
var spans = highlightSpans
enum FieldType: String {
case dns
case allowedips
}
var fieldType: FieldType?
resetLastPeer()
while spans.pointee.type != HighlightEnd {
let span = spans.pointee
var substring = backingStore.attributedSubstring(from: NSRange(location: span.start, length: span.len)).string.lowercased()
if span.type == HighlightError {
resetLastPeer()
return
} else if span.type == HighlightSection {
if substring == "[peer]" {
if hasOnePeer {
resetLastPeer()
return
}
hasOnePeer = true
}
} else if span.type == HighlightField {
fieldType = FieldType(rawValue: substring)
} else if span.type == HighlightIP && fieldType == .dns {
lastOnePeerDNSServers.append(substring)
} else if span.type == HighlightIP && fieldType == .allowedips {
let next = spans.successor()
let nextnext = next.successor()
if next.pointee.type == HighlightDelimiter && nextnext.pointee.type == HighlightCidr {
substring += backingStore.attributedSubstring(from: NSRange(location: next.pointee.start, length: next.pointee.len)).string +
backingStore.attributedSubstring(from: NSRange(location: nextnext.pointee.start, length: nextnext.pointee.len)).string
}
lastOnePeerAllowedIPs.append(substring)
} else if span.type == HighlightPublicKey {
lastOnePeerHasPublicKey = true
}
spans = spans.successor()
}
}
func highlightSyntax() { func highlightSyntax() {
guard let textColorTheme = textColorTheme else { return } guard let textColorTheme = textColorTheme else { return }
hasError = false hasError = false
@@ -94,8 +147,10 @@ class ConfTextStorage: NSTextStorage {
.font: defaultFont .font: defaultFont
] ]
backingStore.setAttributes(defaultAttributes, range: fullTextRange) backingStore.setAttributes(defaultAttributes, range: fullTextRange)
var spans = highlight_config(backingStore.string.cString(using: String.Encoding.utf8))! var spans = highlight_config(backingStore.string)!
evaluateExcludePrivateIPs(highlightSpans: spans)
let spansStart = spans
while spans.pointee.type != HighlightEnd { while spans.pointee.type != HighlightEnd {
let span = spans.pointee let span = spans.pointee
@@ -115,6 +170,7 @@ class ConfTextStorage: NSTextStorage {
spans = spans.successor() spans = spans.successor()
} }
backingStore.endEditing() backingStore.endEditing()
free(spansStart)
beginEditing() beginEditing()
edited(.editedAttributes, range: fullTextRange, changeInLength: 0) edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
@@ -9,10 +9,12 @@ class ConfTextView: NSTextView {
@objc dynamic var hasError: Bool = false @objc dynamic var hasError: Bool = false
@objc dynamic var privateKeyString: String? @objc dynamic var privateKeyString: String?
@objc dynamic var singlePeerAllowedIPs: [String]?
override var string: String { override var string: String {
didSet { didSet {
confTextStorage.highlightSyntax() confTextStorage.highlightSyntax()
updateConfigData()
} }
} }
@@ -52,18 +54,35 @@ class ConfTextView: NSTextView {
} }
} }
} private func updateConfigData() {
extension ConfTextView: NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
confTextStorage.highlightSyntax()
if hasError != confTextStorage.hasError { if hasError != confTextStorage.hasError {
hasError = confTextStorage.hasError hasError = confTextStorage.hasError
} }
if privateKeyString != confTextStorage.privateKeyString { if privateKeyString != confTextStorage.privateKeyString {
privateKeyString = confTextStorage.privateKeyString privateKeyString = confTextStorage.privateKeyString
} }
let hasSyntaxError = confTextStorage.hasError
let hasSemanticError = confTextStorage.privateKeyString == nil || !confTextStorage.lastOnePeerHasPublicKey
let updatedSinglePeerAllowedIPs = confTextStorage.hasOnePeer && !hasSyntaxError && !hasSemanticError ? confTextStorage.lastOnePeerAllowedIPs : nil
if singlePeerAllowedIPs != updatedSinglePeerAllowedIPs {
singlePeerAllowedIPs = updatedSinglePeerAllowedIPs
}
}
func setConfText(_ text: String) {
let fullTextRange = NSRange(location: 0, length: (string as NSString).length)
if shouldChangeText(in: fullTextRange, replacementString: text) {
replaceCharacters(in: fullTextRange, with: text)
didChangeText()
}
}
}
extension ConfTextView: NSTextViewDelegate {
func textDidChange(_ notification: Notification) {
confTextStorage.highlightSyntax()
updateConfigData()
needsDisplay = true needsDisplay = true
} }
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class DeleteTunnelsConfirmationAlert: NSAlert {
var alertDeleteButton: NSButton?
var alertCancelButton: NSButton?
var onDeleteClicked: ((_ completionHandler: @escaping () -> Void) -> Void)?
override init() {
super.init()
let alertDeleteButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
alertDeleteButton.target = self
alertDeleteButton.action = #selector(removeTunnelAlertDeleteClicked)
self.alertDeleteButton = alertDeleteButton
self.alertCancelButton = addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
}
@objc func removeTunnelAlertDeleteClicked() {
alertDeleteButton?.title = tr("macDeleteTunnelConfirmationAlertButtonTitleDeleting")
alertDeleteButton?.isEnabled = false
alertCancelButton?.isEnabled = false
if let onDeleteClicked = onDeleteClicked {
onDeleteClicked { [weak self] in
guard let self = self else { return }
self.window.sheetParent?.endSheet(self.window)
}
}
}
func beginSheetModal(for sheetWindow: NSWindow) {
beginSheetModal(for: sheetWindow) { _ in }
}
}
@@ -24,6 +24,8 @@ class EditableKeyValueRow: NSView {
return valueLabel return valueLabel
}() }()
let valueImageView: NSImageView?
var key: String { var key: String {
get { return keyLabel.stringValue } get { return keyLabel.stringValue }
set(value) { keyLabel.stringValue = value } set(value) { keyLabel.stringValue = value }
@@ -42,13 +44,24 @@ class EditableKeyValueRow: NSView {
} }
} }
} }
var valueImage: NSImage? {
get { return valueImageView?.image }
set(value) { valueImageView?.image = value }
}
var observationToken: AnyObject?
override var intrinsicContentSize: NSSize { override var intrinsicContentSize: NSSize {
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height) let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.height)
return NSSize(width: NSView.noIntrinsicMetric, height: height) return NSSize(width: NSView.noIntrinsicMetric, height: height)
} }
init() { convenience init() {
self.init(hasValueImage: false)
}
fileprivate init(hasValueImage: Bool) {
valueImageView = hasValueImage ? NSImageView() : nil
super.init(frame: CGRect.zero) super.init(frame: CGRect.zero)
addSubview(keyLabel) addSubview(keyLabel)
@@ -60,10 +73,24 @@ class EditableKeyValueRow: NSView {
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor), keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor), keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor),
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor), self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
keyLabel.trailingAnchor.constraint(equalTo: valueLabel.leadingAnchor, constant: -5),
valueLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor) valueLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor)
]) ])
let spacing: CGFloat = 5
if let valueImageView = valueImageView {
addSubview(valueImageView)
valueImageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
valueImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
valueImageView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing),
valueLabel.leadingAnchor.constraint(equalTo: valueImageView.trailingAnchor)
])
} else {
NSLayoutConstraint.activate([
valueLabel.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: spacing)
])
}
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal) keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal) valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
@@ -81,12 +108,26 @@ class EditableKeyValueRow: NSView {
key = "" key = ""
value = "" value = ""
isKeyInBold = false isKeyInBold = false
observationToken = nil
} }
} }
class KeyValueRow: EditableKeyValueRow { class KeyValueRow: EditableKeyValueRow {
override init() { init() {
super.init() super.init(hasValueImage: false)
valueLabel.isEditable = false
valueLabel.isBordered = false
valueLabel.backgroundColor = .clear
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class KeyValueImageRow: EditableKeyValueRow {
init() {
super.init(hasValueImage: true)
valueLabel.isEditable = false valueLabel.isEditable = false
valueLabel.isBordered = false valueLabel.isBordered = false
valueLabel.backgroundColor = .clear valueLabel.backgroundColor = .clear
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LogViewCell: NSTextField {
init() {
super.init(frame: .zero)
isSelectable = false
isEditable = false
isBordered = false
backgroundColor = .clear
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
stringValue = ""
preferredMaxLayoutWidth = 0
}
}
class LogViewTimestampCell: LogViewCell {
override init() {
super.init()
maximumNumberOfLines = 1
lineBreakMode = .byClipping
preferredMaxLayoutWidth = 0
setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
setContentHuggingPriority(.defaultLow, for: .vertical)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class LogViewMessageCell: LogViewCell {
override init() {
super.init()
maximumNumberOfLines = 0
lineBreakMode = .byWordWrapping
setContentCompressionResistancePriority(.required, for: .vertical)
setContentHuggingPriority(.required, for: .vertical)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
import CoreWLAN
class OnDemandControlsRow: NSView {
let keyLabel: NSTextField = {
let keyLabel = NSTextField()
keyLabel.stringValue = tr("macFieldOnDemand")
keyLabel.isEditable = false
keyLabel.isSelectable = false
keyLabel.isBordered = false
keyLabel.alignment = .right
keyLabel.maximumNumberOfLines = 1
keyLabel.lineBreakMode = .byTruncatingTail
keyLabel.backgroundColor = .clear
return keyLabel
}()
let onDemandEthernetCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = tr("tunnelOnDemandEthernet")
checkbox.setButtonType(.switch)
checkbox.state = .off
return checkbox
}()
let onDemandWiFiCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = tr("tunnelOnDemandWiFi")
checkbox.setButtonType(.switch)
checkbox.state = .off
return checkbox
}()
static let onDemandSSIDOptions: [ActivateOnDemandViewModel.OnDemandSSIDOption] = [
.anySSID, .onlySpecificSSIDs, .exceptSpecificSSIDs
]
let onDemandSSIDOptionsPopup = NSPopUpButton()
let onDemandSSIDsField: NSTokenField = {
let tokenField = NSTokenField()
tokenField.tokenizingCharacterSet = CharacterSet([])
tokenField.tokenStyle = .squared
NSLayoutConstraint.activate([
tokenField.widthAnchor.constraint(greaterThanOrEqualToConstant: 180)
])
return tokenField
}()
override var intrinsicContentSize: NSSize {
let minHeight: CGFloat = 22
let height = max(minHeight, keyLabel.intrinsicContentSize.height,
onDemandEthernetCheckbox.intrinsicContentSize.height, onDemandWiFiCheckbox.intrinsicContentSize.height,
onDemandSSIDOptionsPopup.intrinsicContentSize.height, onDemandSSIDsField.intrinsicContentSize.height)
return NSSize(width: NSView.noIntrinsicMetric, height: height)
}
var onDemandViewModel: ActivateOnDemandViewModel? {
didSet { updateControls() }
}
var currentSSIDs: [String]
init() {
currentSSIDs = getCurrentSSIDs()
super.init(frame: CGRect.zero)
onDemandSSIDOptionsPopup.addItems(withTitles: OnDemandControlsRow.onDemandSSIDOptions.map { $0.localizedUIString })
let stackView = NSStackView()
stackView.setViews([onDemandEthernetCheckbox, onDemandWiFiCheckbox, onDemandSSIDOptionsPopup, onDemandSSIDsField], in: .leading)
stackView.orientation = .horizontal
addSubview(keyLabel)
addSubview(stackView)
keyLabel.translatesAutoresizingMaskIntoConstraints = false
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
stackView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
stackView.leadingAnchor.constraint(equalTo: keyLabel.trailingAnchor, constant: 5),
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor)
])
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
widthConstraint.priority = .defaultHigh + 1
widthConstraint.isActive = true
NSLayoutConstraint.activate([
onDemandEthernetCheckbox.centerYAnchor.constraint(equalTo: stackView.centerYAnchor),
onDemandWiFiCheckbox.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
onDemandSSIDOptionsPopup.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor),
onDemandSSIDsField.lastBaselineAnchor.constraint(equalTo: onDemandEthernetCheckbox.lastBaselineAnchor)
])
onDemandSSIDsField.setContentHuggingPriority(.defaultLow, for: .horizontal)
onDemandEthernetCheckbox.target = self
onDemandEthernetCheckbox.action = #selector(ethernetCheckboxToggled)
onDemandWiFiCheckbox.target = self
onDemandWiFiCheckbox.action = #selector(wiFiCheckboxToggled)
onDemandSSIDOptionsPopup.target = self
onDemandSSIDOptionsPopup.action = #selector(ssidOptionsPopupValueChanged)
onDemandSSIDsField.delegate = self
updateControls()
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func saveToViewModel() {
guard let onDemandViewModel = onDemandViewModel else { return }
onDemandViewModel.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
onDemandViewModel.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
onDemandViewModel.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[onDemandSSIDOptionsPopup.indexOfSelectedItem]
onDemandViewModel.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
}
func updateControls() {
guard let onDemandViewModel = onDemandViewModel else { return }
onDemandEthernetCheckbox.state = onDemandViewModel.isNonWiFiInterfaceEnabled ? .on : .off
onDemandWiFiCheckbox.state = onDemandViewModel.isWiFiInterfaceEnabled ? .on : .off
let optionIndex = OnDemandControlsRow.onDemandSSIDOptions.firstIndex(of: onDemandViewModel.ssidOption)
onDemandSSIDOptionsPopup.selectItem(at: optionIndex ?? 0)
onDemandSSIDsField.objectValue = onDemandViewModel.selectedSSIDs
onDemandSSIDOptionsPopup.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled
onDemandSSIDsField.isHidden = !onDemandViewModel.isWiFiInterfaceEnabled || onDemandViewModel.ssidOption == .anySSID
}
@objc func ethernetCheckboxToggled() {
onDemandViewModel?.isNonWiFiInterfaceEnabled = onDemandEthernetCheckbox.state == .on
}
@objc func wiFiCheckboxToggled() {
onDemandViewModel?.isWiFiInterfaceEnabled = onDemandWiFiCheckbox.state == .on
updateControls()
}
@objc func ssidOptionsPopupValueChanged() {
let selectedIndex = onDemandSSIDOptionsPopup.indexOfSelectedItem
onDemandViewModel?.ssidOption = OnDemandControlsRow.onDemandSSIDOptions[selectedIndex]
onDemandViewModel?.selectedSSIDs = (onDemandSSIDsField.objectValue as? [String]) ?? []
updateControls()
if !onDemandSSIDsField.isHidden {
onDemandSSIDsField.becomeFirstResponder()
}
}
}
extension OnDemandControlsRow: NSTokenFieldDelegate {
func tokenField(_ tokenField: NSTokenField, completionsForSubstring substring: String, indexOfToken tokenIndex: Int, indexOfSelectedItem selectedIndex: UnsafeMutablePointer<Int>?) -> [Any]? {
return currentSSIDs.filter { $0.hasPrefix(substring) }
}
}
private func getCurrentSSIDs() -> [String] {
return CWWiFiClient.shared().interfaces()?.compactMap { $0.ssid() } ?? []
}
@@ -1,75 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class PopupRow: NSView {
let keyLabel: NSTextField = {
let keyLabel = NSTextField()
keyLabel.isEditable = false
keyLabel.isSelectable = false
keyLabel.isBordered = false
keyLabel.alignment = .right
keyLabel.maximumNumberOfLines = 1
keyLabel.lineBreakMode = .byTruncatingTail
keyLabel.backgroundColor = .clear
return keyLabel
}()
let valuePopup = NSPopUpButton()
var key: String {
get { return keyLabel.stringValue }
set(value) { keyLabel.stringValue = value }
}
var valueOptions: [String] {
get { return valuePopup.itemTitles }
set(value) {
valuePopup.removeAllItems()
valuePopup.addItems(withTitles: value)
}
}
var selectedOptionIndex: Int {
get { return valuePopup.indexOfSelectedItem }
set(value) { valuePopup.selectItem(at: value) }
}
override var intrinsicContentSize: NSSize {
let height = max(keyLabel.intrinsicContentSize.height, valuePopup.intrinsicContentSize.height)
return NSSize(width: NSView.noIntrinsicMetric, height: height)
}
init() {
super.init(frame: CGRect.zero)
addSubview(keyLabel)
addSubview(valuePopup)
keyLabel.translatesAutoresizingMaskIntoConstraints = false
valuePopup.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
keyLabel.firstBaselineAnchor.constraint(equalTo: valuePopup.firstBaselineAnchor),
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
keyLabel.trailingAnchor.constraint(equalTo: valuePopup.leadingAnchor, constant: -5)
])
keyLabel.setContentCompressionResistancePriority(.defaultHigh + 2, for: .horizontal)
keyLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
let widthConstraint = keyLabel.widthAnchor.constraint(equalToConstant: 150)
widthConstraint.priority = .defaultHigh + 1
widthConstraint.isActive = true
}
required init?(coder decoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
key = ""
valueOptions = []
}
}
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class ButtonedDetailViewController: NSViewController {
var onButtonClicked: (() -> Void)?
let button: NSButton = {
let button = NSButton()
button.title = ""
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let view = NSView()
button.target = self
button.action = #selector(buttonClicked)
view.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
NSLayoutConstraint.activate([
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
])
self.view = view
}
func setButtonTitle(_ title: String) {
button.title = title
}
@objc func buttonClicked() {
onButtonClicked?()
}
}
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class LogViewController: NSViewController {
enum LogColumn: String {
case time = "Time"
case logMessage = "LogMessage"
func createColumn() -> NSTableColumn {
return NSTableColumn(identifier: NSUserInterfaceItemIdentifier(rawValue))
}
func isRepresenting(tableColumn: NSTableColumn?) -> Bool {
return tableColumn?.identifier.rawValue == rawValue
}
}
let scrollView: NSScrollView = {
let scrollView = NSScrollView()
scrollView.hasVerticalScroller = true
scrollView.autohidesScrollers = false
scrollView.borderType = .bezelBorder
return scrollView
}()
let tableView: NSTableView = {
let tableView = NSTableView()
let timeColumn = LogColumn.time.createColumn()
timeColumn.title = tr("macLogColumnTitleTime")
timeColumn.width = 160
timeColumn.resizingMask = []
tableView.addTableColumn(timeColumn)
let messageColumn = LogColumn.logMessage.createColumn()
messageColumn.title = tr("macLogColumnTitleLogMessage")
messageColumn.minWidth = 360
messageColumn.resizingMask = .autoresizingMask
tableView.addTableColumn(messageColumn)
tableView.rowSizeStyle = .custom
tableView.rowHeight = 16
tableView.usesAlternatingRowBackgroundColors = true
tableView.usesAutomaticRowHeights = true
tableView.allowsColumnReordering = false
tableView.allowsColumnResizing = true
tableView.allowsMultipleSelection = true
return tableView
}()
let progressIndicator: NSProgressIndicator = {
let progressIndicator = NSProgressIndicator()
progressIndicator.controlSize = .small
progressIndicator.isIndeterminate = true
progressIndicator.style = .spinning
progressIndicator.isDisplayedWhenStopped = false
return progressIndicator
}()
let closeButton: NSButton = {
let button = NSButton()
button.title = tr("macLogButtonTitleClose")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
let saveButton: NSButton = {
let button = NSButton()
button.title = tr("macLogButtonTitleSave")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
let logViewHelper: LogViewHelper?
var logEntries = [LogViewHelper.LogEntry]()
var isFetchingLogEntries = false
var isInScrolledToEndMode = true
private var updateLogEntriesTimer: Timer?
init() {
logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
tableView.dataSource = self
tableView.delegate = self
closeButton.target = self
closeButton.action = #selector(closeClicked)
saveButton.target = self
saveButton.action = #selector(saveClicked)
saveButton.isEnabled = false
let clipView = NSClipView()
clipView.documentView = tableView
scrollView.contentView = clipView
_ = NotificationCenter.default.addObserver(forName: NSView.boundsDidChangeNotification, object: clipView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
let lastVisibleRowIndex = self.tableView.row(at: NSPoint(x: 0, y: self.scrollView.contentView.documentVisibleRect.maxY - 1))
self.isInScrolledToEndMode = lastVisibleRowIndex < 0 || lastVisibleRowIndex == self.logEntries.count - 1
}
_ = NotificationCenter.default.addObserver(forName: NSView.frameDidChangeNotification, object: tableView, queue: OperationQueue.main) { [weak self] _ in
guard let self = self else { return }
if self.isInScrolledToEndMode {
DispatchQueue.main.async {
self.tableView.scroll(NSPoint(x: 0, y: self.tableView.frame.maxY - clipView.documentVisibleRect.height))
}
}
}
let margin: CGFloat = 20
let internalSpacing: CGFloat = 10
let buttonRowStackView = NSStackView()
buttonRowStackView.addView(closeButton, in: .leading)
buttonRowStackView.addView(saveButton, in: .trailing)
buttonRowStackView.orientation = .horizontal
buttonRowStackView.spacing = internalSpacing
let containerView = NSView()
[scrollView, progressIndicator, buttonRowStackView].forEach { view in
containerView.addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: margin),
scrollView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
containerView.rightAnchor.constraint(equalTo: scrollView.rightAnchor, constant: margin),
buttonRowStackView.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: internalSpacing),
buttonRowStackView.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: margin),
containerView.rightAnchor.constraint(equalTo: buttonRowStackView.rightAnchor, constant: margin),
containerView.bottomAnchor.constraint(equalTo: buttonRowStackView.bottomAnchor, constant: margin),
progressIndicator.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
progressIndicator.centerYAnchor.constraint(equalTo: scrollView.centerYAnchor)
])
NSLayoutConstraint.activate([
containerView.widthAnchor.constraint(equalToConstant: 640),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
])
containerView.frame = NSRect(x: 0, y: 0, width: 640, height: 480)
view = containerView
progressIndicator.startAnimation(self)
startUpdatingLogEntries()
}
func updateLogEntries() {
guard !isFetchingLogEntries else { return }
isFetchingLogEntries = true
logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
guard let self = self else { return }
defer {
self.isFetchingLogEntries = false
}
if !self.progressIndicator.isHidden {
self.progressIndicator.stopAnimation(self)
self.saveButton.isEnabled = true
}
guard !fetchedLogEntries.isEmpty else { return }
let oldCount = self.logEntries.count
self.logEntries.append(contentsOf: fetchedLogEntries)
self.tableView.insertRows(at: IndexSet(integersIn: oldCount ..< oldCount + fetchedLogEntries.count), withAnimation: .slideDown)
}
}
func startUpdatingLogEntries() {
updateLogEntries()
updateLogEntriesTimer?.invalidate()
let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
self?.updateLogEntries()
}
updateLogEntriesTimer = timer
RunLoop.main.add(timer, forMode: .common)
}
func stopUpdatingLogEntries() {
updateLogEntriesTimer?.invalidate()
updateLogEntriesTimer = nil
}
override func viewWillDisappear() {
super.viewWillDisappear()
stopUpdatingLogEntries()
}
@objc func saveClicked() {
let savePanel = NSSavePanel()
savePanel.prompt = tr("macSheetButtonExportLog")
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
savePanel.beginSheetModal(for: self.view.window!) { [weak self] response in
guard response == .OK else { return }
guard let destinationURL = savePanel.url else { return }
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
guard isWritten else {
DispatchQueue.main.async { [weak self] in
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
}
return
}
DispatchQueue.main.async { [weak self] in
self?.dismiss(self)
}
}
}
}
@objc func closeClicked() {
dismiss(self)
}
@objc func copy(_ sender: Any?) {
let text = tableView.selectedRowIndexes.sorted().reduce("") { $0 + self.logEntries[$1].text() + "\n" }
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.writeObjects([text as NSString])
}
}
extension LogViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return logEntries.count
}
}
extension LogViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if LogColumn.time.isRepresenting(tableColumn: tableColumn) {
let cell: LogViewTimestampCell = tableView.dequeueReusableCell()
cell.stringValue = logEntries[row].timestamp
return cell
} else if LogColumn.logMessage.isRepresenting(tableColumn: tableColumn) {
let cell: LogViewMessageCell = tableView.dequeueReusableCell()
cell.stringValue = logEntries[row].message
cell.preferredMaxLayoutWidth = tableColumn?.width ?? 0
return cell
} else {
fatalError()
}
}
}
@@ -54,8 +54,7 @@ class ManageTunnelsRootViewController: NSViewController {
tunnelDetailContainerView.topAnchor.constraint(equalTo: container.topAnchor), tunnelDetailContainerView.topAnchor.constraint(equalTo: container.topAnchor),
tunnelDetailContainerView.bottomAnchor.constraint(equalTo: container.bottomAnchor), tunnelDetailContainerView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing), tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing),
tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor), tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
tunnelsListView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.3)
]) ])
} }
@@ -78,14 +77,40 @@ class ManageTunnelsRootViewController: NSViewController {
} }
extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate { extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate {
func tunnelSelected(tunnel: TunnelContainer) { func tunnelsSelected(tunnelIndices: [Int]) {
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel) assert(!tunnelIndices.isEmpty)
setTunnelDetailContentVC(tunnelDetailVC) if tunnelIndices.count == 1 {
self.tunnelDetailVC = tunnelDetailVC let tunnel = tunnelsManager.tunnel(at: tunnelIndices.first!)
if tunnel.isTunnelConfigurationAvailableInKeychain {
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
setTunnelDetailContentVC(tunnelDetailVC)
self.tunnelDetailVC = tunnelDetailVC
} else {
let unusableTunnelDetailVC = tunnelDetailContentVC as? UnusableTunnelDetailViewController ?? UnusableTunnelDetailViewController()
unusableTunnelDetailVC.onButtonClicked = { [weak tunnelsListVC] in
tunnelsListVC?.handleRemoveTunnelAction()
}
setTunnelDetailContentVC(unusableTunnelDetailVC)
self.tunnelDetailVC = nil
}
} else if tunnelIndices.count > 1 {
let multiSelectionVC = tunnelDetailContentVC as? ButtonedDetailViewController ?? ButtonedDetailViewController()
multiSelectionVC.setButtonTitle(tr(format: "macButtonDeleteTunnels (%d)", tunnelIndices.count))
multiSelectionVC.onButtonClicked = { [weak tunnelsListVC] in
tunnelsListVC?.handleRemoveTunnelAction()
}
setTunnelDetailContentVC(multiSelectionVC)
self.tunnelDetailVC = nil
}
} }
func tunnelsListEmpty() { func tunnelsListEmpty() {
let noTunnelsVC = NoTunnelsDetailViewController(tunnelsManager: tunnelsManager) let noTunnelsVC = ButtonedDetailViewController()
noTunnelsVC.setButtonTitle(tr("macButtonImportTunnels"))
noTunnelsVC.onButtonClicked = { [weak self] in
guard let self = self else { return }
ImportPanelPresenter.presentImportPanel(tunnelsManager: self.tunnelsManager, sourceVC: self)
}
setTunnelDetailContentVC(noTunnelsVC) setTunnelDetailContentVC(noTunnelsVC)
self.tunnelDetailVC = nil self.tunnelDetailVC = nil
} }
@@ -102,7 +127,7 @@ extension ManageTunnelsRootViewController {
switch event.charactersIgnoringModifiers { switch event.charactersIgnoringModifiers {
case "n": case "n":
tunnelsListVC?.handleAddEmptyTunnelAction() tunnelsListVC?.handleAddEmptyTunnelAction()
case "i": case "o":
tunnelsListVC?.handleImportTunnelAction() tunnelsListVC?.handleImportTunnelAction()
case "t": case "t":
tunnelDetailVC?.handleToggleActiveStatusAction() tunnelDetailVC?.handleToggleActiveStatusAction()
@@ -1,46 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class NoTunnelsDetailViewController: NSViewController {
let tunnelsManager: TunnelsManager
let importButton: NSButton = {
let button = NSButton()
button.title = tr("macButtonImportTunnels")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
init(tunnelsManager: TunnelsManager) {
self.tunnelsManager = tunnelsManager
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let view = NSView()
importButton.target = self
importButton.action = #selector(importTunnelClicked)
view.addSubview(importButton)
importButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
importButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
importButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
self.view = view
}
@objc func importTunnelClicked() {
// We pass sourceVC as parent instead of self because this VC will not be visible when the import completes
ImportPanelPresenter.presentImportPanel(tunnelsManager: tunnelsManager, sourceVC: parent)
}
}
@@ -9,13 +9,15 @@ class TunnelDetailTableViewController: NSViewController {
case interfaceFieldRow(TunnelViewModel.InterfaceField) case interfaceFieldRow(TunnelViewModel.InterfaceField)
case peerFieldRow(peer: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField) case peerFieldRow(peer: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField)
case onDemandRow case onDemandRow
case onDemandSSIDRow
case spacerRow case spacerRow
func localizedSectionKeyString() -> String { func localizedSectionKeyString() -> String {
switch self { switch self {
case .interfaceFieldRow: return tr("tunnelSectionTitleInterface") case .interfaceFieldRow: return tr("tunnelSectionTitleInterface")
case .peerFieldRow: return tr("tunnelSectionTitlePeer") case .peerFieldRow: return tr("tunnelSectionTitlePeer")
case .onDemandRow: return "" case .onDemandRow: return tr("macFieldOnDemand")
case .onDemandSSIDRow: return ""
case .spacerRow: return "" case .spacerRow: return ""
} }
} }
@@ -25,14 +27,15 @@ class TunnelDetailTableViewController: NSViewController {
case .interfaceFieldRow(let field): return field == .name case .interfaceFieldRow(let field): return field == .name
case .peerFieldRow(_, let field): return field == .publicKey case .peerFieldRow(_, let field): return field == .publicKey
case .onDemandRow: return true case .onDemandRow: return true
case .onDemandSSIDRow: return false
case .spacerRow: return false case .spacerRow: return false
} }
} }
} }
static let interfaceFields: [TunnelViewModel.InterfaceField] = [ static let interfaceFields: [TunnelViewModel.InterfaceField] = [
.name, .publicKey, .addresses, .name, .status, .publicKey, .addresses,
.listenPort, .mtu, .dns .listenPort, .mtu, .dns, .toggleStatus
] ]
static let peerFields: [TunnelViewModel.PeerField] = [ static let peerFields: [TunnelViewModel.PeerField] = [
@@ -41,6 +44,10 @@ class TunnelDetailTableViewController: NSViewController {
.rxBytes, .txBytes, .lastHandshakeTime .rxBytes, .txBytes, .lastHandshakeTime
] ]
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
.onDemand, .ssid
]
let tableView: NSTableView = { let tableView: NSTableView = {
let tableView = NSTableView() let tableView = NSTableView()
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelDetail"))) tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelDetail")))
@@ -51,21 +58,12 @@ class TunnelDetailTableViewController: NSViewController {
return tableView return tableView
}() }()
let statusCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = ""
checkbox.setButtonType(.switch)
checkbox.state = .off
checkbox.toolTip = "Toggle status (⌘T)"
return checkbox
}()
let editButton: NSButton = { let editButton: NSButton = {
let button = NSButton() let button = NSButton()
button.title = tr("Edit") button.title = tr("Edit")
button.setButtonType(.momentaryPushIn) button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded button.bezelStyle = .rounded
button.toolTip = "Edit tunnel (⌘E)" button.toolTip = tr("macToolTipEditTunnel")
return button return button
}() }()
@@ -85,6 +83,9 @@ class TunnelDetailTableViewController: NSViewController {
updateTableViewModelRows() updateTableViewModelRows()
} }
} }
var onDemandViewModel: ActivateOnDemandViewModel
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]() private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
private var tableViewModelRows = [TableViewModelRow]() private var tableViewModelRows = [TableViewModelRow]()
@@ -96,6 +97,7 @@ class TunnelDetailTableViewController: NSViewController {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
self.tunnel = tunnel self.tunnel = tunnel
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration) tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
updateTableViewModelRowsBySection() updateTableViewModelRowsBySection()
updateTableViewModelRows() updateTableViewModelRows()
@@ -113,9 +115,6 @@ class TunnelDetailTableViewController: NSViewController {
tableView.dataSource = self tableView.dataSource = self
tableView.delegate = self tableView.delegate = self
statusCheckbox.target = self
statusCheckbox.action = #selector(statusCheckboxToggled(sender:))
editButton.target = self editButton.target = self
editButton.action = #selector(handleEditTunnelAction) editButton.action = #selector(handleEditTunnelAction)
@@ -133,11 +132,9 @@ class TunnelDetailTableViewController: NSViewController {
containerView.addLayoutGuide(bottomControlsContainer) containerView.addLayoutGuide(bottomControlsContainer)
containerView.addSubview(box) containerView.addSubview(box)
containerView.addSubview(scrollView) containerView.addSubview(scrollView)
containerView.addSubview(statusCheckbox)
containerView.addSubview(editButton) containerView.addSubview(editButton)
box.translatesAutoresizingMaskIntoConstraints = false box.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false
statusCheckbox.translatesAutoresizingMaskIntoConstraints = false
editButton.translatesAutoresizingMaskIntoConstraints = false editButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@@ -149,10 +146,8 @@ class TunnelDetailTableViewController: NSViewController {
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32), bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor), scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
statusCheckbox.leadingAnchor.constraint(equalTo: bottomControlsContainer.leadingAnchor),
bottomControlsContainer.bottomAnchor.constraint(equalTo: statusCheckbox.bottomAnchor, constant: 4),
editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor), editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor),
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 4) bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0)
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
@@ -162,6 +157,11 @@ class TunnelDetailTableViewController: NSViewController {
scrollView.trailingAnchor.constraint(equalTo: box.trailingAnchor) scrollView.trailingAnchor.constraint(equalTo: box.trailingAnchor)
]) ])
NSLayoutConstraint.activate([
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
])
view = containerView view = containerView
} }
@@ -170,7 +170,9 @@ class TunnelDetailTableViewController: NSViewController {
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]() var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
for field in TunnelDetailTableViewController.interfaceFields { for field in TunnelDetailTableViewController.interfaceFields {
interfaceSection.append((isVisible: !tunnelViewModel.interfaceData[field].isEmpty, modelRow: .interfaceFieldRow(field))) let isStatus = field == .status || field == .toggleStatus
let isEmpty = tunnelViewModel.interfaceData[field].isEmpty
interfaceSection.append((isVisible: isStatus || !isEmpty, modelRow: .interfaceFieldRow(field)))
} }
interfaceSection.append((isVisible: true, modelRow: .spacerRow)) interfaceSection.append((isVisible: true, modelRow: .spacerRow))
modelRowsBySection.append(interfaceSection) modelRowsBySection.append(interfaceSection)
@@ -186,6 +188,9 @@ class TunnelDetailTableViewController: NSViewController {
var onDemandSection = [(isVisible: Bool, modelRow: TableViewModelRow)]() var onDemandSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
onDemandSection.append((isVisible: true, modelRow: .onDemandRow)) onDemandSection.append((isVisible: true, modelRow: .onDemandRow))
if onDemandViewModel.isWiFiInterfaceEnabled {
onDemandSection.append((isVisible: true, modelRow: .onDemandSSIDRow))
}
modelRowsBySection.append(onDemandSection) modelRowsBySection.append(onDemandSection)
tableViewModelRowsBySection = modelRowsBySection tableViewModelRowsBySection = modelRowsBySection
@@ -196,28 +201,6 @@ class TunnelDetailTableViewController: NSViewController {
} }
func updateStatus() { func updateStatus() {
let statusText: String
switch tunnel.status {
case .waiting:
statusText = tr("tunnelStatusWaiting")
case .inactive:
statusText = tr("tunnelStatusInactive")
case .activating:
statusText = tr("tunnelStatusActivating")
case .active:
statusText = tr("tunnelStatusActive")
case .deactivating:
statusText = tr("tunnelStatusDeactivating")
case .reasserting:
statusText = tr("tunnelStatusReasserting")
case .restarting:
statusText = tr("tunnelStatusRestarting")
}
statusCheckbox.title = tr(format: "macStatus (%@)", statusText)
let shouldBeChecked = (tunnel.status != .inactive && tunnel.status != .deactivating)
let shouldBeEnabled = (tunnel.status == .active || tunnel.status == .inactive)
statusCheckbox.state = shouldBeChecked ? .on : .off
statusCheckbox.isEnabled = shouldBeEnabled
if tunnel.status == .active { if tunnel.status == .active {
startUpdatingRuntimeConfiguration() startUpdatingRuntimeConfiguration()
} else if tunnel.status == .inactive { } else if tunnel.status == .inactive {
@@ -244,15 +227,6 @@ class TunnelDetailTableViewController: NSViewController {
} }
} }
@objc func statusCheckboxToggled(sender: AnyObject?) {
guard let statusCheckbox = sender as? NSButton else { return }
if statusCheckbox.state == .on {
tunnelsManager.startActivation(of: tunnel)
} else if statusCheckbox.state == .off {
tunnelsManager.startDeactivation(of: tunnel)
}
}
override func viewWillDisappear() { override func viewWillDisappear() {
super.viewWillDisappear() super.viewWillDisappear()
if let tunnelEditVC = tunnelEditVC { if let tunnelEditVC = tunnelEditVC {
@@ -263,7 +237,24 @@ class TunnelDetailTableViewController: NSViewController {
func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) { func applyTunnelConfiguration(tunnelConfiguration: TunnelConfiguration) {
// Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering. // Incorporates changes from tunnelConfiguation. Ignores any changes in peer ordering.
func sectionChanged<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], tableView: NSTableView, rowOffset: Int, changes: [T: TunnelViewModel.ChangeHandlers.FieldChange]) {
let tableView = self.tableView
func handleSectionFieldsModified<T>(fields: [T], modelRowsInSection: [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
var modifiedRowIndices = IndexSet()
for (index, field) in fields.enumerated() {
guard let change = changes[field] else { continue }
if case .modified(_) = change {
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
modifiedRowIndices.insert(rowOffset + row)
}
}
if !modifiedRowIndices.isEmpty {
tableView.reloadData(forRowIndexes: modifiedRowIndices, columnIndexes: IndexSet(integer: 0))
}
}
func handleSectionFieldsAddedOrRemoved<T>(fields: [T], modelRowsInSection: inout [(isVisible: Bool, modelRow: TableViewModelRow)], rowOffset: Int, changes: [T: TunnelViewModel.Changes.FieldChange]) {
for (index, field) in fields.enumerated() { for (index, field) in fields.enumerated() {
guard let change = changes[field] else { continue } guard let change = changes[field] else { continue }
let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count let row = modelRowsInSection[0 ..< index].filter { $0.isVisible }.count
@@ -275,42 +266,54 @@ class TunnelDetailTableViewController: NSViewController {
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade) tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: .effectFade)
modelRowsInSection[index].isVisible = false modelRowsInSection[index].isVisible = false
case .modified: case .modified:
tableView.removeRows(at: IndexSet(integer: rowOffset + row), withAnimation: []) break
tableView.insertRows(at: IndexSet(integer: rowOffset + row), withAnimation: [])
} }
} }
} }
var isChanged = false let changes = self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration)
let changeHandlers = TunnelViewModel.ChangeHandlers(
interfaceChanged: { [weak self] changes in if !changes.interfaceChanges.isEmpty {
guard let self = self else { return } handleSectionFieldsModified(fields: TunnelDetailTableViewController.interfaceFields,
sectionChanged(fields: TunnelDetailTableViewController.interfaceFields, modelRowsInSection: &self.tableViewModelRowsBySection[0], modelRowsInSection: self.tableViewModelRowsBySection[0],
tableView: self.tableView, rowOffset: 0, changes: changes) rowOffset: 0, changes: changes.interfaceChanges)
isChanged = true }
}, for (peerIndex, peerChanges) in changes.peerChanges {
peerChangedAt: { [weak self] peerIndex, changes in let sectionIndex = 1 + peerIndex
guard let self = self else { return } let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
let sectionIndex = 1 + peerIndex handleSectionFieldsModified(fields: TunnelDetailTableViewController.peerFields,
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count modelRowsInSection: self.tableViewModelRowsBySection[sectionIndex],
sectionChanged(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex], rowOffset: rowOffset, changes: peerChanges)
tableView: self.tableView, rowOffset: rowOffset, changes: changes) }
isChanged = true
}, let isAnyInterfaceFieldAddedOrRemoved = changes.interfaceChanges.contains { $0.value == .added || $0.value == .removed }
peersRemovedAt: { [weak self] peerIndices in let isAnyPeerFieldAddedOrRemoved = changes.peerChanges.contains { $0.changes.contains { $0.value == .added || $0.value == .removed } }
guard let self = self else { return }
for peerIndex in peerIndices { if isAnyInterfaceFieldAddedOrRemoved || isAnyPeerFieldAddedOrRemoved || !changes.peersRemovedIndices.isEmpty || !changes.peersInsertedIndices.isEmpty {
tableView.beginUpdates()
if isAnyInterfaceFieldAddedOrRemoved {
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.interfaceFields,
modelRowsInSection: &self.tableViewModelRowsBySection[0],
rowOffset: 0, changes: changes.interfaceChanges)
}
if isAnyPeerFieldAddedOrRemoved {
for (peerIndex, peerChanges) in changes.peerChanges {
let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
handleSectionFieldsAddedOrRemoved(fields: TunnelDetailTableViewController.peerFields, modelRowsInSection: &self.tableViewModelRowsBySection[sectionIndex], rowOffset: rowOffset, changes: peerChanges)
}
}
if !changes.peersRemovedIndices.isEmpty {
for peerIndex in changes.peersRemovedIndices {
let sectionIndex = 1 + peerIndex let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count let count = self.tableViewModelRowsBySection[sectionIndex].filter { $0.isVisible }.count
self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade) self.tableView.removeRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
self.tableViewModelRowsBySection.remove(at: sectionIndex) self.tableViewModelRowsBySection.remove(at: sectionIndex)
} }
isChanged = true }
}, if !changes.peersInsertedIndices.isEmpty {
peersInsertedAt: { [weak self] peerIndices in for peerIndex in changes.peersInsertedIndices {
guard let self = self else { return }
for peerIndex in peerIndices {
let peerData = self.tunnelViewModel.peersData[peerIndex] let peerData = self.tunnelViewModel.peersData[peerIndex]
let sectionIndex = 1 + peerIndex let sectionIndex = 1 + peerIndex
let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count let rowOffset = self.tableViewModelRowsBySection[0 ..< sectionIndex].flatMap { $0.filter { $0.isVisible } }.count
@@ -322,16 +325,10 @@ class TunnelDetailTableViewController: NSViewController {
self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade) self.tableView.insertRows(at: IndexSet(integersIn: rowOffset ..< rowOffset + count), withAnimation: .effectFade)
self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex) self.tableViewModelRowsBySection.insert(modelRowsInSection, at: sectionIndex)
} }
isChanged = true
} }
)
tableView.beginUpdates()
self.tunnelViewModel.applyConfiguration(other: tunnelConfiguration, changeHandlers: changeHandlers)
if isChanged {
updateTableViewModelRows() updateTableViewModelRows()
tableView.endUpdates()
} }
tableView.endUpdates()
} }
private func reloadRuntimeConfiguration() { private func reloadRuntimeConfiguration() {
@@ -370,18 +367,26 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
let modelRow = tableViewModelRows[row] let modelRow = tableViewModelRows[row]
switch modelRow { switch modelRow {
case .interfaceFieldRow(let field): case .interfaceFieldRow(let field):
let cell: KeyValueRow = tableView.dequeueReusableCell() if field == .status {
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString return statusCell()
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString) } else if field == .toggleStatus {
cell.value = tunnelViewModel.interfaceData[field] return toggleStatusCell()
cell.isKeyInBold = modelRow.isTitleRow() } else {
return cell let cell: KeyValueRow = tableView.dequeueReusableCell()
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
cell.value = tunnelViewModel.interfaceData[field]
cell.isKeyInBold = modelRow.isTitleRow()
return cell
}
case .peerFieldRow(let peerData, let field): case .peerFieldRow(let peerData, let field):
let cell: KeyValueRow = tableView.dequeueReusableCell() let cell: KeyValueRow = tableView.dequeueReusableCell()
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
cell.key = tr(format: "macFieldKey (%@)", localizedKeyString) cell.key = tr(format: "macFieldKey (%@)", localizedKeyString)
if field == .persistentKeepAlive { if field == .persistentKeepAlive {
cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field]) cell.value = tr(format: "tunnelPeerPersistentKeepaliveValue (%@)", peerData[field])
} else if field == .preSharedKey {
cell.value = tr("tunnelPeerPresharedKeyEnabled")
} else { } else {
cell.value = peerData[field] cell.value = peerData[field]
} }
@@ -391,10 +396,103 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
return NSView() return NSView()
case .onDemandRow: case .onDemandRow:
let cell: KeyValueRow = tableView.dequeueReusableCell() let cell: KeyValueRow = tableView.dequeueReusableCell()
cell.key = tr("macFieldOnDemand") cell.key = modelRow.localizedSectionKeyString()
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting) cell.value = onDemandViewModel.localizedInterfaceDescription
cell.isKeyInBold = true cell.isKeyInBold = true
return cell return cell
case .onDemandSSIDRow:
let cell: KeyValueRow = tableView.dequeueReusableCell()
cell.key = tr("macFieldOnDemandSSIDs")
let value: String
if onDemandViewModel.ssidOption == .anySSID {
value = onDemandViewModel.ssidOption.localizedUIString
} else {
value = tr(format: "tunnelOnDemandSSIDOptionDescriptionMac (%1$@: %2$@)",
onDemandViewModel.ssidOption.localizedUIString,
onDemandViewModel.selectedSSIDs.joined(separator: ", "))
}
cell.value = value
cell.isKeyInBold = false
return cell
}
}
func statusCell() -> NSView {
let cell: KeyValueImageRow = tableView.dequeueReusableCell()
cell.key = tr(format: "macFieldKey (%@)", tr("tunnelInterfaceStatus"))
cell.value = TunnelDetailTableViewController.localizedStatusDescription(forStatus: tunnel.status)
cell.valueImage = TunnelDetailTableViewController.image(forStatus: tunnel.status)
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
guard let cell = cell else { return }
cell.value = TunnelDetailTableViewController.localizedStatusDescription(forStatus: tunnel.status)
cell.valueImage = TunnelDetailTableViewController.image(forStatus: tunnel.status)
}
return cell
}
func toggleStatusCell() -> NSView {
let cell: ButtonRow = tableView.dequeueReusableCell()
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
cell.buttonToolTip = tr("macToolTipToggleStatus")
cell.onButtonClicked = { [weak self] in
self?.handleToggleActiveStatusAction()
}
cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in
guard let cell = cell else { return }
cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status)
cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive)
}
return cell
}
private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String {
switch status {
case .inactive:
return tr("tunnelStatusInactive")
case .activating:
return tr("tunnelStatusActivating")
case .active:
return tr("tunnelStatusActive")
case .deactivating:
return tr("tunnelStatusDeactivating")
case .reasserting:
return tr("tunnelStatusReasserting")
case .restarting:
return tr("tunnelStatusRestarting")
case .waiting:
return tr("tunnelStatusWaiting")
}
}
private static func image(forStatus status: TunnelStatus?) -> NSImage? {
guard let status = status else { return nil }
switch status {
case .active, .restarting, .reasserting:
return NSImage(named: NSImage.statusAvailableName)
case .activating, .waiting, .deactivating:
return NSImage(named: NSImage.statusPartiallyAvailableName)
case .inactive:
return NSImage(named: NSImage.statusNoneName)
}
}
private static func localizedToggleStatusActionText(forStatus status: TunnelStatus) -> String {
switch status {
case .waiting:
return tr("macToggleStatusButtonWaiting")
case .inactive:
return tr("macToggleStatusButtonActivate")
case .activating:
return tr("macToggleStatusButtonActivating")
case .active:
return tr("macToggleStatusButtonDeactivate")
case .deactivating:
return tr("macToggleStatusButtonDeactivating")
case .reasserting:
return tr("macToggleStatusButtonReasserting")
case .restarting:
return tr("macToggleStatusButtonRestarting")
} }
} }
} }
@@ -402,6 +500,7 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate { extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
func tunnelSaved(tunnel: TunnelContainer) { func tunnelSaved(tunnel: TunnelContainer) {
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration) tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
updateTableViewModelRowsBySection() updateTableViewModelRowsBySection()
updateTableViewModelRows() updateTableViewModelRows()
updateStatus() updateStatus()
@@ -42,11 +42,7 @@ class TunnelEditViewController: NSViewController {
return textView return textView
}() }()
let onDemandRow: PopupRow = { let onDemandControlsRow = OnDemandControlsRow()
let popupRow = PopupRow()
popupRow.key = tr("macFieldOnDemand")
return popupRow
}()
let scrollView: NSScrollView = { let scrollView: NSScrollView = {
let scrollView = NSScrollView() let scrollView = NSScrollView()
@@ -56,6 +52,14 @@ class TunnelEditViewController: NSViewController {
return scrollView return scrollView
}() }()
let excludePrivateIPsCheckbox: NSButton = {
let checkbox = NSButton()
checkbox.title = tr("tunnelPeerExcludePrivateIPs")
checkbox.setButtonType(.switch)
checkbox.state = .off
return checkbox
}()
let discardButton: NSButton = { let discardButton: NSButton = {
let button = NSButton() let button = NSButton()
button.title = tr("macEditDiscard") button.title = tr("macEditDiscard")
@@ -72,24 +76,22 @@ class TunnelEditViewController: NSViewController {
return button return button
}() }()
let activateOnDemandOptions: [ActivateOnDemandOption] = [
.none,
.useOnDemandOverWiFiOrEthernet,
.useOnDemandOverWiFiOnly,
.useOnDemandOverEthernetOnly
]
let tunnelsManager: TunnelsManager let tunnelsManager: TunnelsManager
let tunnel: TunnelContainer? let tunnel: TunnelContainer?
var onDemandViewModel: ActivateOnDemandViewModel
weak var delegate: TunnelEditViewControllerDelegate? weak var delegate: TunnelEditViewControllerDelegate?
var privateKeyObservationToken: AnyObject? var privateKeyObservationToken: AnyObject?
var hasErrorObservationToken: AnyObject? var hasErrorObservationToken: AnyObject?
var singlePeerAllowedIPsObservationToken: AnyObject?
var dnsServersAddedToAllowedIPs: String?
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) { init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) {
self.tunnelsManager = tunnelsManager self.tunnelsManager = tunnelsManager
self.tunnel = tunnel self.tunnel = tunnel
self.onDemandViewModel = tunnel != nil ? ActivateOnDemandViewModel(tunnel: tunnel!) : ActivateOnDemandViewModel()
super.init(nibName: nil, bundle: nil) super.init(nibName: nil, bundle: nil)
} }
@@ -97,8 +99,7 @@ class TunnelEditViewController: NSViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
func populateTextFields() { func populateFields() {
let selectedActivateOnDemandOption: ActivateOnDemandOption
if let tunnel = tunnel { if let tunnel = tunnel {
// Editing an existing tunnel // Editing an existing tunnel
let tunnelConfiguration = tunnel.tunnelConfiguration! let tunnelConfiguration = tunnel.tunnelConfiguration!
@@ -106,11 +107,9 @@ class TunnelEditViewController: NSViewController {
textView.string = tunnelConfiguration.asWgQuickConfig() textView.string = tunnelConfiguration.asWgQuickConfig()
publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? "" publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? ""
textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? "" textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? ""
if tunnel.activateOnDemandSetting.isActivateOnDemandEnabled { let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil
selectedActivateOnDemandOption = tunnel.activateOnDemandSetting.activateOnDemandOption updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation })
} else { dnsServersAddedToAllowedIPs = excludePrivateIPsCheckbox.state == .on ? tunnelConfiguration.interface.dns.map { $0.stringRepresentation }.joined(separator: ", ") : nil
selectedActivateOnDemandOption = .none
}
} else { } else {
// Creating a new tunnel // Creating a new tunnel
let privateKey = Curve25519.generatePrivateKey() let privateKey = Curve25519.generatePrivateKey()
@@ -118,7 +117,8 @@ class TunnelEditViewController: NSViewController {
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n" let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n"
publicKeyRow.value = publicKey.base64Key() ?? "" publicKeyRow.value = publicKey.base64Key() ?? ""
textView.string = bootstrappingText textView.string = bootstrappingText
selectedActivateOnDemandOption = .none updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: nil)
dnsServersAddedToAllowedIPs = nil
} }
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
if let privateKeyString = textView.privateKeyString, if let privateKeyString = textView.privateKeyString,
@@ -133,13 +133,13 @@ class TunnelEditViewController: NSViewController {
hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in
saveButton?.isEnabled = !textView.hasError saveButton?.isEnabled = !textView.hasError
} }
singlePeerAllowedIPsObservationToken = textView.observe(\.singlePeerAllowedIPs) { [weak self] textView, _ in
onDemandRow.valueOptions = activateOnDemandOptions.map { TunnelViewModel.activateOnDemandOptionText(for: $0) } self?.updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: textView.singlePeerAllowedIPs)
onDemandRow.selectedOptionIndex = activateOnDemandOptions.firstIndex(of: selectedActivateOnDemandOption)! }
} }
override func loadView() { override func loadView() {
populateTextFields() populateFields()
scrollView.documentView = textView scrollView.documentView = textView
@@ -149,16 +149,22 @@ class TunnelEditViewController: NSViewController {
discardButton.target = self discardButton.target = self
discardButton.action = #selector(handleDiscardAction) discardButton.action = #selector(handleDiscardAction)
excludePrivateIPsCheckbox.target = self
excludePrivateIPsCheckbox.action = #selector(excludePrivateIPsCheckboxToggled(sender:))
onDemandControlsRow.onDemandViewModel = onDemandViewModel
let margin: CGFloat = 20 let margin: CGFloat = 20
let internalSpacing: CGFloat = 10 let internalSpacing: CGFloat = 10
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandRow, scrollView]) let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandControlsRow, scrollView])
editorStackView.orientation = .vertical editorStackView.orientation = .vertical
editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal) editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal)
editorStackView.spacing = internalSpacing editorStackView.spacing = internalSpacing
let buttonRowStackView = NSStackView() let buttonRowStackView = NSStackView()
buttonRowStackView.setViews([discardButton, saveButton], in: .trailing) buttonRowStackView.setViews([discardButton, saveButton], in: .trailing)
buttonRowStackView.addView(excludePrivateIPsCheckbox, in: .leading)
buttonRowStackView.orientation = .horizontal buttonRowStackView.orientation = .horizontal
buttonRowStackView.spacing = internalSpacing buttonRowStackView.spacing = internalSpacing
@@ -177,19 +183,22 @@ class TunnelEditViewController: NSViewController {
self.view = containerView self.view = containerView
} }
func setUserInteractionEnabled(_ enabled: Bool) {
view.window?.ignoresMouseEvents = !enabled
nameRow.valueLabel.isEditable = enabled
textView.isEditable = enabled
onDemandControlsRow.onDemandSSIDsField.isEnabled = enabled
}
@objc func handleSaveAction() { @objc func handleSaveAction() {
let name = nameRow.value let name = nameRow.value
guard !name.isEmpty else { guard !name.isEmpty else {
ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self) ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self)
return return
} }
let onDemandSetting: ActivateOnDemandSetting
let onDemandOption = activateOnDemandOptions[onDemandRow.selectedOptionIndex] onDemandControlsRow.saveToViewModel()
if onDemandOption == .none { let onDemandOption = onDemandViewModel.toOnDemandOption()
onDemandSetting = ActivateOnDemandSetting.defaultSetting
} else {
onDemandSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: true, activateOnDemandOption: onDemandOption)
}
let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name) let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name)
guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else { guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else {
@@ -197,7 +206,7 @@ class TunnelEditViewController: NSViewController {
return return
} }
let tunnelConfiguration: TunnelConfiguration var tunnelConfiguration: TunnelConfiguration
do { do {
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value) tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value)
} catch let error as WireGuardAppError { } catch let error as WireGuardAppError {
@@ -207,9 +216,26 @@ class TunnelEditViewController: NSViewController {
fatalError() fatalError()
} }
if excludePrivateIPsCheckbox.state == .on, tunnelConfiguration.peers.count == 1, let dnsServersAddedToAllowedIPs = dnsServersAddedToAllowedIPs {
// Update the DNS servers in the AllowedIPs
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
let originalAllowedIPs = tunnelViewModel.peersData[0][.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
let dnsServersInAllowedIPs = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(dnsServersAddedToAllowedIPs.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
let dnsServersCurrent = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(tunnelViewModel.interfaceData[.dns].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
let modifiedAllowedIPs = originalAllowedIPs.filter { !dnsServersInAllowedIPs.contains($0) } + dnsServersCurrent
tunnelViewModel.peersData[0][.allowedIPs] = modifiedAllowedIPs.joined(separator: ", ")
let saveResult = tunnelViewModel.save()
if case .saved(let modifiedTunnelConfiguration) = saveResult {
tunnelConfiguration = modifiedTunnelConfiguration
}
}
setUserInteractionEnabled(false)
if let tunnel = tunnel { if let tunnel = tunnel {
// We're modifying an existing tunnel // We're modifying an existing tunnel
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] error in tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
self?.setUserInteractionEnabled(true)
if let error = error { if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
return return
@@ -219,7 +245,8 @@ class TunnelEditViewController: NSViewController {
} }
} else { } else {
// We're creating a new tunnel // We're creating a new tunnel
tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] result in self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
self?.setUserInteractionEnabled(true)
if let error = result.error { if let error = result.error {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
return return
@@ -235,4 +262,28 @@ class TunnelEditViewController: NSViewController {
delegate?.tunnelEditingCancelled() delegate?.tunnelEditingCancelled()
dismiss(self) dismiss(self)
} }
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
let shouldAllowExcludePrivateIPsControl: Bool
let excludePrivateIPsValue: Bool
if let singlePeerAllowedIPs = singlePeerAllowedIPs {
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: true, allowedIPs: Set<String>(singlePeerAllowedIPs))
} else {
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: false, allowedIPs: Set<String>())
}
excludePrivateIPsCheckbox.isHidden = !shouldAllowExcludePrivateIPsControl
excludePrivateIPsCheckbox.state = excludePrivateIPsValue ? .on : .off
}
@objc func excludePrivateIPsCheckboxToggled(sender: AnyObject?) {
guard let excludePrivateIPsCheckbox = sender as? NSButton else { return }
guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value) else { return }
let isOn = excludePrivateIPsCheckbox.state == .on
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
tunnelViewModel.peersData.first?.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: tunnelViewModel.interfaceData[.dns], oldDNSServers: dnsServersAddedToAllowedIPs)
if let modifiedConfig = tunnelViewModel.asWgQuickConfig() {
textView.setConfText(modifiedConfig)
dnsServersAddedToAllowedIPs = isOn ? tunnelViewModel.interfaceData[.dns] : nil
}
}
} }
@@ -4,7 +4,7 @@
import Cocoa import Cocoa
protocol TunnelsListTableViewControllerDelegate: class { protocol TunnelsListTableViewControllerDelegate: class {
func tunnelSelected(tunnel: TunnelContainer) func tunnelsSelected(tunnelIndices: [Int])
func tunnelsListEmpty() func tunnelsListEmpty()
} }
@@ -12,41 +12,57 @@ class TunnelsListTableViewController: NSViewController {
let tunnelsManager: TunnelsManager let tunnelsManager: TunnelsManager
weak var delegate: TunnelsListTableViewControllerDelegate? weak var delegate: TunnelsListTableViewControllerDelegate?
var isRemovingTunnels = false
let tableView: NSTableView = { let tableView: NSTableView = {
let tableView = NSTableView() let tableView = NSTableView()
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList"))) tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
tableView.headerView = nil tableView.headerView = nil
tableView.rowSizeStyle = .medium tableView.rowSizeStyle = .medium
tableView.allowsMultipleSelection = true
return tableView return tableView
}() }()
let buttonBar: NSSegmentedControl = { let addButton: NSPopUpButton = {
let addButtonImage = NSImage(named: NSImage.addTemplateName)! let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
let removeButtonImage = NSImage(named: NSImage.removeTemplateName)! imageItem.image = NSImage(named: NSImage.addTemplateName)!
let actionButtonImage = NSImage(named: NSImage.actionTemplateName)!
let buttonBar = NSSegmentedControl(images: [addButtonImage, removeButtonImage, actionButtonImage], let menu = NSMenu()
trackingMode: .momentary, target: nil, action: #selector(buttonBarClicked(sender:))) menu.addItem(imageItem)
buttonBar.segmentStyle = .smallSquare menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
buttonBar.segmentDistribution = .fit menu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
buttonBar.setShowsMenuIndicator(true, forSegment: 0) menu.autoenablesItems = false
buttonBar.setShowsMenuIndicator(false, forSegment: 1)
buttonBar.setShowsMenuIndicator(true, forSegment: 2) let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
return buttonBar button.menu = menu
button.bezelStyle = .smallSquare
(button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
return button
}() }()
let addMenu: NSMenu = { let removeButton: NSButton = {
let addMenu = NSMenu(title: "TunnelsListAdd") let image = NSImage(named: NSImage.removeTemplateName)!
addMenu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n") let button = NSButton(image: image, target: self, action: #selector(handleRemoveTunnelAction))
addMenu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "i") button.bezelStyle = .smallSquare
return addMenu button.imagePosition = .imageOnly
return button
}() }()
let actionMenu: NSMenu = { let actionButton: NSPopUpButton = {
let actionMenu = NSMenu(title: "TunnelsListAction") let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
actionMenu.addItem(withTitle: tr("macMenuExportLog"), action: #selector(handleExportLogAction), keyEquivalent: "") imageItem.image = NSImage(named: NSImage.actionTemplateName)!
actionMenu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
return actionMenu let menu = NSMenu()
menu.addItem(imageItem)
menu.addItem(withTitle: tr("macMenuViewLog"), action: #selector(handleViewLogAction), keyEquivalent: "")
menu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
menu.autoenablesItems = false
let button = NSPopUpButton(frame: NSRect.zero, pullsDown: true)
button.menu = menu
button.bezelStyle = .smallSquare
(button.cell as? NSPopUpButtonCell)?.arrowPosition = .arrowAtBottom
return button
}() }()
init(tunnelsManager: TunnelsManager) { init(tunnelsManager: TunnelsManager) {
@@ -62,10 +78,13 @@ class TunnelsListTableViewController: NSViewController {
tableView.dataSource = self tableView.dataSource = self
tableView.delegate = self tableView.delegate = self
tableView.doubleAction = #selector(listDoubleClicked(sender:))
let isSelected = selectTunnelInOperation() || selectTunnel(at: 0) let isSelected = selectTunnelInOperation() || selectTunnel(at: 0)
if !isSelected { if !isSelected {
delegate?.tunnelsListEmpty() delegate?.tunnelsListEmpty()
} }
tableView.allowsEmptySelection = false
let scrollView = NSScrollView() let scrollView = NSScrollView()
scrollView.hasVerticalScroller = true scrollView.hasVerticalScroller = true
@@ -76,6 +95,16 @@ class TunnelsListTableViewController: NSViewController {
clipView.documentView = tableView clipView.documentView = tableView
scrollView.contentView = clipView scrollView.contentView = clipView
let buttonBar = NSStackView(views: [addButton, removeButton, actionButton])
buttonBar.orientation = .horizontal
buttonBar.spacing = -1
NSLayoutConstraint.activate([
removeButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 26),
removeButton.topAnchor.constraint(equalTo: buttonBar.topAnchor),
removeButton.bottomAnchor.constraint(equalTo: buttonBar.bottomAnchor)
])
let fillerButton = FillerButton() let fillerButton = FillerButton()
let containerView = NSView() let containerView = NSView()
@@ -100,13 +129,12 @@ class TunnelsListTableViewController: NSViewController {
]) ])
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 120), containerView.widthAnchor.constraint(equalToConstant: 180),
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120) containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
]) ])
buttonBar.target = self addButton.menu?.items.forEach { $0.target = self }
addMenu.items.forEach { $0.target = self } actionButton.menu?.items.forEach { $0.target = self }
actionMenu.items.forEach { $0.target = self }
view = containerView view = containerView
} }
@@ -123,22 +151,6 @@ class TunnelsListTableViewController: NSViewController {
return false return false
} }
@objc func buttonBarClicked(sender: AnyObject?) {
guard let buttonBar = sender as? NSSegmentedControl else { return }
// We have to resort to explicitly showing the menu instead of using NSSegmentedControl.setMenu()
// because we have a mix of menu and non-menu segments.
// See: http://openradar.appspot.com/radar?id=61419
if buttonBar.selectedSegment == 0 {
let segmentBottomLeft = NSPoint(x: 0, y: buttonBar.bounds.height + 2)
addMenu.popUp(positioning: nil, at: segmentBottomLeft, in: buttonBar)
} else if buttonBar.selectedSegment == 1 {
handleRemoveTunnelAction()
} else if buttonBar.selectedSegment == 2 {
let segmentBottomLeft = NSPoint(x: buttonBar.bounds.width * 0.66, y: buttonBar.bounds.height + 2)
actionMenu.popUp(positioning: nil, at: segmentBottomLeft, in: buttonBar)
}
}
@objc func handleAddEmptyTunnelAction() { @objc func handleAddEmptyTunnelAction() {
let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil) let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil)
tunnelEditVC.delegate = self tunnelEditVC.delegate = self
@@ -151,59 +163,41 @@ class TunnelsListTableViewController: NSViewController {
@objc func handleRemoveTunnelAction() { @objc func handleRemoveTunnelAction() {
guard let window = view.window else { return } guard let window = view.window else { return }
let selectedTunnelIndex = tableView.selectedRow
guard selectedTunnelIndex >= 0 && selectedTunnelIndex < tunnelsManager.numberOfTunnels() else { return } let selectedTunnelIndices = tableView.selectedRowIndexes.sorted().filter { $0 >= 0 && $0 < tunnelsManager.numberOfTunnels() }
let selectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndex) guard !selectedTunnelIndices.isEmpty else { return }
let alert = NSAlert() var nextSelection = selectedTunnelIndices.last! + 1
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", selectedTunnel.name) if nextSelection >= tunnelsManager.numberOfTunnels() {
nextSelection = max(selectedTunnelIndices.first! - 1, 0)
}
let alert = DeleteTunnelsConfirmationAlert()
if selectedTunnelIndices.count == 1 {
let firstSelectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndices.first!)
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", firstSelectedTunnel.name)
} else {
alert.messageText = tr(format: "macDeleteMultipleTunnelsConfirmationAlertMessage (%d)", selectedTunnelIndices.count)
}
alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo") alert.informativeText = tr("macDeleteTunnelConfirmationAlertInfo")
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete")) alert.onDeleteClicked = { [weak self] completion in
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel")) guard let self = self else { return }
alert.beginSheetModal(for: window) { [weak self] response in self.selectTunnel(at: nextSelection)
guard response == .alertFirstButtonReturn else { return } let selectedTunnels = selectedTunnelIndices.map { self.tunnelsManager.tunnel(at: $0) }
self?.buttonBar.setEnabled(false, forSegment: 1) self.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
self?.tunnelsManager.remove(tunnel: selectedTunnel) { [weak self] error in
guard let self = self else { return } guard let self = self else { return }
defer { self.buttonBar.setEnabled(true, forSegment: 1) } defer { completion() }
if let error = error { if let error = error {
ErrorPresenter.showErrorAlert(error: error, from: self) ErrorPresenter.showErrorAlert(error: error, from: self)
return return
} }
} }
} }
alert.beginSheetModal(for: window)
} }
@objc func handleExportLogAction() { @objc func handleViewLogAction() {
guard let window = view.window else { return } let logVC = LogViewController()
let savePanel = NSSavePanel() self.presentAsSheet(logVC)
savePanel.prompt = tr("macSheetButtonExportLog")
savePanel.nameFieldLabel = tr("macNameFieldExportLog")
let dateFormatter = ISO8601DateFormatter()
dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
let timeStampString = dateFormatter.string(from: Date())
savePanel.nameFieldStringValue = "wireguard-log-\(timeStampString).txt"
guard let networkExtensionLogFilePath = FileManager.networkExtensionLogFileURL?.path else {
ErrorPresenter.showErrorAlert(title: tr("alertUnableToFindExtensionLogPathTitle"), message: tr("alertUnableToFindExtensionLogPathMessage"), from: self)
return
}
savePanel.beginSheetModal(for: window) { response in
guard response == .OK else { return }
guard let destinationURL = savePanel.url else { return }
DispatchQueue.global(qos: .userInitiated).async {
let isWritten = Logger.global?.writeLog(called: "APP", mergedWith: networkExtensionLogFilePath, called: "NET", to: destinationURL.path) ?? false
guard isWritten else {
DispatchQueue.main.async { [weak self] in
ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
}
return
}
}
}
} }
@objc func handleExportTunnelsAction() { @objc func handleExportTunnelsAction() {
@@ -232,9 +226,21 @@ class TunnelsListTableViewController: NSViewController {
} }
} }
@objc func listDoubleClicked(sender: AnyObject) {
let tunnelIndex = tableView.clickedRow
guard tunnelIndex >= 0 && tunnelIndex < tunnelsManager.numberOfTunnels() else { return }
let tunnel = tunnelsManager.tunnel(at: tunnelIndex)
if tunnel.status == .inactive {
tunnelsManager.startActivation(of: tunnel)
} else if tunnel.status == .active {
tunnelsManager.startDeactivation(of: tunnel)
}
}
@discardableResult @discardableResult
private func selectTunnel(at index: Int) -> Bool { private func selectTunnel(at index: Int) -> Bool {
if index < tunnelsManager.numberOfTunnels() { if index < tunnelsManager.numberOfTunnels() {
tableView.scrollRowToVisible(index)
tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false) tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
return true return true
} }
@@ -275,15 +281,10 @@ extension TunnelsListTableViewController {
} }
func tunnelRemoved(at index: Int) { func tunnelRemoved(at index: Int) {
let selectedTunnelIndex = tableView.selectedRow
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft) tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
if tunnelsManager.numberOfTunnels() == 0 { if tunnelsManager.numberOfTunnels() == 0 {
delegate?.tunnelsListEmpty() delegate?.tunnelsListEmpty()
} }
let tunnelIndex = min(selectedTunnelIndex, self.tunnelsManager.numberOfTunnels() - 1)
if tunnelIndex >= 0 {
self.selectTunnel(at: tunnelIndex)
}
} }
} }
@@ -301,9 +302,10 @@ extension TunnelsListTableViewController: NSTableViewDelegate {
} }
func tableViewSelectionDidChange(_ notification: Notification) { func tableViewSelectionDidChange(_ notification: Notification) {
guard tableView.selectedRow >= 0 else { return } let selectedTunnelIndices = tableView.selectedRowIndexes.sorted()
let selectedTunnel = tunnelsManager.tunnel(at: tableView.selectedRow) if !selectedTunnelIndices.isEmpty {
delegate?.tunnelSelected(tunnel: selectedTunnel) delegate?.tunnelsSelected(tunnelIndices: tableView.selectedRowIndexes.sorted())
}
} }
} }
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
import Cocoa
class UnusableTunnelDetailViewController: NSViewController {
var onButtonClicked: (() -> Void)?
let messageLabel: NSTextField = {
let text = tr("macUnusableTunnelMessage")
let boldFont = NSFont.boldSystemFont(ofSize: NSFont.systemFontSize)
let boldText = NSAttributedString(string: text, attributes: [.font: boldFont])
let label = NSTextField(labelWithAttributedString: boldText)
return label
}()
let infoLabel: NSTextField = {
let label = NSTextField(wrappingLabelWithString: tr("macUnusableTunnelInfo"))
return label
}()
let button: NSButton = {
let button = NSButton()
button.title = tr("macUnusableTunnelButtonTitleDeleteTunnel")
button.setButtonType(.momentaryPushIn)
button.bezelStyle = .rounded
return button
}()
init() {
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
button.target = self
button.action = #selector(buttonClicked)
let margin: CGFloat = 20
let internalSpacing: CGFloat = 20
let buttonSpacing: CGFloat = 30
let stackView = NSStackView(views: [messageLabel, infoLabel, button])
stackView.orientation = .vertical
stackView.edgeInsets = NSEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)
stackView.spacing = internalSpacing
stackView.setCustomSpacing(buttonSpacing, after: infoLabel)
let view = NSView()
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalToConstant: 360),
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view.widthAnchor.constraint(greaterThanOrEqualToConstant: 420),
view.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
])
self.view = view
}
@objc func buttonClicked() {
onButtonClicked?()
}
}
@@ -31,7 +31,7 @@ class ZipArchive {
let fileName = input.fileName let fileName = input.fileName
let contents = input.contents let contents = input.contents
zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION) zipOpenNewFileInZip(zipFile, fileName.cString(using: .utf8), nil, nil, 0, nil, 0, nil, Z_DEFLATED, Z_DEFAULT_COMPRESSION)
contents.withUnsafeBytes { (ptr: UnsafePointer<UInt8>) -> Void in contents.withUnsafeUInt8Bytes { ptr -> Void in
zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count)) zipWriteInFileInZip(zipFile, UnsafeRawPointer(ptr), UInt32(contents.count))
} }
zipCloseFileInZip(zipFile) zipCloseFileInZip(zipFile)
@@ -42,6 +42,7 @@ class ZipArchive {
static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] { static func unarchive(url: URL, requiredFileExtensions: [String]) throws -> [(fileBaseName: String, contents: Data)] {
var results = [(fileBaseName: String, contents: Data)]() var results = [(fileBaseName: String, contents: Data)]()
var requiredFileExtensionsLowercased = requiredFileExtensions.map { $0.lowercased() }
guard let zipFile = unzOpen64(url.path) else { guard let zipFile = unzOpen64(url.path) else {
throw ZipArchiveError.cantOpenInputZipFile throw ZipArchiveError.cantOpenInputZipFile
@@ -70,7 +71,7 @@ class ZipArchive {
let isDirectory = (lastChar == "/" || lastChar == "\\") let isDirectory = (lastChar == "/" || lastChar == "\\")
let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil) let fileURL = URL(fileURLWithFileSystemRepresentation: fileNameBuffer, isDirectory: isDirectory, relativeTo: nil)
if !isDirectory && requiredFileExtensions.contains(fileURL.pathExtension) { if !isDirectory && requiredFileExtensionsLowercased.contains(fileURL.pathExtension.lowercased()) {
var unzippedData = Data() var unzippedData = Data()
var bytesRead: Int32 = 0 var bytesRead: Int32 = 0
repeat { repeat {
@@ -37,7 +37,7 @@ class ZipImporter {
fatalError() fatalError()
} }
unarchivedFiles.sort { $0.fileBaseName < $1.fileBaseName } unarchivedFiles.sort { TunnelsManager.tunnelNameIsLessThan($0.fileBaseName, $1.fileBaseName) }
var configs: [TunnelConfiguration?] = Array(repeating: nil, count: unarchivedFiles.count) var configs: [TunnelConfiguration?] = Array(repeating: nil, count: unarchivedFiles.count)
for (index, file) in unarchivedFiles.enumerated() { for (index, file) in unarchivedFiles.enumerated() {
if index > 0 && file == unarchivedFiles[index - 1] { if index > 0 && file == unarchivedFiles[index - 1] {
@@ -117,6 +117,8 @@ extension Endpoint {
hostname = "\(address)" hostname = "\(address)"
case .ipv6(let address): case .ipv6(let address):
hostname = "\(address)" hostname = "\(address)"
@unknown default:
fatalError()
} }
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0)) var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
@@ -122,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
private func configureLogger() { private func configureLogger() {
Logger.configureGlobal(withFilePath: FileManager.networkExtensionLogFileURL?.path) Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
wgSetLogger { level, msgC in wgSetLogger { level, msgC in
guard let msgC = msgC else { return } guard let msgC = msgC else { return }
let logType: OSLogType let logType: OSLogType
Submodule wireguard-go deleted from c4b43e35a7
+23 -39
View File
@@ -26,61 +26,48 @@ export CGO_ENABLED := 1
build: $(DESTDIR)/libwg-go.a build: $(DESTDIR)/libwg-go.a
version-header: $(DESTDIR)/wireguard-go-version.h version-header: $(DESTDIR)/wireguard-go-version.h
GOBUILDARCH := $(GOARCH_$(shell uname -m)) GOBUILDVERSION_NEEDED := go version go1.12.1 darwin/amd64
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]') GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
GOBUILDVERSION := 1.11.5 export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
GOBUILDTARBALL := go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
GOBUILDTARBALLURL := https://dl.google.com/go/$(GOBUILDTARBALL)
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
export GOROOT := $(BUILDDIR)/goroot export GOROOT := $(BUILDDIR)/goroot
export GOPATH := $(BUILDDIR)/gopath export GOPATH := $(BUILDDIR)/gopath
export PATH := $(GOROOT)/bin:$(PATH) export PATH := $(GOPATH)/bin:$(PATH)
GOBUILDVERSION_CURRENT := $(shell $(GOROOT)/bin/go version 2>/dev/null) GOBUILDVERSION_FAKE := $(shell $(GOROOT)/bin/go version 2>/dev/null)
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT)) ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_CURRENT))
$(shell rm -f $(GOROOT)/bin/go) $(error This requires $(GOBUILDVERSION_NEEDED))
endif
ifneq ($(GOBUILDVERSION_NEEDED),$(GOBUILDVERSION_FAKE))
$(shell rm -f $(GOROOT)/.prepared)
endif endif
.cache/$(GOBUILDTARBALL):
mkdir -p $(dir $@)
curl -o $@ $(GOBUILDTARBALLURL) || { rm -f $@; exit 1; }
$(GOROOT)/bin/go: .cache/$(GOBUILDTARBALL) $(GOROOT)/.prepared:
rm -rf "$(GOROOT)" [ -n "$(REAL_GOROOT)" ]
mkdir -p "$(GOROOT)" mkdir -p "$(GOROOT)"
tar -C "$(GOROOT)" --strip-components=1 -xzf - < .cache/$(GOBUILDTARBALL) || { rm -rf "$(GOROOT)"; exit 1; } rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/"
patch -p1 -f -N -r- -d "$(GOROOT)" < goruntime-boottime-over-monotonic.diff || { rm -rf "$(GOROOT)"; exit 1; } cat goruntime-*.diff | patch -p1 -f -N -r- -d "$(GOROOT)"
rm -rf "$(GOPATH)/pkg/mod"
go get -d -tags ios; chmod -fR +w "$(GOPATH)/pkg/mod"
for sys in "$(GOPATH)/pkg/mod/golang.org/x/sys@"*; do cat sys-unix-*.diff | patch -p1 -f -N -r- -d "$$sys"; done
touch "$@"
$(shell test "$$(cat "$(BUILDDIR)/.gobuildversion" 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.a") $(shell test "$$(cat "$(BUILDDIR)/.gobuildversion" 2>/dev/null)" = "$(GOBUILDVERSION_CURRENT)" || rm -f "$(DESTDIR)/libwg-go.a")
define copy-src-to-build
$(subst $(1),$(BUILDDIR)/,$(2)): $(2)
@mkdir -vp "$$(dir $$@)"
@cp -vp "$$<" "$$@"
$(BUILDDIR)/.prepared: $(subst $(1),$(BUILDDIR)/,$(2))
endef
$(foreach FILE,$(UPSTREAM_FILES),$(eval $(call copy-src-to-build,../wireguard-go/,$(FILE))))
$(foreach FILE,$(DOWNSTREAM_FILES),$(eval $(call copy-src-to-build,src/,$(FILE))))
$(BUILDDIR)/.prepared: $(GOROOT)/bin/go
cd "$(BUILDDIR)" || exit $$?; $(foreach ARCH,$(ARCHS),CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" GOARCH="$(GOARCH_$(ARCH))" go get -tags ios || { ret=$$?; chmod -fR +w "$(GOPATH)/pkg/mod"; rm -rf "$(GOPATH)/pkg/mod"; exit $$ret; };)
chmod -fR +w "$(GOPATH)/pkg/mod"
touch "$@"
define libwg-go-a define libwg-go-a
$(BUILDDIR)/libwg-go-$(1).a: $(BUILDDIR)/.prepared $(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared
cd "$(BUILDDIR)" || exit $$$$?; \
CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \ CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \ CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
GOARCH="$(GOARCH_$(1))" \ GOARCH="$(GOARCH_$(1))" \
go build -tags ios -ldflags=-w -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive && go version > "$(BUILDDIR)/.gobuildversion"; \ go build -tags ios -ldflags=-w -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive && go version > "$(BUILDDIR)/.gobuildversion"; \
chmod -fR +w "$(GOPATH)/pkg/mod"; \
ret=$$$$?; \ ret=$$$$?; \
rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \ rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \
exit $$$$ret exit $$$$ret
endef endef
$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH)))) $(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH))))
$(DESTDIR)/wireguard-go-version.h: ../wireguard-go/version.go $(DESTDIR)/wireguard-go-version.h: go.mod $(GOROOT)/.prepared
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$^" > "$@" wggo="$(GOPATH)/pkg/mod/$$(sed -n 's/.*\(golang\.zx2c4\.com\/wireguard\) \(.*\)$$/\1@\2/p' go.mod)"; \
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$$wggo/device/version.go" > "$@"
$(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a) $(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
@mkdir -vp "$(DESTDIR)" @mkdir -vp "$(DESTDIR)"
@@ -89,9 +76,6 @@ $(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
clean: clean:
rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h" rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h"
distclean: clean
rm -rf .cache
install: build install: build
.PHONY: distclean clean build version-header install .PHONY: clean build version-header install
@@ -1,4 +1,4 @@
/* SPDX-License-Identifier: GPL-2.0 /* SPDX-License-Identifier: MIT
* *
* Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. * Copyright (C) 2018-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/ */
@@ -17,8 +17,9 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"errors" "errors"
"git.zx2c4.com/wireguard-go/tun"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun"
"log" "log"
"math" "math"
"os" "os"
@@ -45,12 +46,16 @@ func (l *CLogger) Write(p []byte) (int, error) {
return len(p), nil return len(p), nil
} }
var tunnelHandles map[int32]*Device type tunnelHandle struct {
*device.Device
*device.Logger
}
var tunnelHandles = make(map[int32]tunnelHandle)
func init() { func init() {
versionString = C.CString(WireGuardGoVersion) versionString = C.CString(device.WireGuardGoVersion)
roamingDisabled = true device.RoamingDisabled = true
tunnelHandles = make(map[int32]*Device)
signals := make(chan os.Signal) signals := make(chan os.Signal)
signal.Notify(signals, unix.SIGUSR2) signal.Notify(signals, unix.SIGUSR2)
go func() { go func() {
@@ -61,7 +66,7 @@ func init() {
n := runtime.Stack(buf, true) n := runtime.Stack(buf, true)
buf[n] = 0 buf[n] = 0
if uintptr(loggerFunc) != 0 { if uintptr(loggerFunc) != 0 {
C.callLogger(loggerFunc, 0, (*_Ctype_char)(unsafe.Pointer(&buf[0]))) C.callLogger(loggerFunc, 0, (*C.char)(unsafe.Pointer(&buf[0])))
} }
} }
} }
@@ -70,7 +75,7 @@ func init() {
//export wgEnableRoaming //export wgEnableRoaming
func wgEnableRoaming(enabled bool) { func wgEnableRoaming(enabled bool) {
roamingDisabled = !enabled device.RoamingDisabled = !enabled
} }
//export wgSetLogger //export wgSetLogger
@@ -80,21 +85,26 @@ func wgSetLogger(loggerFn uintptr) {
//export wgTurnOn //export wgTurnOn
func wgTurnOn(settings string, tunFd int32) int32 { func wgTurnOn(settings string, tunFd int32) int32 {
logger := &Logger{ logger := &device.Logger{
Debug: log.New(&CLogger{level: 0}, "", 0), Debug: log.New(&CLogger{level: 0}, "", 0),
Info: log.New(&CLogger{level: 1}, "", 0), Info: log.New(&CLogger{level: 1}, "", 0),
Error: log.New(&CLogger{level: 2}, "", 0), Error: log.New(&CLogger{level: 2}, "", 0),
} }
tun, _, err := tun.CreateTUNFromFD(int(tunFd)) err := unix.SetNonblock(int(tunFd), true)
if err != nil {
logger.Error.Println(err)
return -1
}
tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(tunFd), "/dev/tun"), 0)
if err != nil { if err != nil {
logger.Error.Println(err) logger.Error.Println(err)
return -1 return -1
} }
logger.Info.Println("Attaching to interface") logger.Info.Println("Attaching to interface")
device := NewDevice(tun, logger) device := device.NewDevice(tun, logger)
setError := ipcSetOperation(device, bufio.NewReader(strings.NewReader(settings))) setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
if setError != nil { if setError != nil {
logger.Error.Println(setError) logger.Error.Println(setError)
return -1 return -1
@@ -112,7 +122,7 @@ func wgTurnOn(settings string, tunFd int32) int32 {
if i == math.MaxInt32 { if i == math.MaxInt32 {
return -1 return -1
} }
tunnelHandles[i] = device tunnelHandles[i] = tunnelHandle{device, logger}
return i return i
} }
@@ -132,10 +142,10 @@ func wgSetConfig(tunnelHandle int32, settings string) int64 {
if !ok { if !ok {
return 0 return 0
} }
err := ipcSetOperation(device, bufio.NewReader(strings.NewReader(settings))) err := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
if err != nil { if err != nil {
device.log.Error.Println(err) device.Error.Println(err)
return err.int64 return err.ErrorCode()
} }
return 0 return 0
} }
@@ -148,7 +158,7 @@ func wgGetConfig(tunnelHandle int32) *C.char {
} }
settings := new(bytes.Buffer) settings := new(bytes.Buffer)
writer := bufio.NewWriter(settings) writer := bufio.NewWriter(settings)
err := ipcGetOperation(device, writer) err := device.IpcGetOperation(writer)
if err != nil { if err != nil {
return nil return nil
} }
@@ -158,46 +168,18 @@ func wgGetConfig(tunnelHandle int32) *C.char {
//export wgBindInterfaceScope //export wgBindInterfaceScope
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) { func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
var operr error
device, ok := tunnelHandles[tunnelHandle] device, ok := tunnelHandles[tunnelHandle]
if !ok { if !ok {
return return
} }
device.log.Info.Printf("Binding sockets to interface %d\n", ifscope) device.Info.Printf("Binding sockets to interface %d\n", ifscope)
bind := device.net.bind.(*NativeBind) err := device.BindSocketToInterface4(uint32(ifscope))
for bind.ipv4 != nil { if err != nil {
fd, err := bind.ipv4.SyscallConn() device.Error.Printf("Unable to bind v4 socket to interface:", err)
if err != nil {
device.log.Error.Printf("Unable to bind v4 socket to interface:", err)
break
}
err = fd.Control(func(fd uintptr) {
operr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, int(ifscope))
})
if err == nil {
err = operr
}
if err != nil {
device.log.Error.Printf("Unable to bind v4 socket to interface:", err)
}
break
} }
for bind.ipv6 != nil { err = device.BindSocketToInterface6(uint32(ifscope))
fd, err := bind.ipv6.SyscallConn() if err != nil {
if err != nil { device.Error.Printf("Unable to bind v6 socket to interface:", err)
device.log.Error.Printf("Unable to bind v6 socket to interface:", err)
break
}
err = fd.Control(func(fd uintptr) {
operr = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, int(ifscope))
})
if err == nil {
err = operr
}
if err != nil {
device.log.Error.Printf("Unable to bind v6 socket to interface:", err)
}
break
} }
} }
+8
View File
@@ -0,0 +1,8 @@
module golang.zx2c4.com/wireguard/ios
go 1.12
require (
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265
)
+13
View File
@@ -0,0 +1,13 @@
github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53 h1:kcXqo9vE6fsZY5X5Rd7R1l7fTgnWaDCVmln65REefiE=
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265 h1:ujM5BaP4MD/2MJZ1n7pmw6IIsyyS7SyPl0fDefnx/2o=
golang.zx2c4.com/wireguard v0.0.0-20190409083948-18fa27047265/go.mod h1:u0Cl3X+pyWdXaax3S583DQrnGDuTASO0QdlKFrs8r/8=
@@ -1,55 +1,27 @@
diff -r -u go/src/runtime/sys_darwin_386.s go-modified/src/runtime/sys_darwin_386.s From 74523c5a12d37fa792e77a252bcc569484c3d41a Mon Sep 17 00:00:00 2001
--- go/src/runtime/sys_darwin_386.s 2018-10-01 23:02:54.000000000 +0200 From: "Jason A. Donenfeld" <Jason@zx2c4.com>
+++ go-modified/src/runtime/sys_darwin_386.s 2018-11-01 23:18:04.383055355 +0100 Date: Wed, 27 Feb 2019 05:33:01 +0100
@@ -184,7 +184,7 @@ Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin
PUSHL BP
MOVL SP, BP This makes timers account for having expired while a computer was
SUBL $8+(machTimebaseInfo__size+15)/16*16, SP asleep, which is quite common on mobile devices. Note that
- CALL libc_mach_absolute_time(SB) continuous_time absolute_time, except that it takes into account
+ CALL libc_mach_continuous_time(SB) time spent in suspend.
MOVL 16+(machTimebaseInfo__size+15)/16*16(SP), CX
MOVL AX, 0(CX) Fixes #24595
MOVL DX, 4(CX) ---
diff -r -u go/src/runtime/sys_darwin_amd64.s go-modified/src/runtime/sys_darwin_amd64.s src/runtime/sys_darwin.go | 2 +-
--- go/src/runtime/sys_darwin_amd64.s 2018-10-01 23:02:54.000000000 +0200 src/runtime/sys_darwin_386.s | 2 +-
+++ go-modified/src/runtime/sys_darwin_amd64.s 2018-11-01 23:18:04.382055360 +0100 src/runtime/sys_darwin_amd64.s | 2 +-
@@ -85,7 +85,7 @@ src/runtime/sys_darwin_arm.s | 2 +-
PUSHQ BP src/runtime/sys_darwin_arm64.s | 2 +-
MOVQ SP, BP 5 files changed, 5 insertions(+), 5 deletions(-)
MOVQ DI, BX
- CALL libc_mach_absolute_time(SB) diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
+ CALL libc_mach_continuous_time(SB) index f34ac88352..416fcb673f 100644
MOVQ AX, 0(BX) --- a/src/runtime/sys_darwin.go
MOVL timebase<>+machTimebaseInfo_numer(SB), SI +++ b/src/runtime/sys_darwin.go
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read @@ -403,7 +403,7 @@ func closeonexec(fd int32) {
diff -r -u go/src/runtime/sys_darwin_arm64.s go-modified/src/runtime/sys_darwin_arm64.s
--- go/src/runtime/sys_darwin_arm64.s 2018-10-01 23:02:54.000000000 +0200
+++ go-modified/src/runtime/sys_darwin_arm64.s 2018-11-01 23:18:04.380055369 +0100
@@ -110,7 +110,7 @@
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
MOVD R0, R19
- BL libc_mach_absolute_time(SB)
+ BL libc_mach_continuous_time(SB)
MOVD R0, 0(R19)
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
diff -r -u go/src/runtime/sys_darwin_arm.s go-modified/src/runtime/sys_darwin_arm.s
--- go/src/runtime/sys_darwin_arm.s 2018-10-01 23:02:54.000000000 +0200
+++ go-modified/src/runtime/sys_darwin_arm.s 2018-11-01 23:18:04.381055364 +0100
@@ -118,7 +118,7 @@
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
MOVW R0, R8
- BL libc_mach_absolute_time(SB)
+ BL libc_mach_continuous_time(SB)
MOVW R0, 0(R8)
MOVW R1, 4(R8)
MOVW timebase<>+machTimebaseInfo_numer(SB), R6
diff -r -u go/src/runtime/sys_darwin.go go-modified/src/runtime/sys_darwin.go
--- go/src/runtime/sys_darwin.go 2018-10-01 23:02:54.000000000 +0200
+++ go-modified/src/runtime/sys_darwin.go 2018-11-01 23:18:04.384055350 +0100
@@ -348,7 +348,7 @@
//go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib"
@@ -58,3 +30,58 @@ diff -r -u go/src/runtime/sys_darwin.go go-modified/src/runtime/sys_darwin.go
//go:cgo_import_dynamic libc_gettimeofday gettimeofday "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_gettimeofday gettimeofday "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib"
diff --git a/src/runtime/sys_darwin_386.s b/src/runtime/sys_darwin_386.s
index 1bc1a63c28..34a3561350 100644
--- a/src/runtime/sys_darwin_386.s
+++ b/src/runtime/sys_darwin_386.s
@@ -184,7 +184,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
PUSHL BP
MOVL SP, BP
SUBL $8+(machTimebaseInfo__size+15)/16*16, SP
- CALL libc_mach_absolute_time(SB)
+ CALL libc_mach_continuous_time(SB)
MOVL 16+(machTimebaseInfo__size+15)/16*16(SP), CX
MOVL AX, 0(CX)
MOVL DX, 4(CX)
diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s
index f99cb00ab8..8b99316983 100644
--- a/src/runtime/sys_darwin_amd64.s
+++ b/src/runtime/sys_darwin_amd64.s
@@ -86,7 +86,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
PUSHQ BP
MOVQ SP, BP
MOVQ DI, BX
- CALL libc_mach_absolute_time(SB)
+ CALL libc_mach_continuous_time(SB)
MOVQ AX, 0(BX)
MOVL timebase<>+machTimebaseInfo_numer(SB), SI
MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read
diff --git a/src/runtime/sys_darwin_arm.s b/src/runtime/sys_darwin_arm.s
index 54c7afbf34..a4f06fdb85 100644
--- a/src/runtime/sys_darwin_arm.s
+++ b/src/runtime/sys_darwin_arm.s
@@ -118,7 +118,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0
MOVW R0, R8
- BL libc_mach_absolute_time(SB)
+ BL libc_mach_continuous_time(SB)
MOVW R0, 0(R8)
MOVW R1, 4(R8)
MOVW timebase<>+machTimebaseInfo_numer(SB), R6
diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s
index 29951d8ad7..cdaf0a630e 100644
--- a/src/runtime/sys_darwin_arm64.s
+++ b/src/runtime/sys_darwin_arm64.s
@@ -113,7 +113,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
MOVD R0, R19
- BL libc_mach_absolute_time(SB)
+ BL libc_mach_continuous_time(SB)
MOVD R0, 0(R19)
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
--
2.20.1
@@ -0,0 +1,279 @@
From bc77ad792117829909eeeca3aa492d6f292d004d Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Tue, 19 Mar 2019 13:55:44 -0600
Subject: [PATCH] syscall: do not link against ___getdirentries64
---
.../x/sys/unix/syscall_darwin_386.go | 5 ++++-
.../x/sys/unix/syscall_darwin_amd64.go | 5 ++++-
.../x/sys/unix/zsyscall_darwin_386.go | 22 -------------------
.../x/sys/unix/zsyscall_darwin_386.s | 2 --
.../x/sys/unix/zsyscall_darwin_amd64.go | 22 -------------------
.../x/sys/unix/zsyscall_darwin_amd64.s | 2 --
src/syscall/syscall_darwin_386.go | 5 ++++-
src/syscall/syscall_darwin_amd64.go | 5 ++++-
src/syscall/zsyscall_darwin_386.go | 20 -----------------
src/syscall/zsyscall_darwin_386.s | 2 --
src/syscall/zsyscall_darwin_amd64.go | 20 -----------------
src/syscall/zsyscall_darwin_amd64.s | 2 --
12 files changed, 16 insertions(+), 96 deletions(-)
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
index 489726fa9b..900dd7c91f 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_386.go
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
index 914b89bde5..95e245fff7 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
index 23346dc68f..db4f1eaf1c 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
n = int(r0)
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
index 37b85b4f61..6165f70e33 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_getfsstat64(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
index b50178d679..dea5dee75e 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
n = int(r0)
diff --git a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
index da9b900a8c..f1e2d7e9a4 100644
--- a/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
+++ b/src/cmd/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_getfsstat64(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
diff --git a/src/syscall/syscall_darwin_386.go b/src/syscall/syscall_darwin_386.go
index 045ebc726b..826d76f569 100644
--- a/src/syscall/syscall_darwin_386.go
+++ b/src/syscall/syscall_darwin_386.go
@@ -16,7 +16,6 @@ func setTimeval(sec, usec int64) Timeval {
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
//sysnb Gettimeofday(tp *Timeval) (err error)
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
@@ -63,3 +62,7 @@ func libc_sendfile_trampoline()
func syscall9(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) // sic
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/src/syscall/syscall_darwin_amd64.go b/src/syscall/syscall_darwin_amd64.go
index 7b6493bf9f..3868790049 100644
--- a/src/syscall/syscall_darwin_amd64.go
+++ b/src/syscall/syscall_darwin_amd64.go
@@ -16,7 +16,6 @@ func setTimeval(sec, usec int64) Timeval {
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_fstat64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_fstatfs64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS___getdirentries64
//sysnb Gettimeofday(tp *Timeval) (err error)
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_lstat64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_stat64
@@ -63,3 +62,7 @@ func libc_sendfile_trampoline()
func syscallX(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno)
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/src/syscall/zsyscall_darwin_386.go b/src/syscall/zsyscall_darwin_386.go
index 758ff7b129..a666a1f65e 100644
--- a/src/syscall/zsyscall_darwin_386.go
+++ b/src/syscall/zsyscall_darwin_386.go
@@ -1845,27 +1845,7 @@ func libc_fstatfs64_trampoline()
//go:linkname libc_fstatfs64 libc_fstatfs64
//go:cgo_import_dynamic libc_fstatfs64 fstatfs64 "/usr/lib/libSystem.B.dylib"
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Gettimeofday(tp *Timeval) (err error) {
diff --git a/src/syscall/zsyscall_darwin_386.s b/src/syscall/zsyscall_darwin_386.s
index a688192501..a5af9b64b9 100644
--- a/src/syscall/zsyscall_darwin_386.s
+++ b/src/syscall/zsyscall_darwin_386.s
@@ -235,8 +235,6 @@ TEXT ·libc_fstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0
JMP libc_gettimeofday(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
diff --git a/src/syscall/zsyscall_darwin_amd64.go b/src/syscall/zsyscall_darwin_amd64.go
index afc3d72d8d..bb87aef1f9 100644
--- a/src/syscall/zsyscall_darwin_amd64.go
+++ b/src/syscall/zsyscall_darwin_amd64.go
@@ -1845,27 +1845,7 @@ func libc_fstatfs64_trampoline()
//go:linkname libc_fstatfs64 libc_fstatfs64
//go:cgo_import_dynamic libc_fstatfs64 fstatfs64 "/usr/lib/libSystem.B.dylib"
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func Gettimeofday(tp *Timeval) (err error) {
diff --git a/src/syscall/zsyscall_darwin_amd64.s b/src/syscall/zsyscall_darwin_amd64.s
index 21ab38e3ee..409320dea5 100644
--- a/src/syscall/zsyscall_darwin_amd64.s
+++ b/src/syscall/zsyscall_darwin_amd64.s
@@ -235,8 +235,6 @@ TEXT ·libc_fstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0
JMP libc_gettimeofday(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
--
2.21.0
-16
View File
@@ -1,16 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2017-2019 WireGuard LLC. All Rights Reserved.
*/
package main
/* Fit within memory limits for iOS */
const (
QueueOutboundSize = 1024
QueueInboundSize = 1024
QueueHandshakeSize = 1024
MaxSegmentSize = 1700
PreallocatedBuffersPerPool = 1024
)
-52
View File
@@ -1,52 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0
*
* Copyright (C) 2017-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
package tun
import (
"git.zx2c4.com/wireguard-go/rwcancel"
"golang.org/x/sys/unix"
"net"
"os"
)
func CreateTUNFromFD(tunFd int) (TUNDevice, string, error) {
file := os.NewFile(uintptr(tunFd), "/dev/tun")
tun := &nativeTun{
tunFile: file,
fd: file.Fd(),
events: make(chan TUNEvent, 5),
errors: make(chan error, 5),
}
var err error
tun.rwcancel, err = rwcancel.NewRWCancel(tunFd)
if err != nil {
return nil, "", err
}
name, err := tun.Name()
if err != nil {
tun.rwcancel.Cancel()
return nil, "", err
}
tunIfindex, err := func() (int, error) {
iface, err := net.InterfaceByName(name)
if err != nil {
return -1, err
}
return iface.Index, nil
}()
if err != nil {
tun.tunFile.Close()
return nil, "", err
}
tun.routeSocket, err = unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)
if err != nil {
tun.tunFile.Close()
return nil, "", err
}
go tun.routineRouteListener(tunIfindex)
return tun, name, nil
}
@@ -0,0 +1,143 @@
From 3efe4df9b66d4af86363e37768ea469abb8f7bdc Mon Sep 17 00:00:00 2001
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
Date: Tue, 19 Mar 2019 14:01:21 -0600
Subject: [PATCH] unix: do not link against ___getdirentries64
---
unix/syscall_darwin_386.go | 5 ++++-
unix/syscall_darwin_amd64.go | 5 ++++-
unix/zsyscall_darwin_386.go | 22 ----------------------
unix/zsyscall_darwin_386.s | 2 --
unix/zsyscall_darwin_amd64.go | 22 ----------------------
unix/zsyscall_darwin_amd64.s | 2 --
6 files changed, 8 insertions(+), 50 deletions(-)
diff --git a/unix/syscall_darwin_386.go b/unix/syscall_darwin_386.go
index 489726f..900dd7c 100644
--- a/unix/syscall_darwin_386.go
+++ b/unix/syscall_darwin_386.go
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/unix/syscall_darwin_amd64.go b/unix/syscall_darwin_amd64.go
index 914b89b..95e245f 100644
--- a/unix/syscall_darwin_amd64.go
+++ b/unix/syscall_darwin_amd64.go
@@ -56,8 +56,11 @@ const SYS___SYSCTL = SYS_SYSCTL
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
-//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
//sys getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) = SYS_GETFSSTAT64
//sys Lstat(path string, stat *Stat_t) (err error) = SYS_LSTAT64
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
//sys Statfs(path string, stat *Statfs_t) (err error) = SYS_STATFS64
+
+func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
+ return 0, ENOSYS
+}
diff --git a/unix/zsyscall_darwin_386.go b/unix/zsyscall_darwin_386.go
index 23346dc..db4f1ea 100644
--- a/unix/zsyscall_darwin_386.go
+++ b/unix/zsyscall_darwin_386.go
@@ -2408,28 +2408,6 @@ func libc_fstatfs64_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
n = int(r0)
diff --git a/unix/zsyscall_darwin_386.s b/unix/zsyscall_darwin_386.s
index 37b85b4..6165f70 100644
--- a/unix/zsyscall_darwin_386.s
+++ b/unix/zsyscall_darwin_386.s
@@ -272,8 +272,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_getfsstat64(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
diff --git a/unix/zsyscall_darwin_amd64.go b/unix/zsyscall_darwin_amd64.go
index c142e33..126f993 100644
--- a/unix/zsyscall_darwin_amd64.go
+++ b/unix/zsyscall_darwin_amd64.go
@@ -2423,28 +2423,6 @@ func libc_fstatfs64_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
- var _p0 unsafe.Pointer
- if len(buf) > 0 {
- _p0 = unsafe.Pointer(&buf[0])
- } else {
- _p0 = unsafe.Pointer(&_zero)
- }
- r0, _, e1 := syscall_syscall6(funcPC(libc___getdirentries64_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
- n = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-func libc___getdirentries64_trampoline()
-
-//go:linkname libc___getdirentries64 libc___getdirentries64
-//go:cgo_import_dynamic libc___getdirentries64 __getdirentries64 "/usr/lib/libSystem.B.dylib"
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func getfsstat(buf unsafe.Pointer, size uintptr, flags int) (n int, err error) {
r0, _, e1 := syscall_syscall(funcPC(libc_getfsstat64_trampoline), uintptr(buf), uintptr(size), uintptr(flags))
n = int(r0)
diff --git a/unix/zsyscall_darwin_amd64.s b/unix/zsyscall_darwin_amd64.s
index 1a39151..a19c4f5 100644
--- a/unix/zsyscall_darwin_amd64.s
+++ b/unix/zsyscall_darwin_amd64.s
@@ -274,8 +274,6 @@ TEXT ·libc_fstatat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatat64(SB)
TEXT ·libc_fstatfs64_trampoline(SB),NOSPLIT,$0-0
JMP libc_fstatfs64(SB)
-TEXT ·libc___getdirentries64_trampoline(SB),NOSPLIT,$0-0
- JMP libc___getdirentries64(SB)
TEXT ·libc_getfsstat64_trampoline(SB),NOSPLIT,$0-0
JMP libc_getfsstat64(SB)
TEXT ·libc_lstat64_trampoline(SB),NOSPLIT,$0-0
--
2.21.0