Compare commits
126 Commits
0.0.201902
...
0.0.201904
Author | SHA1 | Date | |
---|---|---|---|
571349bb3d | |||
98ebd55208 | |||
83d0d34411 | |||
8c7c4b6792 | |||
5b34f49166 | |||
cef3957875 | |||
d9e88c51bd | |||
db876647d6 | |||
90eb45e287 | |||
9f3d86723a | |||
11063d0f88 | |||
557ee4390b | |||
9ce42d152d | |||
4c1b2e1258 | |||
3bd611aa7c | |||
adbe0b065e | |||
6015661beb | |||
6e6a6b88fb | |||
9690365dd4 | |||
0299c3929e | |||
0043233872 | |||
a74dd24578 | |||
dd9506ecee | |||
0c2eb003a0 | |||
f83f159f97 | |||
6175de0438 | |||
bd61be52e6 | |||
909f88be70 | |||
b7c3bd0d8c | |||
4237ab4a6f | |||
0fcaf6debb | |||
dbd5ea1ff0 | |||
9afe230c10 | |||
4bdfbb518e | |||
fbe101eabb | |||
cda3170970 | |||
a5e7c3906b | |||
b21fdfed67 | |||
5f8843e247 | |||
7a3f65fd2f | |||
dca0fb29f6 | |||
af9c800af8 | |||
a9b925c69b | |||
f93f9d62f4 | |||
fc163fc9ff | |||
adc5a7cac2 | |||
0dd22ca45a | |||
bca9fead5e | |||
51822f722a | |||
121d223229 | |||
439fb6bbac | |||
9c8231dcf7 | |||
0440c4a33a | |||
01be43aa7a | |||
e29c6900e5 | |||
a334c25aff | |||
f56b2ad968 | |||
503ac6c8a2 | |||
5f30e021ef | |||
d748382fce | |||
63299a2752 | |||
b7f8f74b56 | |||
8e5a9215de | |||
64925cab89 | |||
062b4d4b16 | |||
d9bdc61fb9 | |||
0ae8d25134 | |||
574d8433b3 | |||
bd339e2876 | |||
fff75adfe1 | |||
01604dd8d1 | |||
bdeb89a9e5 | |||
36dc252512 | |||
5941bf181c | |||
7a450089c0 | |||
5d757982ba | |||
3767a12983 | |||
9795b0609a | |||
0f98312d15 | |||
f81275812c | |||
b2b5e0e379 | |||
a6f80135ef | |||
e23c221aff | |||
50bc994762 | |||
3e05da4486 | |||
c750f28c67 | |||
f6c70500a7 | |||
663923864c | |||
9250780ffc | |||
9bc17034dd | |||
db6f0729c6 | |||
4503c11b0c | |||
fe4f8b666d | |||
90c0f7e92e | |||
3afcee04be | |||
202e7a4890 | |||
d7b16ffb1f | |||
b1dabf5a00 | |||
a389bd93cb | |||
b2a2110d8c | |||
5ed28907ec | |||
ab6d714070 | |||
d3df8734c2 | |||
ea5996abe0 | |||
ce405f856e | |||
98a967acc8 | |||
b01d09dfb5 | |||
7a580e8941 | |||
39fb52a2e3 | |||
69a064d954 | |||
eb684ef711 | |||
b0eff424f9 | |||
c195760b15 | |||
ba3f0db92c | |||
5031a7db4c | |||
a355232e09 | |||
6f7214ff38 | |||
4c88f477a2 | |||
2fb9d6af71 | |||
38ac66071c | |||
910fdfc321 | |||
c38a88988b | |||
cf51344444 | |||
fda36b571d | |||
1be3224d7a | |||
ca088e9ddc |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
||||
[submodule "wireguard-go"]
|
||||
path = wireguard-go
|
||||
url = https://git.zx2c4.com/wireguard-go
|
@ -14,14 +14,12 @@ A .mobileconfig file is a plist file in XML format. The top-level XML item is a
|
||||
- `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.
|
||||
|
||||
@ -66,7 +64,6 @@ keys:
|
||||
- `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`
|
||||
@ -79,13 +76,11 @@ keys:
|
||||
- `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).
|
||||
|
||||
|
21
README.md
21
README.md
@ -1,21 +1,14 @@
|
||||
# [WireGuard](https://www.wireguard.com/) for iOS
|
||||
# [WireGuard](https://www.wireguard.com/) for iOS and macOS
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
- Init and update submodule:
|
||||
|
||||
```
|
||||
$ git submodule init
|
||||
$ git submodule update
|
||||
```
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
- 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
|
||||
```
|
||||
|
||||
- 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
|
||||
|
||||
|
@ -20,6 +20,6 @@ opt_in_rules:
|
||||
- unneeded_parentheses_in_closure_argument
|
||||
- unused_import
|
||||
- trailing_closure
|
||||
variable_name:
|
||||
identifier_name:
|
||||
min_length:
|
||||
warning: 0
|
||||
|
@ -27,7 +27,7 @@ extension FileManager {
|
||||
return sharedFolderURL
|
||||
}
|
||||
|
||||
static var networkExtensionLogFileURL: URL? {
|
||||
static var logFileURL: URL? {
|
||||
return sharedFolderURL?.appendingPathComponent("tunnel-log.bin")
|
||||
}
|
||||
|
||||
@ -35,14 +35,6 @@ extension FileManager {
|
||||
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 {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: url)
|
||||
|
@ -12,10 +12,12 @@ public class Logger {
|
||||
static var global: Logger?
|
||||
|
||||
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 }
|
||||
self.log = log
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -23,17 +25,14 @@ public class Logger {
|
||||
}
|
||||
|
||||
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 {
|
||||
guard let other = open_log(otherLogFile) else { return false }
|
||||
let ret = write_logs_to_file(targetFile, log, ourTag, other, otherTag)
|
||||
close_log(other)
|
||||
return ret == 0
|
||||
func writeLog(to targetFile: String) -> Bool {
|
||||
return write_log_to_file(targetFile, self.log) == 0
|
||||
}
|
||||
|
||||
static func configureGlobal(withFilePath filePath: String?) {
|
||||
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
|
||||
if Logger.global != nil {
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
@ -52,7 +51,6 @@ public class Logger {
|
||||
}
|
||||
let goBackendVersion = WIREGUARD_GO_VERSION
|
||||
Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
@ -19,100 +21,124 @@
|
||||
|
||||
enum {
|
||||
MAX_LOG_LINE_LENGTH = 512,
|
||||
MAX_LINES = 1024,
|
||||
MAGIC = 0xbeefbabeU
|
||||
MAX_LINES = 2048,
|
||||
MAGIC = 0xabadbeefU
|
||||
};
|
||||
|
||||
struct log_line {
|
||||
struct timeval tv;
|
||||
atomic_uint_fast64_t time_ns;
|
||||
char line[MAX_LOG_LINE_LENGTH];
|
||||
};
|
||||
|
||||
struct log {
|
||||
struct { uint32_t first, len; } header;
|
||||
atomic_uint_fast32_t next_index;
|
||||
struct log_line lines[MAX_LINES];
|
||||
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)
|
||||
log->header.first = (log->header.first + 1) % MAX_LINES;
|
||||
else
|
||||
++log->header.len;
|
||||
// Race: This isn't synchronized with the fetch_add below, so items might be slightly out of order.
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
|
||||
gettimeofday(&line->tv, NULL);
|
||||
strncpy(line->line, msg, MAX_LOG_LINE_LENGTH - 1);
|
||||
line->line[MAX_LOG_LINE_LENGTH - 1] = '\0';
|
||||
// Race: More than MAX_LINES writers and this will clash.
|
||||
index = atomic_fetch_add(&log->next_index, 1);
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
return true;
|
||||
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;
|
||||
struct log *log;
|
||||
uint32_t l, i;
|
||||
FILE *file;
|
||||
int ret;
|
||||
|
||||
if (len1 > MAX_LINES)
|
||||
len1 = MAX_LINES;
|
||||
if (len2 > MAX_LINES)
|
||||
len2 = MAX_LINES;
|
||||
log = malloc(sizeof(*log));
|
||||
if (!log)
|
||||
return -errno;
|
||||
memcpy(log, input_log, sizeof(*log));
|
||||
|
||||
file = fopen(file_name, "w");
|
||||
if (!file)
|
||||
if (!file) {
|
||||
free(log);
|
||||
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;
|
||||
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))) {
|
||||
line = line1;
|
||||
tag = tag1;
|
||||
++i1;
|
||||
} else if (i2 < len2 && (i1 >= len1 || first_before_second(line2, line1))) {
|
||||
line = line2;
|
||||
tag = tag2;
|
||||
++i2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (!line->time_ns)
|
||||
continue;
|
||||
|
||||
memcpy(buf, line->line, MAX_LOG_LINE_LENGTH);
|
||||
buf[MAX_LOG_LINE_LENGTH - 1] = '\0';
|
||||
if (!localtime_r(&line->tv.tv_sec, &tm))
|
||||
if (!localtime_r(&seconds, &tm))
|
||||
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_hour, tm.tm_min, tm.tm_sec, line->tv.tv_usec,
|
||||
tag, buf) < 0)
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec, useconds,
|
||||
line->line) < 0)
|
||||
goto err;
|
||||
|
||||
|
||||
}
|
||||
errno = 0;
|
||||
|
||||
err:
|
||||
ret = -errno;
|
||||
fclose(file);
|
||||
free(log);
|
||||
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)
|
||||
{
|
||||
int fd;
|
||||
|
@ -6,9 +6,12 @@
|
||||
#ifndef RINGLOGGER_H
|
||||
#define RINGLOGGER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct log;
|
||||
void write_msg_to_log(struct log *log, 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);
|
||||
void write_msg_to_log(struct log *log, const char *tag, const char *msg);
|
||||
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);
|
||||
void close_log(struct log *log);
|
||||
|
||||
|
63
WireGuard/Shared/Logging/test_ringlogger.c
Normal file
63
WireGuard/Shared/Logging/test_ringlogger.c
Normal file
@ -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;
|
||||
}
|
@ -13,8 +13,8 @@ extension Data {
|
||||
return nil
|
||||
}
|
||||
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_HEX))
|
||||
out.withUnsafeMutableBytes { outBytes in
|
||||
self.withUnsafeBytes { inBytes in
|
||||
out.withUnsafeMutableInt8Bytes { outBytes in
|
||||
self.withUnsafeUInt8Bytes { inBytes in
|
||||
key_to_hex(outBytes, inBytes)
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@ extension Data {
|
||||
init?(hexKey hexString: String) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -35,8 +35,8 @@ extension Data {
|
||||
return nil
|
||||
}
|
||||
var out = Data(repeating: 0, count: Int(WG_KEY_LEN_BASE64))
|
||||
out.withUnsafeMutableBytes { outBytes in
|
||||
self.withUnsafeBytes { inBytes in
|
||||
out.withUnsafeMutableInt8Bytes { outBytes in
|
||||
self.withUnsafeUInt8Bytes { inBytes in
|
||||
key_to_base64(outBytes, inBytes)
|
||||
}
|
||||
}
|
||||
@ -47,8 +47,34 @@ extension Data {
|
||||
init?(base64Key base64String: String) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ extension Endpoint {
|
||||
return "\(address):\(port)"
|
||||
case .ipv6(let address):
|
||||
return "[\(address)]:\(port)"
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +80,8 @@ extension Endpoint {
|
||||
return true
|
||||
case .ipv6:
|
||||
return true
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +93,8 @@ extension Endpoint {
|
||||
return nil
|
||||
case .ipv6:
|
||||
return nil
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,9 +49,9 @@ extension NETunnelProviderProtocol {
|
||||
Keychain.deleteReference(called: ref)
|
||||
}
|
||||
|
||||
func verifyConfigurationReference() -> Data? {
|
||||
guard let ref = passwordReference else { return nil }
|
||||
return Keychain.verifyReference(called: ref) ? ref : nil
|
||||
func verifyConfigurationReference() -> Bool {
|
||||
guard let ref = passwordReference else { return false }
|
||||
return Keychain.verifyReference(called: ref)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
|
@ -52,15 +52,15 @@ extension TunnelConfiguration {
|
||||
trimmedLine = String(line)
|
||||
}
|
||||
|
||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
||||
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let lowercasedLine = trimmedLine.lowercased()
|
||||
|
||||
if !trimmedLine.isEmpty {
|
||||
if let equalsIndex = trimmedLine.firstIndex(of: "=") {
|
||||
// Line contains an attribute
|
||||
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespaces)
|
||||
let keyWithCase = trimmedLine[..<equalsIndex].trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
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"]
|
||||
if let presentValue = attributes[key] {
|
||||
if keysWithMultipleEntriesAllowed.contains(key) {
|
||||
@ -182,7 +182,7 @@ extension TunnelConfiguration {
|
||||
}
|
||||
if let addressesString = attributes["address"] {
|
||||
var addresses = [IPAddressRange]()
|
||||
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespaces) {
|
||||
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
|
||||
guard let address = IPAddressRange(from: addressString) else {
|
||||
throw ParseError.interfaceHasInvalidAddress(addressString)
|
||||
}
|
||||
@ -192,7 +192,7 @@ extension TunnelConfiguration {
|
||||
}
|
||||
if let dnsString = attributes["dns"] {
|
||||
var dnsServers = [DNSServer]()
|
||||
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespaces) {
|
||||
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) {
|
||||
guard let dnsServer = DNSServer(from: dnsServerString) else {
|
||||
throw ParseError.interfaceHasInvalidDNS(dnsServerString)
|
||||
}
|
||||
|
@ -41,13 +41,17 @@
|
||||
6B62E45F220A6FA900EF34A6 /* 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 */; };
|
||||
6B6956362211DA80001B618A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6B6956352211DA80001B618A /* main.m */; };
|
||||
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 */; };
|
||||
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */; };
|
||||
6BD5C97B220D1AE200784E08 /* 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 */; };
|
||||
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 */; };
|
||||
6F4DD16C21DA558F00690EAE /* NSTableView+Reuse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */; };
|
||||
6F4DD16E21DBEA0700690EAE /* ManageTunnelsRootViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */; };
|
||||
@ -55,6 +59,7 @@
|
||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.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, ); }; };
|
||||
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5EA59A223E58A8002B380A /* ButtonRow.swift */; };
|
||||
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; };
|
||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; };
|
||||
6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; };
|
||||
@ -66,6 +71,7 @@
|
||||
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 */; };
|
||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E0217181B1006A79B3 /* AppDelegate.swift */; };
|
||||
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7774E321718281006A79B3 /* TunnelsListTableViewController.swift */; };
|
||||
@ -76,13 +82,21 @@
|
||||
6F7F7E5F21C7D74B00527607 /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F89E17921EDEB0E00C97BB9 /* StatusItemController.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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
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 */; };
|
||||
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */; };
|
||||
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FB1BD5F21D2607A00A991BF /* AppDelegate.swift */; };
|
||||
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, ); }; };
|
||||
@ -120,7 +134,7 @@
|
||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A52180447E0012E523 /* x25519.c */; };
|
||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6899A7218044FC0012E523 /* Curve25519.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 */; };
|
||||
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */; };
|
||||
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE254FA219C10800028284D /* ZipImporter.swift */; };
|
||||
@ -141,11 +155,14 @@
|
||||
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */; };
|
||||
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FBA104121D6BC210051C35F /* ErrorPresenter.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 */; };
|
||||
6FCD99B121E0EDA900BA4C82 /* TunnelEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */; };
|
||||
6FDB3C3B21DCF47400A0C0BF /* TunnelDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.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 */; };
|
||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
|
||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
|
||||
@ -173,7 +190,7 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@ -185,6 +202,13 @@
|
||||
remoteGlobalIDString = 6F5D0C19218352EF000F85AD;
|
||||
remoteInfo = WireGuardNetworkExtension;
|
||||
};
|
||||
6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6F70E22822106A2D008BDFB4;
|
||||
remoteInfo = WireGuardmacOSLoginItemHelper;
|
||||
};
|
||||
6FB1BD9721D4BFE700A991BF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6FF4AC0C211EC46F002C96EB /* Project object */;
|
||||
@ -220,6 +244,17 @@
|
||||
name = "Embed App Extensions";
|
||||
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 */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -255,10 +290,14 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePrivacyNotice.swift; 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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -270,6 +309,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -282,6 +322,9 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -292,13 +335,19 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@ -314,10 +363,13 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
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>"; };
|
||||
@ -345,7 +397,7 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -397,6 +449,9 @@
|
||||
5F4541A521C4449E00994C13 /* ButtonCell.swift */,
|
||||
5F45419121C2D55800994C13 /* CheckmarkCell.swift */,
|
||||
5F4541A121C2D6DF00994C13 /* BorderedTextButton.swift */,
|
||||
6F8F0D7322267AD2000E8335 /* ChevronCell.swift */,
|
||||
6F0F44C8222D55BB00B0FF04 /* TextCell.swift */,
|
||||
6F0F44CA222D55FD00B0FF04 /* EditableTextCell.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -410,6 +465,9 @@
|
||||
6F628C40217F47DB003482A3 /* TunnelDetailTableViewController.swift */,
|
||||
6FDEF805218725D200D8FBF6 /* SettingsTableViewController.swift */,
|
||||
6F7774DF217181B1006A79B3 /* MainViewController.swift */,
|
||||
6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
|
||||
6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
|
||||
6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
|
||||
);
|
||||
path = ViewController;
|
||||
sourceTree = "<group>";
|
||||
@ -423,8 +481,11 @@
|
||||
6F613D9A21DE33B8004B217A /* KeyValueRow.swift */,
|
||||
5F52D0BA21E3781B00283CEA /* ConfTextView.swift */,
|
||||
5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */,
|
||||
6F9B582721E8CD4300544D02 /* PopupRow.swift */,
|
||||
6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */,
|
||||
6F5EA59A223E58A8002B380A /* ButtonRow.swift */,
|
||||
6FB17945222FD5960018AE71 /* OnDemandWiFiControls.swift */,
|
||||
6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */,
|
||||
6F1075632258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -465,15 +526,27 @@
|
||||
path = Crypto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6F70E23922109BEF008BDFB4 /* LoginItemHelper.entitlements */,
|
||||
6F70E23222106A31008BDFB4 /* Info.plist */,
|
||||
6B6956352211DA80001B618A /* main.m */,
|
||||
);
|
||||
path = LoginItemHelper;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
6F7774DD217181B1006A79B3 /* UI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6FB1BD5E21D2607A00A991BF /* macOS */,
|
||||
6F7774DE217181B1006A79B3 /* iOS */,
|
||||
6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */,
|
||||
6F8F0D7022258153000E8335 /* ActivateOnDemandViewModel.swift */,
|
||||
6FBA103D21D6B6D70051C35F /* TunnelImporter.swift */,
|
||||
6FBA103A21D6B4280051C35F /* ErrorPresenterProtocol.swift */,
|
||||
6B62E45E220A6FA900EF34A6 /* PrivateDataConfirmation.swift */,
|
||||
6F907C9B224663A2003CED21 /* LogViewHelper.swift */,
|
||||
);
|
||||
path = UI;
|
||||
sourceTree = "<group>";
|
||||
@ -483,10 +556,11 @@
|
||||
children = (
|
||||
6FF4AC1E211EC472002C96EB /* Assets.xcassets */,
|
||||
6FF4AC20211EC472002C96EB /* LaunchScreen.storyboard */,
|
||||
5F4541AC21C4720B00994C13 /* ViewController */,
|
||||
5F4541A721C44F5B00994C13 /* View */,
|
||||
5F4541AC21C4720B00994C13 /* ViewController */,
|
||||
6F7774E0217181B1006A79B3 /* AppDelegate.swift */,
|
||||
6F919EC2218A2AE90023B400 /* ErrorPresenter.swift */,
|
||||
6F19D30322402B8700A126F2 /* ConfirmationAlertPresenter.swift */,
|
||||
5F45417C21C1B23600994C13 /* UITableViewCell+Reuse.swift */,
|
||||
6FF4AC23211EC472002C96EB /* Info.plist */,
|
||||
6FF4AC482120B9E0002C96EB /* WireGuard.entitlements */,
|
||||
@ -518,7 +592,7 @@
|
||||
children = (
|
||||
6F7774EE21722D97006A79B3 /* TunnelsManager.swift */,
|
||||
6B707D8321F918D4000A8F73 /* TunnelConfiguration+UapiConfig.swift */,
|
||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */,
|
||||
6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */,
|
||||
5F4541A821C451D100994C13 /* TunnelStatus.swift */,
|
||||
6FB1017821C57DE600766195 /* MockTunnels.swift */,
|
||||
6F7F7E5E21C7D74B00527607 /* TunnelErrors.swift */,
|
||||
@ -548,6 +622,7 @@
|
||||
6FB1BD5E21D2607A00A991BF /* macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6F70E22A22106A2D008BDFB4 /* LoginItemHelper */,
|
||||
6F4DD16921DA556600690EAE /* View */,
|
||||
6FBA104421D7EA750051C35F /* ViewController */,
|
||||
6FBA101321D613F30051C35F /* Application.swift */,
|
||||
@ -563,7 +638,6 @@
|
||||
6F4DD16721DA552B00690EAE /* NSTableView+Reuse.swift */,
|
||||
5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */,
|
||||
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */,
|
||||
6BAC16E42216324B00A5FB78 /* AppStorePrivacyNotice.swift */,
|
||||
);
|
||||
path = macOS;
|
||||
sourceTree = "<group>";
|
||||
@ -574,8 +648,10 @@
|
||||
6FBA104521D7EBFA0051C35F /* TunnelsListTableViewController.swift */,
|
||||
6F4DD16D21DBEA0700690EAE /* ManageTunnelsRootViewController.swift */,
|
||||
6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */,
|
||||
6FCD99A821E0E0C700BA4C82 /* NoTunnelsDetailViewController.swift */,
|
||||
6FCD99A821E0E0C700BA4C82 /* ButtonedDetailViewController.swift */,
|
||||
6FCD99B021E0EDA900BA4C82 /* TunnelEditViewController.swift */,
|
||||
6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */,
|
||||
6FADE96A2254A6C200B838A4 /* UnusableTunnelDetailViewController.swift */,
|
||||
);
|
||||
path = ViewController;
|
||||
sourceTree = "<group>";
|
||||
@ -642,6 +718,7 @@
|
||||
6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */,
|
||||
6FB1BD5D21D2607A00A991BF /* WireGuard.app */,
|
||||
6FB1BD9121D4BFE600A991BF /* WireGuardNetworkExtension.appex */,
|
||||
6F70E22922106A2D008BDFB4 /* WireGuardLoginItemHelper.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -728,6 +805,21 @@
|
||||
productReference = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */;
|
||||
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 */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 6FB1BD6A21D2607E00A991BF /* Build configuration list for PBXNativeTarget "WireGuardmacOS" */;
|
||||
@ -739,10 +831,12 @@
|
||||
6FB1BD5A21D2607A00A991BF /* Frameworks */,
|
||||
6FB1BD5B21D2607A00A991BF /* Resources */,
|
||||
6FB1BD9D21D4BFE700A991BF /* Embed App Extensions */,
|
||||
6F70E23C22109DE5008BDFB4 /* Embed Login Item Helper */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */,
|
||||
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */,
|
||||
);
|
||||
name = WireGuardmacOS;
|
||||
@ -804,10 +898,19 @@
|
||||
TargetAttributes = {
|
||||
6F5D0C19218352EF000F85AD = {
|
||||
CreatedOnToolsVersion = 10.0;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
};
|
||||
6F70E22822106A2D008BDFB4 = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
SystemCapabilities = {
|
||||
com.apple.Sandbox = {
|
||||
enabled = 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
6FB1BD5C21D2607A00A991BF = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.ApplicationGroups.Mac = {
|
||||
enabled = 1;
|
||||
@ -822,6 +925,7 @@
|
||||
};
|
||||
6FB1BD9021D4BFE600A991BF = {
|
||||
CreatedOnToolsVersion = 10.1;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.ApplicationGroups.Mac = {
|
||||
enabled = 1;
|
||||
@ -833,8 +937,11 @@
|
||||
};
|
||||
6FF4AC13211EC46F002C96EB = {
|
||||
CreatedOnToolsVersion = 9.4.1;
|
||||
LastSwiftMigration = 1000;
|
||||
LastSwiftMigration = 1020;
|
||||
SystemCapabilities = {
|
||||
com.apple.AccessWiFi = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.ApplicationGroups.iOS = {
|
||||
enabled = 1;
|
||||
};
|
||||
@ -864,6 +971,7 @@
|
||||
6FB1BD5C21D2607A00A991BF /* WireGuardmacOS */,
|
||||
6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */,
|
||||
6FDAA03421CE69D000FA6925 /* WireGuardGoBridgemacOS */,
|
||||
6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -1123,13 +1231,22 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6F70E22522106A2D008BDFB4 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6B6956362211DA80001B618A /* main.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
6FB1BD5921D2607A00A991BF /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
6FBA101521D613F90051C35F /* Application.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 */,
|
||||
6FB1BDD021D50F5300A991BF /* TunnelErrors.swift in Sources */,
|
||||
6FB1BDD121D50F5300A991BF /* ZipImporter.swift in Sources */,
|
||||
@ -1138,6 +1255,7 @@
|
||||
6FB1BDD321D50F5300A991BF /* ZipArchive.swift in Sources */,
|
||||
6FB1BDD421D50F5300A991BF /* ioapi.c in Sources */,
|
||||
6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */,
|
||||
6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */,
|
||||
6B5C5E29220A48D30024272E /* Keychain.swift in Sources */,
|
||||
6FCD99AF21E0EA1700BA4C82 /* ImportPanelPresenter.swift in Sources */,
|
||||
6FB1BDD521D50F5300A991BF /* unzip.c in Sources */,
|
||||
@ -1158,32 +1276,36 @@
|
||||
6FB1BDCA21D50F1700A991BF /* x25519.c in Sources */,
|
||||
6FB1BDCB21D50F1700A991BF /* Curve25519.swift in Sources */,
|
||||
6B586C55220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||
6F9B582921E8D6D100544D02 /* PopupRow.swift in Sources */,
|
||||
6BAC16E6221634B300A5FB78 /* AppStorePrivacyNotice.swift in Sources */,
|
||||
6FB17946222FD5960018AE71 /* OnDemandWiFiControls.swift in Sources */,
|
||||
6FB1BDBB21D50F0200A991BF /* Localizable.strings in Sources */,
|
||||
6FB1BDBC21D50F0200A991BF /* ringlogger.c in Sources */,
|
||||
6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */,
|
||||
6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */,
|
||||
6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */,
|
||||
6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */,
|
||||
6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */,
|
||||
6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */,
|
||||
6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */,
|
||||
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
|
||||
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
|
||||
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
||||
6FADE96C2254B8C300B838A4 /* UnusableTunnelDetailViewController.swift in Sources */,
|
||||
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
|
||||
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||
6F1075642258AE9800D78929 /* DeleteTunnelsConfirmationAlert.swift in Sources */,
|
||||
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
|
||||
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
|
||||
6FB1BDC121D50F0200A991BF /* String+ArrayConversion.swift in Sources */,
|
||||
5F52D0BB21E3781B00283CEA /* ConfTextView.swift in Sources */,
|
||||
6FBA104021D6B7040051C35F /* ErrorPresenterProtocol.swift in Sources */,
|
||||
6FCD99AA21E0E14700BA4C82 /* NoTunnelsDetailViewController.swift in Sources */,
|
||||
6FCD99AA21E0E14700BA4C82 /* ButtonedDetailViewController.swift in Sources */,
|
||||
6FB1BDC321D50F0300A991BF /* TunnelConfiguration.swift in Sources */,
|
||||
6FB1BDC421D50F0300A991BF /* IPAddressRange.swift in Sources */,
|
||||
6FBA104321D6BC250051C35F /* ErrorPresenter.swift in Sources */,
|
||||
6FB1BDC521D50F0300A991BF /* Endpoint.swift in Sources */,
|
||||
6FB1BDC621D50F0300A991BF /* DNSServer.swift in Sources */,
|
||||
6FB1BDC721D50F0300A991BF /* InterfaceConfiguration.swift in Sources */,
|
||||
6F907C9D224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
||||
6FB1BDC821D50F0300A991BF /* PeerConfiguration.swift in Sources */,
|
||||
6FB1BDC921D50F0300A991BF /* FileManager+Extension.swift in Sources */,
|
||||
6FB1BD6021D2607A00A991BF /* AppDelegate.swift in Sources */,
|
||||
@ -1222,18 +1344,23 @@
|
||||
files = (
|
||||
6FE1765A21C90E87002690EA /* LocalizationHelper.swift in Sources */,
|
||||
6FF3527221C2616C0008484E /* ringlogger.c in Sources */,
|
||||
6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */,
|
||||
6FF3527321C2616C0008484E /* Logger.swift in Sources */,
|
||||
6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */,
|
||||
6F7774EF21722D97006A79B3 /* TunnelsManager.swift in Sources */,
|
||||
5F45417D21C1B23600994C13 /* UITableViewCell+Reuse.swift in Sources */,
|
||||
5F45419221C2D55800994C13 /* CheckmarkCell.swift in Sources */,
|
||||
6FE254FF219C60290028284D /* ZipExporter.swift in Sources */,
|
||||
6F8F0D7122258153000E8335 /* ActivateOnDemandViewModel.swift in Sources */,
|
||||
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */,
|
||||
6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
|
||||
6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
|
||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
||||
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
|
||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||
5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
|
||||
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
||||
6F8F0D7422267AD2000E8335 /* ChevronCell.swift in Sources */,
|
||||
6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */,
|
||||
6F6899A62180447E0012E523 /* x25519.c in Sources */,
|
||||
6F7774E2217181B1006A79B3 /* AppDelegate.swift in Sources */,
|
||||
@ -1246,6 +1373,8 @@
|
||||
5F4541A621C4449E00994C13 /* ButtonCell.swift in Sources */,
|
||||
5F45419821C2D60500994C13 /* KeyValueCell.swift in Sources */,
|
||||
6FBA103E21D6B6D70051C35F /* TunnelImporter.swift in Sources */,
|
||||
6F9B8A8E223398610041B9C4 /* SSIDOptionDetailTableViewController.swift in Sources */,
|
||||
6F19D30422402B8700A126F2 /* ConfirmationAlertPresenter.swift in Sources */,
|
||||
6F919EC3218A2AE90023B400 /* ErrorPresenter.swift in Sources */,
|
||||
6B62E45F220A6FA900EF34A6 /* PrivateDataConfirmation.swift in Sources */,
|
||||
6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */,
|
||||
@ -1255,6 +1384,7 @@
|
||||
6F7774E82172020C006A79B3 /* TunnelConfiguration.swift in Sources */,
|
||||
6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */,
|
||||
6F6899A8218044FC0012E523 /* Curve25519.swift in Sources */,
|
||||
6F0F44C9222D55BB00B0FF04 /* TextCell.swift in Sources */,
|
||||
5F4541A021C2D6B700994C13 /* TunnelListCell.swift in Sources */,
|
||||
5F9696B021CD7128008063FE /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
|
||||
6F628C41217F47DB003482A3 /* TunnelDetailTableViewController.swift in Sources */,
|
||||
@ -1270,8 +1400,9 @@
|
||||
5F4541A221C2D6DF00994C13 /* BorderedTextButton.swift in Sources */,
|
||||
5FF7B96521CC95FA00A7DD74 /* PeerConfiguration.swift in Sources */,
|
||||
6F7774E1217181B1006A79B3 /* MainViewController.swift in Sources */,
|
||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */,
|
||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */,
|
||||
5F4541B221CBFAEE00994C13 /* String+ArrayConversion.swift in Sources */,
|
||||
6F907C9C224663A2003CED21 /* LogViewHelper.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1283,6 +1414,11 @@
|
||||
target = 6F5D0C19218352EF000F85AD /* WireGuardNetworkExtensioniOS */;
|
||||
targetProxy = 6F5D0C20218352EF000F85AD /* PBXContainerItemProxy */;
|
||||
};
|
||||
6F70E23B22109DD3008BDFB4 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6F70E22822106A2D008BDFB4 /* WireGuardmacOSLoginItemHelper */;
|
||||
targetProxy = 6F70E23A22109DD3008BDFB4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
6FB1BD9821D4BFE700A991BF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 6FB1BD9021D4BFE600A991BF /* WireGuardNetworkExtensionmacOS */;
|
||||
@ -1346,7 +1482,7 @@
|
||||
PRODUCT_NAME = WireGuardNetworkExtension;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||
SWIFT_VERSION = 4.2;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1368,7 +1504,33 @@
|
||||
PRODUCT_NAME = WireGuardNetworkExtension;
|
||||
SKIP_INSTALL = YES;
|
||||
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;
|
||||
};
|
||||
@ -1388,6 +1550,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
||||
PRODUCT_NAME = WireGuard;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1407,6 +1570,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_MACOS)";
|
||||
PRODUCT_NAME = WireGuard;
|
||||
SDKROOT = macosx;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1428,6 +1592,7 @@
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1449,6 +1614,7 @@
|
||||
SDKROOT = macosx;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "WireGuardNetworkExtension/WireGuardNetworkExtension-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1625,6 +1791,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
||||
PRODUCT_NAME = WireGuard;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@ -1641,6 +1808,7 @@
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(APP_ID_IOS)";
|
||||
PRODUCT_NAME = WireGuard;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
@ -1656,6 +1824,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
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" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
@ -13,6 +13,10 @@
|
||||
"tunnelsListSettingsButtonTitle" = "Settings";
|
||||
"tunnelsListCenteredAddTunnelButtonTitle" = "Add a tunnel";
|
||||
"tunnelsListSwipeDeleteButtonTitle" = "Delete";
|
||||
"tunnelsListSelectButtonTitle" = "Select";
|
||||
"tunnelsListSelectAllButtonTitle" = "Select All";
|
||||
"tunnelsListDeleteButtonTitle" = "Delete";
|
||||
"tunnelsListSelectedTitle (%d)" = "%d selected";
|
||||
|
||||
// Tunnels list menu
|
||||
|
||||
@ -23,11 +27,18 @@
|
||||
|
||||
// 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";
|
||||
"alertImportedFromZipMessage (%1$d of %2$d)" = "Created %1$d of %2$d tunnels from zip archive";
|
||||
|
||||
"alertUnableToImportTitle" = "Unable to import tunnel";
|
||||
"alertUnableToImportMessage" = "An error occured when importing the tunnel configuration.";
|
||||
"alertBadConfigImportTitle" = "Unable to import tunnel";
|
||||
"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
|
||||
|
||||
@ -44,6 +55,14 @@
|
||||
"tunnelStatusRestarting" = "Restarting";
|
||||
"tunnelStatusWaiting" = "Waiting";
|
||||
|
||||
"macToggleStatusButtonActivate" = "Activate";
|
||||
"macToggleStatusButtonActivating" = "Activating…";
|
||||
"macToggleStatusButtonDeactivate" = "Deactivate";
|
||||
"macToggleStatusButtonDeactivating" = "Deactivating…";
|
||||
"macToggleStatusButtonReasserting" = "Reactivating…";
|
||||
"macToggleStatusButtonRestarting" = "Restarting…";
|
||||
"macToggleStatusButtonWaiting" = "Waiting…";
|
||||
|
||||
"tunnelSectionTitleInterface" = "Interface";
|
||||
|
||||
"tunnelInterfaceName" = "Name";
|
||||
@ -54,6 +73,7 @@
|
||||
"tunnelInterfaceListenPort" = "Listen port";
|
||||
"tunnelInterfaceMTU" = "MTU";
|
||||
"tunnelInterfaceDNS" = "DNS servers";
|
||||
"tunnelInterfaceStatus" = "Status";
|
||||
|
||||
"tunnelSectionTitlePeer" = "Peer";
|
||||
|
||||
@ -69,7 +89,28 @@
|
||||
|
||||
"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";
|
||||
"tunnelOnDemandOptionWiFiOnly" = "Wi-Fi only";
|
||||
"tunnelOnDemandOptionWiFiOrCellular" = "Wi-Fi or cellular";
|
||||
@ -168,17 +209,18 @@
|
||||
"settingsSectionTitleExportConfigurations" = "Export configurations";
|
||||
"settingsExportZipButtonTitle" = "Export zip archive";
|
||||
|
||||
"settingsSectionTitleTunnelLog" = "Tunnel log";
|
||||
"settingsExportLogFileButtonTitle" = "Export log file";
|
||||
"settingsSectionTitleTunnelLog" = "Log";
|
||||
"settingsViewLogButtonTitle" = "View log";
|
||||
|
||||
// Settings alerts
|
||||
// Log view
|
||||
|
||||
"logViewTitle" = "Log";
|
||||
|
||||
// Log alerts
|
||||
|
||||
"alertUnableToRemovePreviousLogTitle" = "Log export failed";
|
||||
"alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
|
||||
|
||||
"alertUnableToFindExtensionLogPathTitle" = "Log export failed";
|
||||
"alertUnableToFindExtensionLogPathMessage" = "Unable to determine extension log path";
|
||||
|
||||
"alertUnableToWriteLogTitle" = "Log export failed";
|
||||
"alertUnableToWriteLogMessage" = "Unable to write logs to file";
|
||||
|
||||
@ -199,6 +241,11 @@
|
||||
"alertNoTunnelsInImportedZipArchiveTitle" = "No tunnels in 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
|
||||
|
||||
"alertTunnelActivationFailureTitle" = "Activation failure";
|
||||
@ -253,7 +300,7 @@
|
||||
"macMenuManageTunnels" = "Manage tunnels";
|
||||
"macMenuImportTunnels" = "Import tunnel(s) from file…";
|
||||
"macMenuAddEmptyTunnel" = "Add empty tunnel…";
|
||||
"macMenuExportLog" = "Export log to file…";
|
||||
"macMenuViewLog" = "View log";
|
||||
"macMenuExportTunnels" = "Export tunnels to zip…";
|
||||
"macMenuAbout" = "About WireGuard";
|
||||
"macMenuQuit" = "Quit";
|
||||
@ -263,23 +310,28 @@
|
||||
"macWindowTitleManageTunnels" = "Manage WireGuard Tunnels";
|
||||
|
||||
"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.";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDelete" = "Delete";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleCancel" = "Cancel";
|
||||
"macDeleteTunnelConfirmationAlertButtonTitleDeleting" = "Deleting…";
|
||||
|
||||
"macButtonImportTunnels" = "Import tunnel(s) from file";
|
||||
"macSheetButtonImport" = "Import";
|
||||
|
||||
"macNameFieldExportLog" = "Export log to";
|
||||
"macNameFieldExportLog" = "Save log to:";
|
||||
"macSheetButtonExportLog" = "Save";
|
||||
|
||||
"macNameFieldExportZip" = "Export tunnels to";
|
||||
"macNameFieldExportZip" = "Export tunnels to:";
|
||||
"macSheetButtonExportZip" = "Save";
|
||||
|
||||
"macButtonDeleteTunnels (%d)" = "Delete %d tunnels";
|
||||
|
||||
// Mac detail/edit view fields
|
||||
|
||||
"macFieldKey (%@)" = "%@:";
|
||||
"macFieldOnDemand" = "On-Demand:";
|
||||
"macFieldOnDemandSSIDs" = "SSIDs:";
|
||||
|
||||
// Mac status display
|
||||
|
||||
@ -335,3 +387,21 @@
|
||||
"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";
|
||||
|
@ -1,2 +1,2 @@
|
||||
VERSION_NAME = 0.0.20190207
|
||||
VERSION_ID = 3
|
||||
VERSION_NAME = 0.0.20190409
|
||||
VERSION_ID = 7
|
||||
|
@ -9,7 +9,7 @@ struct Curve25519 {
|
||||
|
||||
static func generatePrivateKey() -> Data {
|
||||
var privateKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
||||
privateKey.withUnsafeMutableBytes { bytes in
|
||||
privateKey.withUnsafeMutableUInt8Bytes { bytes in
|
||||
curve25519_generate_private_key(bytes)
|
||||
}
|
||||
assert(privateKey.count == TunnelConfiguration.keyLength)
|
||||
@ -19,8 +19,8 @@ struct Curve25519 {
|
||||
static func generatePublicKey(fromPrivateKey privateKey: Data) -> Data {
|
||||
assert(privateKey.count == TunnelConfiguration.keyLength)
|
||||
var publicKey = Data(repeating: 0, count: TunnelConfiguration.keyLength)
|
||||
privateKey.withUnsafeBytes { privateKeyBytes in
|
||||
publicKey.withUnsafeMutableBytes { bytes in
|
||||
privateKey.withUnsafeUInt8Bytes { privateKeyBytes in
|
||||
publicKey.withUnsafeMutableUInt8Bytes { bytes in
|
||||
curve25519_derive_public_key(bytes, privateKeyBytes)
|
||||
}
|
||||
}
|
||||
|
120
WireGuard/WireGuard/Tunnel/ActivateOnDemandOption.swift
Normal file
120
WireGuard/WireGuard/Tunnel/ActivateOnDemandOption.swift
Normal file
@ -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
|
||||
case .invalid:
|
||||
self = .inactive
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,6 +56,8 @@ extension NEVPNStatus: CustomDebugStringConvertible {
|
||||
case .disconnecting: return "disconnecting"
|
||||
case .reasserting: return "reasserting"
|
||||
case .invalid: return "invalid"
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ class TunnelsManager {
|
||||
private var configurationsObservationToken: AnyObject?
|
||||
|
||||
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()
|
||||
startObservingTunnelConfigurations()
|
||||
}
|
||||
@ -47,11 +47,18 @@ class TunnelsManager {
|
||||
var tunnelManagers = managers ?? []
|
||||
var refs: Set<Data> = []
|
||||
for (index, tunnelManager) in tunnelManagers.enumerated().reversed() {
|
||||
let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol
|
||||
if proto?.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") ?? false {
|
||||
guard let proto = tunnelManager.protocolConfiguration as? NETunnelProviderProtocol else { continue }
|
||||
if proto.migrateConfigurationIfNeeded(called: tunnelManager.localizedDescription ?? "unknown") {
|
||||
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)
|
||||
} else {
|
||||
tunnelManager.removeFromPreferences { _ in }
|
||||
@ -71,14 +78,14 @@ class TunnelsManager {
|
||||
let loadedTunnelProviders = managers ?? []
|
||||
|
||||
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
|
||||
self.tunnels.remove(at: index)
|
||||
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: currentTunnel)
|
||||
}
|
||||
}
|
||||
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.refreshStatus()
|
||||
} else {
|
||||
@ -90,14 +97,14 @@ class TunnelsManager {
|
||||
}
|
||||
let tunnel = TunnelContainer(tunnel: loadedTunnelProvider)
|
||||
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)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 ?? ""
|
||||
if tunnelName.isEmpty {
|
||||
completionHandler(.failure(TunnelsManagerError.tunnelNameEmpty))
|
||||
@ -113,7 +120,9 @@ class TunnelsManager {
|
||||
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
||||
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
|
||||
guard error == nil else {
|
||||
@ -125,32 +134,47 @@ class TunnelsManager {
|
||||
|
||||
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)
|
||||
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)!)
|
||||
completionHandler(.success(tunnel))
|
||||
}
|
||||
}
|
||||
|
||||
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt) -> Void) {
|
||||
addMultiple(tunnelConfigurations: ArraySlice(tunnelConfigurations), numberSuccessful: 0, completionHandler: completionHandler)
|
||||
func addMultiple(tunnelConfigurations: [TunnelConfiguration], completionHandler: @escaping (UInt, TunnelsManagerError?) -> Void) {
|
||||
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 {
|
||||
completionHandler(numberSuccessful)
|
||||
completionHandler(numberSuccessful, lastError)
|
||||
return
|
||||
}
|
||||
let tail = tunnelConfigurations.dropFirst()
|
||||
add(tunnelConfiguration: head) { [weak self, tail] result in
|
||||
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 ?? ""
|
||||
if tunnelName.isEmpty {
|
||||
completionHandler(TunnelsManagerError.tunnelNameEmpty)
|
||||
@ -167,11 +191,15 @@ class TunnelsManager {
|
||||
tunnel.name = tunnelName
|
||||
}
|
||||
|
||||
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
||||
var isTunnelConfigurationChanged = false
|
||||
if tunnelProviderManager.tunnelConfiguration != tunnelConfiguration {
|
||||
tunnelProviderManager.setTunnelConfiguration(tunnelConfiguration)
|
||||
isTunnelConfigurationChanged = true
|
||||
}
|
||||
tunnelProviderManager.isEnabled = true
|
||||
|
||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && activateOnDemandSetting.isActivateOnDemandEnabled
|
||||
activateOnDemandSetting.apply(on: tunnelProviderManager)
|
||||
let isActivatingOnDemand = !tunnelProviderManager.isOnDemandEnabled && onDemandOption != .off
|
||||
onDemandOption.apply(on: tunnelProviderManager)
|
||||
|
||||
tunnelProviderManager.saveToPreferences { [weak self] error in
|
||||
guard error == nil else {
|
||||
@ -183,16 +211,18 @@ class TunnelsManager {
|
||||
guard let self = self else { return }
|
||||
if isNameChanged {
|
||||
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)!
|
||||
self.tunnelsListDelegate?.tunnelMoved(from: oldIndex, to: newIndex)
|
||||
}
|
||||
self.tunnelsListDelegate?.tunnelModified(at: self.tunnels.firstIndex(of: tunnel)!)
|
||||
|
||||
if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
|
||||
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
||||
tunnel.status = .restarting
|
||||
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||
if isTunnelConfigurationChanged {
|
||||
if tunnel.status == .active || tunnel.status == .activating || tunnel.status == .reasserting {
|
||||
// Turn off the tunnel, and then turn it back on, so the changes are made effective
|
||||
tunnel.status = .restarting
|
||||
(tunnel.tunnelProvider.connection as? NETunnelProviderSession)?.stopTunnel()
|
||||
}
|
||||
}
|
||||
|
||||
if isActivatingOnDemand {
|
||||
@ -215,7 +245,9 @@ class TunnelsManager {
|
||||
|
||||
func remove(tunnel: TunnelContainer, completionHandler: @escaping (TunnelsManagerError?) -> Void) {
|
||||
let tunnelProviderManager = tunnel.tunnelProvider
|
||||
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
if tunnel.isTunnelConfigurationAvailableInKeychain {
|
||||
(tunnelProviderManager.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference()
|
||||
}
|
||||
|
||||
tunnelProviderManager.removeFromPreferences { [weak self] error in
|
||||
guard error == nil else {
|
||||
@ -223,8 +255,7 @@ class TunnelsManager {
|
||||
completionHandler(TunnelsManagerError.systemErrorOnRemoveTunnel(systemError: error!))
|
||||
return
|
||||
}
|
||||
if let self = self {
|
||||
let index = self.tunnels.firstIndex(of: tunnel)!
|
||||
if let self = self, let index = self.tunnels.firstIndex(of: tunnel) {
|
||||
self.tunnels.remove(at: index)
|
||||
self.tunnelsListDelegate?.tunnelRemoved(at: index, tunnel: tunnel)
|
||||
}
|
||||
@ -232,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 {
|
||||
return tunnels.count
|
||||
}
|
||||
@ -357,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)? {
|
||||
@ -409,8 +464,12 @@ class TunnelContainer: NSObject {
|
||||
return tunnelProvider.tunnelConfiguration
|
||||
}
|
||||
|
||||
var activateOnDemandSetting: ActivateOnDemandSetting {
|
||||
return ActivateOnDemandSetting(from: tunnelProvider)
|
||||
var isTunnelConfigurationAvailableInKeychain: Bool {
|
||||
return tunnelProvider.isTunnelConfigurationAvailableInKeychain
|
||||
}
|
||||
|
||||
var onDemandOption: ActivateOnDemandOption {
|
||||
return ActivateOnDemandOption(from: tunnelProvider)
|
||||
}
|
||||
|
||||
init(tunnel: NETunnelProviderManager) {
|
||||
@ -427,7 +486,7 @@ class TunnelContainer: NSObject {
|
||||
completionHandler(tunnelConfiguration)
|
||||
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 {
|
||||
completionHandler(self.tunnelConfiguration)
|
||||
return
|
||||
@ -440,7 +499,7 @@ class TunnelContainer: NSObject {
|
||||
}
|
||||
|
||||
func refreshStatus() {
|
||||
if (status == .restarting) && (tunnelProvider.connection.status == .disconnected || tunnelProvider.connection.status == .disconnecting) {
|
||||
if status == .restarting {
|
||||
return
|
||||
}
|
||||
status = TunnelStatus(from: tunnelProvider.connection.status)
|
||||
@ -521,7 +580,18 @@ class TunnelContainer: NSObject {
|
||||
}
|
||||
|
||||
extension NETunnelProviderManager {
|
||||
private static var cachedIsConfigAvailableInKeychainKey: UInt8 = 0
|
||||
private static var cachedConfigKey: UInt8 = 0
|
||||
|
||||
var isTunnelConfigurationAvailableInKeychain: Bool {
|
||||
if let cachedNumber = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey) as? NSNumber {
|
||||
return cachedNumber.boolValue
|
||||
}
|
||||
let isAvailable = (protocolConfiguration as? NETunnelProviderProtocol)?.verifyConfigurationReference() ?? false
|
||||
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: isAvailable), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
return isAvailable
|
||||
}
|
||||
|
||||
var tunnelConfiguration: TunnelConfiguration? {
|
||||
if let cached = objc_getAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey) as? TunnelConfiguration {
|
||||
return cached
|
||||
@ -537,5 +607,17 @@ extension NETunnelProviderManager {
|
||||
protocolConfiguration = NETunnelProviderProtocol(tunnelConfiguration: tunnelConfiguration, previouslyFrom: protocolConfiguration)
|
||||
localizedDescription = tunnelConfiguration.name
|
||||
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedConfigKey, tunnelConfiguration, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
objc_setAssociatedObject(self, &NETunnelProviderManager.cachedIsConfigAvailableInKeychainKey, NSNumber(value: true), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
func isEquivalentTo(_ tunnel: TunnelContainer) -> Bool {
|
||||
switch (isTunnelConfigurationAvailableInKeychain, tunnel.isTunnelConfigurationAvailableInKeychain) {
|
||||
case (true, true):
|
||||
return tunnelConfiguration == tunnel.tunnelConfiguration
|
||||
case (false, false):
|
||||
return localizedDescription == tunnel.name
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
198
WireGuard/WireGuard/UI/ActivateOnDemandViewModel.swift
Normal file
198
WireGuard/WireGuard/UI/ActivateOnDemandViewModel.swift
Normal file
@ -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
|
||||
}
|
||||
}
|
57
WireGuard/WireGuard/UI/LogViewHelper.swift
Normal file
57
WireGuard/WireGuard/UI/LogViewHelper.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,40 +4,81 @@
|
||||
import Foundation
|
||||
|
||||
class TunnelImporter {
|
||||
static func importFromFile(url: URL, into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) {
|
||||
if url.pathExtension == "zip" {
|
||||
ZipImporter.importConfigFiles(from: url) { result in
|
||||
if let error = result.error {
|
||||
errorPresenterType.showErrorAlert(error: error, from: sourceVC)
|
||||
return
|
||||
static func importFromFile(urls: [URL], into tunnelsManager: TunnelsManager, sourceVC: AnyObject?, errorPresenterType: ErrorPresenterProtocol.Type, completionHandler: (() -> Void)? = nil) {
|
||||
guard !urls.isEmpty else {
|
||||
completionHandler?()
|
||||
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!
|
||||
tunnelsManager.addMultiple(tunnelConfigurations: configs.compactMap { $0 }) { numberSuccessful in
|
||||
if numberSuccessful == configs.count {
|
||||
completionHandler?()
|
||||
} else { /* if it is not a zip, we assume it is a conf */
|
||||
let fileName = url.lastPathComponent
|
||||
let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
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
|
||||
}
|
||||
let title = tr(format: "alertImportedFromZipTitle (%d)", numberSuccessful)
|
||||
let message = tr(format: "alertImportedFromZipMessage (%1$d of %2$d)", numberSuccessful, configs.count)
|
||||
errorPresenterType.showErrorAlert(title: title, message: message, from: sourceVC, onPresented: completionHandler)
|
||||
}
|
||||
}
|
||||
} else /* if (url.pathExtension == "conf") -- we assume everything else is a conf */ {
|
||||
let fileBaseName = url.deletingPathExtension().lastPathComponent.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
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?()
|
||||
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: fileContents, called: fileBaseName)
|
||||
DispatchQueue.main.async {
|
||||
if tunnelConfiguration == nil {
|
||||
lastFileImportErrorText = (title: tr("alertBadConfigImportTitle"), message: tr(format: "alertBadConfigImportMessage (%@)", fileName))
|
||||
}
|
||||
configs.append(tunnelConfiguration)
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
} 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?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ class TunnelViewModel {
|
||||
case listenPort
|
||||
case mtu
|
||||
case dns
|
||||
case status
|
||||
case toggleStatus
|
||||
|
||||
var localizedUIString: String {
|
||||
switch self {
|
||||
@ -25,6 +27,8 @@ class TunnelViewModel {
|
||||
case .listenPort: return tr("tunnelInterfaceListenPort")
|
||||
case .mtu: return tr("tunnelInterfaceMTU")
|
||||
case .dns: return tr("tunnelInterfaceDNS")
|
||||
case .status: return tr("tunnelInterfaceStatus")
|
||||
case .toggleStatus: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -401,39 +405,49 @@ 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"
|
||||
]
|
||||
|
||||
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() {
|
||||
if scratchpad.isEmpty {
|
||||
populateScratchpad()
|
||||
}
|
||||
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)
|
||||
guard numberOfPeers == 1 else {
|
||||
shouldAllowExcludePrivateIPsControl = false
|
||||
excludePrivateIPsValue = false
|
||||
return
|
||||
}
|
||||
if allowedIPStrings.contains(TunnelViewModel.PeerData.ipv4DefaultRouteString) {
|
||||
shouldAllowExcludePrivateIPsControl = true
|
||||
excludePrivateIPsValue = false
|
||||
} else if allowedIPStrings.isSuperset(of: TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String) {
|
||||
shouldAllowExcludePrivateIPsControl = true
|
||||
excludePrivateIPsValue = true
|
||||
}
|
||||
|
||||
static func normalizedIPAddressRangeStrings(_ list: [String]) -> [String] {
|
||||
return list.compactMap { IPAddressRange(from: $0) }.map { $0.stringRepresentation }
|
||||
}
|
||||
|
||||
static func modifiedAllowedIPs(currentAllowedIPs: [String], excludePrivateIPs: Bool, dnsServers: [String], oldDNSServers: [String]?) -> [String] {
|
||||
let normalizedDNSServers = normalizedIPAddressRangeStrings(dnsServers)
|
||||
let normalizedOldDNSServers = oldDNSServers == nil ? normalizedDNSServers : normalizedIPAddressRangeStrings(oldDNSServers!)
|
||||
let ipv6Addresses = normalizedIPAddressRangeStrings(currentAllowedIPs.filter { $0.contains(":") })
|
||||
if excludePrivateIPs {
|
||||
return ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + normalizedDNSServers
|
||||
} else {
|
||||
shouldAllowExcludePrivateIPsControl = false
|
||||
excludePrivateIPsValue = false
|
||||
return ipv6Addresses.filter { !normalizedOldDNSServers.contains($0) } + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
||||
}
|
||||
}
|
||||
|
||||
func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String) {
|
||||
func excludePrivateIPsValueChanged(isOn: Bool, dnsServers: String, oldDNSServers: String? = nil) {
|
||||
let allowedIPStrings = scratchpad[.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||
let dnsServerStrings = dnsServers.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||
let ipv6Addresses = allowedIPStrings.filter { $0.contains(":") }
|
||||
let modifiedAllowedIPStrings: [String]
|
||||
if isOn {
|
||||
modifiedAllowedIPStrings = ipv6Addresses + TunnelViewModel.PeerData.ipv4DefaultRouteModRFC1918String + dnsServerStrings
|
||||
} else {
|
||||
modifiedAllowedIPStrings = ipv6Addresses + [TunnelViewModel.PeerData.ipv4DefaultRouteString]
|
||||
}
|
||||
let oldDNSServerStrings = oldDNSServers?.splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||
let modifiedAllowedIPStrings = TunnelViewModel.PeerData.modifiedAllowedIPs(currentAllowedIPs: allowedIPStrings, excludePrivateIPs: isOn, dnsServers: dnsServerStrings, oldDNSServers: oldDNSServerStrings)
|
||||
scratchpad[.allowedIPs] = modifiedAllowedIPStrings.joined(separator: ", ")
|
||||
validatedConfiguration = nil
|
||||
excludePrivateIPsValue = isOn
|
||||
@ -515,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> {
|
||||
let interfaceSaveResult = interfaceData.save()
|
||||
let peerSaveResults = peersData.map { $0.save() } // Save all, to help mark erroring fields in red
|
||||
@ -544,6 +569,14 @@ class TunnelViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -589,52 +622,6 @@ class TunnelViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelViewModel {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
private func prettyBytes(_ bytes: UInt64) -> String {
|
||||
switch bytes {
|
||||
case 0..<1024:
|
||||
|
@ -11,7 +11,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
var mainVC: MainViewController?
|
||||
|
||||
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)
|
||||
window.backgroundColor = .white
|
||||
@ -28,7 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
guard let tunnelsManager = mainVC?.tunnelsManager else { return true }
|
||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
|
||||
TunnelImporter.importFromFile(urls: [url], into: tunnelsManager, sourceVC: mainVC, errorPresenterType: ErrorPresenter.self) {
|
||||
_ = FileManager.deleteFile(at: url)
|
||||
}
|
||||
return true
|
||||
|
25
WireGuard/WireGuard/UI/iOS/ConfirmationAlertPresenter.swift
Normal file
25
WireGuard/WireGuard/UI/iOS/ConfirmationAlertPresenter.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
@ -82,7 +82,6 @@
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
|
30
WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift
Normal file
30
WireGuard/WireGuard/UI/iOS/View/ChevronCell.swift
Normal file
@ -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 = ""
|
||||
}
|
||||
}
|
64
WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift
Normal file
64
WireGuard/WireGuard/UI/iOS/View/EditableTextCell.swift
Normal file
@ -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 contentSizeBasedConstraints = [NSLayoutConstraint]()
|
||||
|
||||
var onValueChanged: ((String) -> Void)?
|
||||
var onValueChanged: ((String, String) -> Void)?
|
||||
var onValueBeingEdited: ((String) -> Void)?
|
||||
|
||||
var observationToken: AnyObject?
|
||||
@ -206,7 +206,7 @@ extension KeyValueCell: UITextFieldDelegate {
|
||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||
let isModified = textField.text ?? "" != textFieldValueOnBeginEditing
|
||||
guard isModified else { return }
|
||||
onValueChanged?(textField.text ?? "")
|
||||
onValueChanged?(textFieldValueOnBeginEditing, textField.text ?? "")
|
||||
}
|
||||
|
||||
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
|
||||
|
34
WireGuard/WireGuard/UI/iOS/View/TextCell.swift
Normal file
34
WireGuard/WireGuard/UI/iOS/View/TextCell.swift
Normal file
@ -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")
|
||||
}
|
||||
|
||||
override func setEditing(_ editing: Bool, animated: Bool) {
|
||||
super.setEditing(editing, animated: animated)
|
||||
statusSwitch.isEnabled = !editing
|
||||
}
|
||||
|
||||
private func reset() {
|
||||
statusSwitch.isOn = 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 goBackendVersion
|
||||
case exportZipArchive
|
||||
case exportLogFile
|
||||
case viewLog
|
||||
|
||||
var localizedUIString: String {
|
||||
switch self {
|
||||
case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
|
||||
case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
|
||||
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]] = [
|
||||
[.iosAppVersion, .goBackendVersion],
|
||||
[.exportZipArchive],
|
||||
[.exportLogFile]
|
||||
[.viewLog]
|
||||
]
|
||||
|
||||
let tunnelsManager: TunnelsManager?
|
||||
@ -108,46 +108,10 @@ class SettingsTableViewController: UITableViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func exportLogForLastActivatedTunnel(sourceView: UIView) {
|
||||
guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
|
||||
func presentLogView() {
|
||||
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
|
||||
} else {
|
||||
assert(field == .exportLogFile)
|
||||
assert(field == .viewLog)
|
||||
let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.buttonText = field.localizedUIString
|
||||
cell.onTapped = { [weak self] in
|
||||
self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
|
||||
self?.presentLogView()
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
@ -24,21 +24,28 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||
.rxBytes, .txBytes, .lastHandshakeTime
|
||||
]
|
||||
|
||||
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||
.onDemand, .ssid
|
||||
]
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
let tunnel: TunnelContainer
|
||||
var tunnelViewModel: TunnelViewModel
|
||||
var onDemandViewModel: ActivateOnDemandViewModel
|
||||
|
||||
private var sections = [Section]()
|
||||
private var interfaceFieldIsVisible = [Bool]()
|
||||
private var peerFieldIsVisible = [[Bool]]()
|
||||
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var onDemandObservationToken: AnyObject?
|
||||
private var reloadRuntimeConfigurationTimer: Timer?
|
||||
|
||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
self.tunnel = tunnel
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||
super.init(style: .grouped)
|
||||
loadSections()
|
||||
loadVisibleFields()
|
||||
@ -51,6 +58,11 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||
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) {
|
||||
@ -64,10 +76,10 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||
|
||||
tableView.estimatedRowHeight = 44
|
||||
tableView.rowHeight = UITableView.automaticDimension
|
||||
tableView.allowsSelection = false
|
||||
tableView.register(SwitchCell.self)
|
||||
tableView.register(KeyValueCell.self)
|
||||
tableView.register(ButtonCell.self)
|
||||
tableView.register(ChevronCell.self)
|
||||
|
||||
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() {
|
||||
reloadRuntimeConfiguration()
|
||||
reloadRuntimeConfigurationTimer?.invalidate()
|
||||
@ -242,11 +239,27 @@ class TunnelDetailTableViewController: UITableViewController {
|
||||
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 {
|
||||
func tunnelSaved(tunnel: TunnelContainer) {
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||
loadSections()
|
||||
loadVisibleFields()
|
||||
title = tunnel.name
|
||||
@ -272,7 +285,7 @@ extension TunnelDetailTableViewController {
|
||||
case .peer(let peerIndex, _):
|
||||
return peerFieldIsVisible[peerIndex].filter { $0 }.count
|
||||
case .onDemand:
|
||||
return 1
|
||||
return onDemandViewModel.isWiFiInterfaceEnabled ? 2 : 1
|
||||
case .delete:
|
||||
return 1
|
||||
}
|
||||
@ -379,13 +392,26 @@ extension TunnelDetailTableViewController {
|
||||
}
|
||||
|
||||
private func onDemandCell(for tableView: UITableView, at indexPath: IndexPath) -> UITableViewCell {
|
||||
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.key = tr("tunnelOnDemandKey")
|
||||
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
||||
cell.observationToken = tunnel.observe(\.isActivateOnDemandEnabled) { [weak cell] tunnel, _ in
|
||||
cell?.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
||||
let field = TunnelDetailTableViewController.onDemandFields[indexPath.row]
|
||||
if field == .onDemand {
|
||||
let cell: KeyValueCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.key = field.localizedUIString
|
||||
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 {
|
||||
@ -394,7 +420,9 @@ extension TunnelDetailTableViewController {
|
||||
cell.hasDestructiveAction = true
|
||||
cell.onTapped = { [weak self] in
|
||||
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 }
|
||||
self.tunnelsManager.remove(tunnel: self.tunnel) { error in
|
||||
if error != nil {
|
||||
@ -408,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
|
||||
]
|
||||
|
||||
let activateOnDemandOptions: [ActivateOnDemandOption] = [
|
||||
.useOnDemandOverWiFiOrCellular,
|
||||
.useOnDemandOverWiFiOnly,
|
||||
.useOnDemandOverCellularOnly
|
||||
let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||
.nonWiFiInterface,
|
||||
.wiFiInterface,
|
||||
.ssid
|
||||
]
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
let tunnel: TunnelContainer?
|
||||
let tunnelViewModel: TunnelViewModel
|
||||
var activateOnDemandSetting: ActivateOnDemandSetting
|
||||
var onDemandViewModel: ActivateOnDemandViewModel
|
||||
private var sections = [Section]()
|
||||
|
||||
// Use this initializer to edit an existing tunnel.
|
||||
@ -60,7 +60,7 @@ class TunnelEditTableViewController: UITableViewController {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
self.tunnel = tunnel
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||
activateOnDemandSetting = tunnel.activateOnDemandSetting
|
||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||
super.init(style: .grouped)
|
||||
loadSections()
|
||||
}
|
||||
@ -70,7 +70,7 @@ class TunnelEditTableViewController: UITableViewController {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
tunnel = nil
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: nil)
|
||||
activateOnDemandSetting = ActivateOnDemandSetting.defaultSetting
|
||||
onDemandViewModel = ActivateOnDemandViewModel()
|
||||
super.init(style: .grouped)
|
||||
loadSections()
|
||||
}
|
||||
@ -92,7 +92,7 @@ class TunnelEditTableViewController: UITableViewController {
|
||||
tableView.register(TunnelEditEditableKeyValueCell.self)
|
||||
tableView.register(ButtonCell.self)
|
||||
tableView.register(SwitchCell.self)
|
||||
tableView.register(CheckmarkCell.self)
|
||||
tableView.register(ChevronCell.self)
|
||||
}
|
||||
|
||||
private func loadSections() {
|
||||
@ -113,9 +113,10 @@ class TunnelEditTableViewController: UITableViewController {
|
||||
ErrorPresenter.showErrorAlert(title: alertTitle, message: errorMessage, from: self)
|
||||
tableView.reloadData() // Highlight erroring fields
|
||||
case .saved(let tunnelConfiguration):
|
||||
let onDemandOption = onDemandViewModel.toOnDemandOption()
|
||||
if let tunnel = 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 {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
} else {
|
||||
@ -125,7 +126,7 @@ class TunnelEditTableViewController: UITableViewController {
|
||||
}
|
||||
} else {
|
||||
// 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 {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
} else {
|
||||
@ -161,10 +162,10 @@ extension TunnelEditTableViewController {
|
||||
case .addPeer:
|
||||
return 1
|
||||
case .onDemand:
|
||||
if activateOnDemandSetting.isActivateOnDemandEnabled {
|
||||
return 4
|
||||
if onDemandViewModel.isWiFiInterfaceEnabled {
|
||||
return 3
|
||||
} else {
|
||||
return 1
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,6 +251,8 @@ extension TunnelEditTableViewController {
|
||||
cell.keyboardType = .numberPad
|
||||
case .publicKey, .generateKeyPair:
|
||||
cell.keyboardType = .default
|
||||
case .status, .toggleStatus:
|
||||
fatalError("Unexpected interface field")
|
||||
}
|
||||
|
||||
cell.isValueValid = (!tunnelViewModel.interfaceData.fieldsWithError.contains(field))
|
||||
@ -259,8 +262,18 @@ extension TunnelEditTableViewController {
|
||||
cell.onValueBeingEdited = { [weak self] value in
|
||||
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 {
|
||||
cell.onValueChanged = { [weak self] value in
|
||||
cell.onValueChanged = { [weak self] _, value in
|
||||
self?.tunnelViewModel.interfaceData[field] = value
|
||||
}
|
||||
}
|
||||
@ -298,7 +311,9 @@ extension TunnelEditTableViewController {
|
||||
cell.hasDestructiveAction = true
|
||||
cell.onTapped = { [weak self, weak peerData] in
|
||||
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 }
|
||||
let removedSectionIndices = self.deletePeer(peer: peerData)
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
cell.onValueChanged = { [weak peerData] value in
|
||||
cell.onValueChanged = { [weak peerData] _, value in
|
||||
peerData?[field] = value
|
||||
}
|
||||
}
|
||||
@ -409,36 +424,29 @@ extension TunnelEditTableViewController {
|
||||
}
|
||||
|
||||
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)
|
||||
cell.message = tr("tunnelOnDemandKey")
|
||||
cell.isOn = activateOnDemandSetting.isActivateOnDemandEnabled
|
||||
cell.message = field.localizedUIString
|
||||
cell.isOn = onDemandViewModel.isEnabled(field: field)
|
||||
cell.onSwitchToggled = { [weak self] isOn in
|
||||
guard let self = self else { return }
|
||||
guard isOn != self.activateOnDemandSetting.isActivateOnDemandEnabled else { return }
|
||||
|
||||
self.activateOnDemandSetting.isActivateOnDemandEnabled = isOn
|
||||
self.loadSections()
|
||||
|
||||
self.onDemandViewModel.setEnabled(field: field, isEnabled: isOn)
|
||||
let section = self.sections.firstIndex { $0 == .onDemand }!
|
||||
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: section) }
|
||||
if isOn {
|
||||
if self.activateOnDemandSetting.activateOnDemandOption == .none {
|
||||
self.activateOnDemandSetting.activateOnDemandOption = TunnelViewModel.defaultActivateOnDemandOption()
|
||||
let indexPath = IndexPath(row: 2, section: section)
|
||||
if field == .wiFiInterface {
|
||||
if isOn {
|
||||
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
|
||||
} else {
|
||||
let cell: CheckmarkCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
let rowOption = activateOnDemandOptions[indexPath.row - 1]
|
||||
let selectedOption = activateOnDemandSetting.activateOnDemandOption
|
||||
assert(selectedOption != .none)
|
||||
cell.message = TunnelViewModel.activateOnDemandOptionText(for: rowOption)
|
||||
cell.isChecked = selectedOption == rowOption
|
||||
let cell: ChevronCell = tableView.dequeueReusableCell(for: indexPath)
|
||||
cell.message = field.localizedUIString
|
||||
cell.detailMessage = onDemandViewModel.localizedSSIDDescription
|
||||
return cell
|
||||
}
|
||||
}
|
||||
@ -455,26 +463,11 @@ extension TunnelEditTableViewController {
|
||||
loadSections()
|
||||
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 {
|
||||
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
|
||||
} else {
|
||||
return nil
|
||||
@ -484,16 +477,27 @@ extension TunnelEditTableViewController {
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
switch sections[indexPath.section] {
|
||||
case .onDemand:
|
||||
let option = activateOnDemandOptions[indexPath.row - 1]
|
||||
assert(option != .none)
|
||||
activateOnDemandSetting.activateOnDemandOption = option
|
||||
|
||||
let indexPaths = (1 ..< 4).map { IndexPath(row: $0, section: indexPath.section) }
|
||||
UIView.performWithoutAnimation {
|
||||
tableView.reloadRows(at: indexPaths, with: .none)
|
||||
}
|
||||
assert(indexPath.row == 2)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
let ssidOptionVC = SSIDOptionEditTableViewController(option: onDemandViewModel.ssidOption, ssids: onDemandViewModel.selectedSSIDs)
|
||||
ssidOptionVC.delegate = self
|
||||
navigationController?.pushViewController(ssidOptionVC, animated: true)
|
||||
default:
|
||||
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?
|
||||
|
||||
enum TableState: Equatable {
|
||||
case normal
|
||||
case rowSwiped
|
||||
case multiSelect(selectionCount: Int)
|
||||
}
|
||||
|
||||
let tableView: UITableView = {
|
||||
let tableView = UITableView(frame: CGRect.zero, style: .plain)
|
||||
tableView.estimatedRowHeight = 60
|
||||
@ -32,6 +38,11 @@ class TunnelsListTableViewController: UIViewController {
|
||||
}()
|
||||
|
||||
var detailDisplayedTunnel: TunnelContainer?
|
||||
var tableState: TableState = .normal {
|
||||
didSet {
|
||||
handleTableStateChange()
|
||||
}
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
view = UIView()
|
||||
@ -74,13 +85,39 @@ class TunnelsListTableViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = tr("tunnelsListTitle")
|
||||
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:)))
|
||||
|
||||
tableState = .normal
|
||||
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) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
tunnelsManager.tunnelsListDelegate = self
|
||||
@ -159,14 +196,67 @@ class TunnelsListTableViewController: UIViewController {
|
||||
scanQRCodeNC.modalPresentationStyle = .fullScreen
|
||||
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 {
|
||||
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
|
||||
guard let tunnelsManager = tunnelsManager else { return }
|
||||
urls.forEach { url in
|
||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
|
||||
}
|
||||
TunnelImporter.importFromFile(urls: urls, into: tunnelsManager, sourceVC: self, errorPresenterType: ErrorPresenter.self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,6 +302,10 @@ extension TunnelsListTableViewController: UITableViewDataSource {
|
||||
|
||||
extension TunnelsListTableViewController: UITableViewDelegate {
|
||||
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 }
|
||||
let tunnel = tunnelsManager.tunnel(at: indexPath.row)
|
||||
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager,
|
||||
@ -222,6 +316,13 @@ extension TunnelsListTableViewController: UITableViewDelegate {
|
||||
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,
|
||||
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
|
||||
let deleteAction = UIContextualAction(style: .destructive, title: tr("tunnelsListSwipeDeleteButtonTitle")) { [weak self] _, _, completionHandler in
|
||||
@ -238,6 +339,18 @@ extension TunnelsListTableViewController: UITableViewDelegate {
|
||||
}
|
||||
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 {
|
||||
|
@ -6,6 +6,8 @@
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.networking.wifi-info</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.$(APP_ID_IOS)</string>
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
import ServiceManagement
|
||||
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
@ -12,10 +13,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
var manageTunnelsRootVC: ManageTunnelsRootViewController?
|
||||
var manageTunnelsWindowObject: NSWindow?
|
||||
var isTerminationAlertShown = false
|
||||
|
||||
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
|
||||
guard let self = self else { return }
|
||||
@ -42,25 +43,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
|
||||
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
|
||||
return .terminateNow
|
||||
@objc func quit() {
|
||||
if let manageWindow = manageTunnelsWindowObject, manageWindow.attachedSheet != nil {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
manageWindow.orderFront(self)
|
||||
return
|
||||
}
|
||||
if isTerminationAlertShown {
|
||||
return .terminateNow
|
||||
registerLoginItem(shouldLaunchAtLogin: false)
|
||||
guard let currentTunnel = tunnelsTracker?.currentTunnel, currentTunnel.status == .active || currentTunnel.status == .activating else {
|
||||
NSApp.terminate(nil)
|
||||
return
|
||||
}
|
||||
let alert = NSAlert()
|
||||
alert.messageText = tr("macAppExitingWithActiveTunnelMessage")
|
||||
alert.informativeText = tr("macAppExitingWithActiveTunnelInfo")
|
||||
if let window = manageTunnelsWindowObject {
|
||||
alert.beginSheetModal(for: window) { [weak self] _ in
|
||||
self?.isTerminationAlertShown = true
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
if let manageWindow = manageTunnelsWindowObject {
|
||||
manageWindow.orderFront(self)
|
||||
alert.beginSheetModal(for: manageWindow) { _ in
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
return .terminateCancel
|
||||
} else {
|
||||
alert.runModal()
|
||||
return .terminateNow
|
||||
NSApp.terminate(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -79,3 +84,10 @@ extension AppDelegate: StatusMenuWindowDelegate {
|
||||
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)
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class AppStorePrivacyNotice {
|
||||
// The App Store Review Board does not comprehend the fact that this application
|
||||
// is not a service and does not have any servers of its own. They therefore require
|
||||
// us to give a notice regarding collection of user data using our non-existent
|
||||
// servers. This demand is obviously impossible to fulfill, since it doesn't make sense,
|
||||
// but we do our best here to show something in that category.
|
||||
static func show(from sourceVC: NSViewController?, into tunnelsManager: TunnelsManager, _ callback: @escaping () -> Void) {
|
||||
if tunnelsManager.numberOfTunnels() > 0 {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
let alert = NSAlert()
|
||||
|
||||
alert.messageText = tr("macPrivacyNoticeMessage")
|
||||
alert.informativeText = tr("macPrivacyNoticeInfo")
|
||||
alert.alertStyle = NSAlert.Style.warning
|
||||
if let window = sourceVC?.view.window {
|
||||
alert.beginSheetModal(for: window) { _ in callback() }
|
||||
} else {
|
||||
alert.runModal()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ class Application: NSApplication {
|
||||
"Z": #selector(UndoActionRespondable.redo(_:)),
|
||||
"w": #selector(NSWindow.performClose(_:)),
|
||||
"m": #selector(NSWindow.performMiniaturize(_:)),
|
||||
"q": #selector(NSApplication.terminate(_:))
|
||||
"q": #selector(AppDelegate.quit)
|
||||
]
|
||||
|
||||
private var appDelegate: AppDelegate? //swiftlint:disable:this weak_delegate
|
||||
|
@ -9,13 +9,11 @@ class ImportPanelPresenter {
|
||||
let openPanel = NSOpenPanel()
|
||||
openPanel.prompt = tr("macSheetButtonImport")
|
||||
openPanel.allowedFileTypes = ["conf", "zip"]
|
||||
openPanel.allowsMultipleSelection = true
|
||||
openPanel.beginSheetModal(for: window) { [weak tunnelsManager] response in
|
||||
guard let tunnelsManager = tunnelsManager else { return }
|
||||
guard response == .OK else { return }
|
||||
guard let url = openPanel.url else { return }
|
||||
AppStorePrivacyNotice.show(from: sourceVC, into: tunnelsManager) {
|
||||
TunnelImporter.importFromFile(url: url, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
||||
}
|
||||
TunnelImporter.importFromFile(urls: openPanel.urls, into: tunnelsManager, sourceVC: sourceVC, errorPresenterType: ErrorPresenter.self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,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>LSMultipleInstancesProhibited</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
|
34
WireGuard/WireGuard/UI/macOS/LoginItemHelper/Info.plist
Normal file
34
WireGuard/WireGuard/UI/macOS/LoginItemHelper/Info.plist
Normal file
@ -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>
|
16
WireGuard/WireGuard/UI/macOS/LoginItemHelper/main.m
Normal file
16
WireGuard/WireGuard/UI/macOS/LoginItemHelper/main.m
Normal file
@ -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;
|
||||
}
|
@ -13,6 +13,7 @@ class StatusMenu: NSMenu {
|
||||
|
||||
var statusMenuItem: NSMenuItem?
|
||||
var networksMenuItem: NSMenuItem?
|
||||
var deactivateMenuItem: NSMenuItem?
|
||||
var firstTunnelMenuItemIndex = 0
|
||||
var numberOfTunnelMenuItems = 0
|
||||
|
||||
@ -53,16 +54,22 @@ class StatusMenu: NSMenu {
|
||||
networksMenuItem.isEnabled = false
|
||||
networksMenuItem.isHidden = true
|
||||
addItem(networksMenuItem)
|
||||
let deactivateMenuItem = NSMenuItem(title: tr("macToggleStatusButtonDeactivate"), action: #selector(deactivateClicked), keyEquivalent: "")
|
||||
deactivateMenuItem.target = self
|
||||
deactivateMenuItem.isHidden = true
|
||||
addItem(deactivateMenuItem)
|
||||
self.statusMenuItem = statusMenuItem
|
||||
self.networksMenuItem = networksMenuItem
|
||||
self.deactivateMenuItem = deactivateMenuItem
|
||||
}
|
||||
|
||||
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 {
|
||||
statusMenuItem.title = tr(format: "macStatus (%@)", tr("tunnelStatusInactive"))
|
||||
networksMenuItem.title = ""
|
||||
networksMenuItem.isHidden = true
|
||||
deactivateMenuItem.isHidden = true
|
||||
return
|
||||
}
|
||||
var statusText: String
|
||||
@ -98,6 +105,7 @@ class StatusMenu: NSMenu {
|
||||
}
|
||||
networksMenuItem.isHidden = false
|
||||
}
|
||||
deactivateMenuItem.isHidden = tunnel.status != .active
|
||||
}
|
||||
|
||||
func addTunnelMenuItems() -> Bool {
|
||||
@ -122,11 +130,17 @@ class StatusMenu: NSMenu {
|
||||
let aboutItem = NSMenuItem(title: tr("macMenuAbout"), action: #selector(aboutClicked), keyEquivalent: "")
|
||||
aboutItem.target = self
|
||||
addItem(aboutItem)
|
||||
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(NSApplication.terminate), keyEquivalent: "")
|
||||
quitItem.target = NSApp
|
||||
let quitItem = NSMenuItem(title: tr("macMenuQuit"), action: #selector(AppDelegate.quit), keyEquivalent: "")
|
||||
quitItem.target = NSApp.delegate
|
||||
addItem(quitItem)
|
||||
}
|
||||
|
||||
@objc func deactivateClicked() {
|
||||
if let currentTunnel = currentTunnel {
|
||||
tunnelsManager.startDeactivation(of: currentTunnel)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tunnelClicked(sender: AnyObject) {
|
||||
guard let tunnelMenuItem = sender as? TunnelMenuItem else { return }
|
||||
if tunnelMenuItem.state == .off {
|
||||
@ -170,6 +184,7 @@ extension StatusMenu {
|
||||
func insertTunnelMenuItem(for tunnel: TunnelContainer, at tunnelIndex: Int) {
|
||||
let menuItem = TunnelMenuItem(tunnel: tunnel, action: #selector(tunnelClicked(sender:)))
|
||||
menuItem.target = self
|
||||
menuItem.isHidden = !tunnel.isTunnelConfigurationAvailableInKeychain
|
||||
insertItem(menuItem, at: firstTunnelMenuItemIndex + tunnelIndex)
|
||||
if numberOfTunnelMenuItems == 0 {
|
||||
insertItem(NSMenuItem.separator(), at: firstTunnelMenuItemIndex + tunnelIndex + 1)
|
||||
|
67
WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift
Normal file
67
WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift
Normal file
@ -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
|
||||
|
||||
class ConfTextStorage: NSTextStorage {
|
||||
|
||||
let defaultFont = NSFontManager.shared.convertWeight(true, of: NSFont.systemFont(ofSize: fontSize))
|
||||
private let boldFont = NSFont.boldSystemFont(ofSize: fontSize)
|
||||
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 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() {
|
||||
backingStore = NSMutableAttributedString(string: "")
|
||||
super.init()
|
||||
@ -81,6 +85,55 @@ class ConfTextStorage: NSTextStorage {
|
||||
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() {
|
||||
guard let textColorTheme = textColorTheme else { return }
|
||||
hasError = false
|
||||
@ -94,8 +147,10 @@ class ConfTextStorage: NSTextStorage {
|
||||
.font: defaultFont
|
||||
]
|
||||
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 {
|
||||
let span = spans.pointee
|
||||
|
||||
@ -115,6 +170,7 @@ class ConfTextStorage: NSTextStorage {
|
||||
spans = spans.successor()
|
||||
}
|
||||
backingStore.endEditing()
|
||||
free(spansStart)
|
||||
|
||||
beginEditing()
|
||||
edited(.editedAttributes, range: fullTextRange, changeInLength: 0)
|
||||
|
@ -9,10 +9,12 @@ class ConfTextView: NSTextView {
|
||||
|
||||
@objc dynamic var hasError: Bool = false
|
||||
@objc dynamic var privateKeyString: String?
|
||||
@objc dynamic var singlePeerAllowedIPs: [String]?
|
||||
|
||||
override var string: String {
|
||||
didSet {
|
||||
confTextStorage.highlightSyntax()
|
||||
updateConfigData()
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,18 +54,35 @@ class ConfTextView: NSTextView {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension ConfTextView: NSTextViewDelegate {
|
||||
|
||||
func textDidChange(_ notification: Notification) {
|
||||
confTextStorage.highlightSyntax()
|
||||
private func updateConfigData() {
|
||||
if hasError != confTextStorage.hasError {
|
||||
hasError = confTextStorage.hasError
|
||||
}
|
||||
if 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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}()
|
||||
|
||||
let valueImageView: NSImageView?
|
||||
|
||||
var key: String {
|
||||
get { return keyLabel.stringValue }
|
||||
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 {
|
||||
let height = max(keyLabel.intrinsicContentSize.height, valueLabel.intrinsicContentSize.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)
|
||||
|
||||
addSubview(keyLabel)
|
||||
@ -60,10 +73,24 @@ class EditableKeyValueRow: NSView {
|
||||
keyLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
keyLabel.firstBaselineAnchor.constraint(equalTo: valueLabel.firstBaselineAnchor),
|
||||
self.leadingAnchor.constraint(equalTo: keyLabel.leadingAnchor),
|
||||
keyLabel.trailingAnchor.constraint(equalTo: valueLabel.leadingAnchor, constant: -5),
|
||||
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.setContentHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
valueLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
@ -81,12 +108,26 @@ class EditableKeyValueRow: NSView {
|
||||
key = ""
|
||||
value = ""
|
||||
isKeyInBold = false
|
||||
observationToken = nil
|
||||
}
|
||||
}
|
||||
|
||||
class KeyValueRow: EditableKeyValueRow {
|
||||
override init() {
|
||||
super.init()
|
||||
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.isBordered = false
|
||||
valueLabel.backgroundColor = .clear
|
||||
|
52
WireGuard/WireGuard/UI/macOS/View/LogViewCell.swift
Normal file
52
WireGuard/WireGuard/UI/macOS/View/LogViewCell.swift
Normal file
@ -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")
|
||||
}
|
||||
}
|
171
WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift
Normal file
171
WireGuard/WireGuard/UI/macOS/View/OnDemandWiFiControls.swift
Normal file
@ -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.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||
tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing),
|
||||
tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||||
tunnelsListView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.3)
|
||||
tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
@ -78,14 +77,40 @@ class ManageTunnelsRootViewController: NSViewController {
|
||||
}
|
||||
|
||||
extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate {
|
||||
func tunnelSelected(tunnel: TunnelContainer) {
|
||||
let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel)
|
||||
setTunnelDetailContentVC(tunnelDetailVC)
|
||||
self.tunnelDetailVC = tunnelDetailVC
|
||||
func tunnelsSelected(tunnelIndices: [Int]) {
|
||||
assert(!tunnelIndices.isEmpty)
|
||||
if tunnelIndices.count == 1 {
|
||||
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() {
|
||||
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)
|
||||
self.tunnelDetailVC = nil
|
||||
}
|
||||
|
@ -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 peerFieldRow(peer: TunnelViewModel.PeerData, field: TunnelViewModel.PeerField)
|
||||
case onDemandRow
|
||||
case onDemandSSIDRow
|
||||
case spacerRow
|
||||
|
||||
func localizedSectionKeyString() -> String {
|
||||
switch self {
|
||||
case .interfaceFieldRow: return tr("tunnelSectionTitleInterface")
|
||||
case .peerFieldRow: return tr("tunnelSectionTitlePeer")
|
||||
case .onDemandRow: return ""
|
||||
case .onDemandRow: return tr("macFieldOnDemand")
|
||||
case .onDemandSSIDRow: return ""
|
||||
case .spacerRow: return ""
|
||||
}
|
||||
}
|
||||
@ -25,14 +27,15 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
case .interfaceFieldRow(let field): return field == .name
|
||||
case .peerFieldRow(_, let field): return field == .publicKey
|
||||
case .onDemandRow: return true
|
||||
case .onDemandSSIDRow: return false
|
||||
case .spacerRow: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static let interfaceFields: [TunnelViewModel.InterfaceField] = [
|
||||
.name, .publicKey, .addresses,
|
||||
.listenPort, .mtu, .dns
|
||||
.name, .status, .publicKey, .addresses,
|
||||
.listenPort, .mtu, .dns, .toggleStatus
|
||||
]
|
||||
|
||||
static let peerFields: [TunnelViewModel.PeerField] = [
|
||||
@ -41,6 +44,10 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
.rxBytes, .txBytes, .lastHandshakeTime
|
||||
]
|
||||
|
||||
static let onDemandFields: [ActivateOnDemandViewModel.OnDemandField] = [
|
||||
.onDemand, .ssid
|
||||
]
|
||||
|
||||
let tableView: NSTableView = {
|
||||
let tableView = NSTableView()
|
||||
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelDetail")))
|
||||
@ -51,21 +58,12 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
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 button = NSButton()
|
||||
button.title = tr("Edit")
|
||||
button.setButtonType(.momentaryPushIn)
|
||||
button.bezelStyle = .rounded
|
||||
button.toolTip = "Edit tunnel (⌘E)"
|
||||
button.toolTip = tr("macToolTipEditTunnel")
|
||||
return button
|
||||
}()
|
||||
|
||||
@ -85,6 +83,9 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
updateTableViewModelRows()
|
||||
}
|
||||
}
|
||||
|
||||
var onDemandViewModel: ActivateOnDemandViewModel
|
||||
|
||||
private var tableViewModelRowsBySection = [[(isVisible: Bool, modelRow: TableViewModelRow)]]()
|
||||
private var tableViewModelRows = [TableViewModelRow]()
|
||||
|
||||
@ -96,6 +97,7 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
self.tunnel = tunnel
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
updateTableViewModelRowsBySection()
|
||||
updateTableViewModelRows()
|
||||
@ -113,9 +115,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
|
||||
statusCheckbox.target = self
|
||||
statusCheckbox.action = #selector(statusCheckboxToggled(sender:))
|
||||
|
||||
editButton.target = self
|
||||
editButton.action = #selector(handleEditTunnelAction)
|
||||
|
||||
@ -133,11 +132,9 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
containerView.addLayoutGuide(bottomControlsContainer)
|
||||
containerView.addSubview(box)
|
||||
containerView.addSubview(scrollView)
|
||||
containerView.addSubview(statusCheckbox)
|
||||
containerView.addSubview(editButton)
|
||||
box.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
statusCheckbox.translatesAutoresizingMaskIntoConstraints = false
|
||||
editButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
@ -149,10 +146,8 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32),
|
||||
scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor),
|
||||
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),
|
||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 4)
|
||||
bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0)
|
||||
])
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
@ -162,6 +157,11 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
scrollView.trailingAnchor.constraint(equalTo: box.trailingAnchor)
|
||||
])
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 320),
|
||||
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||
])
|
||||
|
||||
view = containerView
|
||||
}
|
||||
|
||||
@ -170,7 +170,9 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
|
||||
var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
||||
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))
|
||||
modelRowsBySection.append(interfaceSection)
|
||||
@ -186,6 +188,9 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
|
||||
var onDemandSection = [(isVisible: Bool, modelRow: TableViewModelRow)]()
|
||||
onDemandSection.append((isVisible: true, modelRow: .onDemandRow))
|
||||
if onDemandViewModel.isWiFiInterfaceEnabled {
|
||||
onDemandSection.append((isVisible: true, modelRow: .onDemandSSIDRow))
|
||||
}
|
||||
modelRowsBySection.append(onDemandSection)
|
||||
|
||||
tableViewModelRowsBySection = modelRowsBySection
|
||||
@ -196,28 +201,6 @@ class TunnelDetailTableViewController: NSViewController {
|
||||
}
|
||||
|
||||
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 {
|
||||
startUpdatingRuntimeConfiguration()
|
||||
} 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() {
|
||||
super.viewWillDisappear()
|
||||
if let tunnelEditVC = tunnelEditVC {
|
||||
@ -393,12 +367,18 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||
let modelRow = tableViewModelRows[row]
|
||||
switch modelRow {
|
||||
case .interfaceFieldRow(let field):
|
||||
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
|
||||
if field == .status {
|
||||
return statusCell()
|
||||
} else if field == .toggleStatus {
|
||||
return toggleStatusCell()
|
||||
} else {
|
||||
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):
|
||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||
let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString
|
||||
@ -416,10 +396,103 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||
return NSView()
|
||||
case .onDemandRow:
|
||||
let cell: KeyValueRow = tableView.dequeueReusableCell()
|
||||
cell.key = tr("macFieldOnDemand")
|
||||
cell.value = TunnelViewModel.activateOnDemandDetailText(for: tunnel.activateOnDemandSetting)
|
||||
cell.key = modelRow.localizedSectionKeyString()
|
||||
cell.value = onDemandViewModel.localizedInterfaceDescription
|
||||
cell.isKeyInBold = true
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,6 +500,7 @@ extension TunnelDetailTableViewController: NSTableViewDelegate {
|
||||
extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {
|
||||
func tunnelSaved(tunnel: TunnelContainer) {
|
||||
tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnel.tunnelConfiguration)
|
||||
onDemandViewModel = ActivateOnDemandViewModel(tunnel: tunnel)
|
||||
updateTableViewModelRowsBySection()
|
||||
updateTableViewModelRows()
|
||||
updateStatus()
|
||||
|
@ -42,11 +42,7 @@ class TunnelEditViewController: NSViewController {
|
||||
return textView
|
||||
}()
|
||||
|
||||
let onDemandRow: PopupRow = {
|
||||
let popupRow = PopupRow()
|
||||
popupRow.key = tr("macFieldOnDemand")
|
||||
return popupRow
|
||||
}()
|
||||
let onDemandControlsRow = OnDemandControlsRow()
|
||||
|
||||
let scrollView: NSScrollView = {
|
||||
let scrollView = NSScrollView()
|
||||
@ -56,6 +52,14 @@ class TunnelEditViewController: NSViewController {
|
||||
return scrollView
|
||||
}()
|
||||
|
||||
let excludePrivateIPsCheckbox: NSButton = {
|
||||
let checkbox = NSButton()
|
||||
checkbox.title = tr("tunnelPeerExcludePrivateIPs")
|
||||
checkbox.setButtonType(.switch)
|
||||
checkbox.state = .off
|
||||
return checkbox
|
||||
}()
|
||||
|
||||
let discardButton: NSButton = {
|
||||
let button = NSButton()
|
||||
button.title = tr("macEditDiscard")
|
||||
@ -72,24 +76,22 @@ class TunnelEditViewController: NSViewController {
|
||||
return button
|
||||
}()
|
||||
|
||||
let activateOnDemandOptions: [ActivateOnDemandOption] = [
|
||||
.none,
|
||||
.useOnDemandOverWiFiOrEthernet,
|
||||
.useOnDemandOverWiFiOnly,
|
||||
.useOnDemandOverEthernetOnly
|
||||
]
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
let tunnel: TunnelContainer?
|
||||
var onDemandViewModel: ActivateOnDemandViewModel
|
||||
|
||||
weak var delegate: TunnelEditViewControllerDelegate?
|
||||
|
||||
var privateKeyObservationToken: AnyObject?
|
||||
var hasErrorObservationToken: AnyObject?
|
||||
var singlePeerAllowedIPsObservationToken: AnyObject?
|
||||
|
||||
var dnsServersAddedToAllowedIPs: String?
|
||||
|
||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
self.tunnel = tunnel
|
||||
self.onDemandViewModel = tunnel != nil ? ActivateOnDemandViewModel(tunnel: tunnel!) : ActivateOnDemandViewModel()
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@ -97,8 +99,7 @@ class TunnelEditViewController: NSViewController {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func populateTextFields() {
|
||||
let selectedActivateOnDemandOption: ActivateOnDemandOption
|
||||
func populateFields() {
|
||||
if let tunnel = tunnel {
|
||||
// Editing an existing tunnel
|
||||
let tunnelConfiguration = tunnel.tunnelConfiguration!
|
||||
@ -106,11 +107,9 @@ class TunnelEditViewController: NSViewController {
|
||||
textView.string = tunnelConfiguration.asWgQuickConfig()
|
||||
publicKeyRow.value = tunnelConfiguration.interface.publicKey.base64Key() ?? ""
|
||||
textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key() ?? ""
|
||||
if tunnel.activateOnDemandSetting.isActivateOnDemandEnabled {
|
||||
selectedActivateOnDemandOption = tunnel.activateOnDemandSetting.activateOnDemandOption
|
||||
} else {
|
||||
selectedActivateOnDemandOption = .none
|
||||
}
|
||||
let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil
|
||||
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation })
|
||||
dnsServersAddedToAllowedIPs = excludePrivateIPsCheckbox.state == .on ? tunnelConfiguration.interface.dns.map { $0.stringRepresentation }.joined(separator: ", ") : nil
|
||||
} else {
|
||||
// Creating a new tunnel
|
||||
let privateKey = Curve25519.generatePrivateKey()
|
||||
@ -118,7 +117,8 @@ class TunnelEditViewController: NSViewController {
|
||||
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key() ?? "")\n"
|
||||
publicKeyRow.value = publicKey.base64Key() ?? ""
|
||||
textView.string = bootstrappingText
|
||||
selectedActivateOnDemandOption = .none
|
||||
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: nil)
|
||||
dnsServersAddedToAllowedIPs = nil
|
||||
}
|
||||
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
|
||||
if let privateKeyString = textView.privateKeyString,
|
||||
@ -133,13 +133,13 @@ class TunnelEditViewController: NSViewController {
|
||||
hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in
|
||||
saveButton?.isEnabled = !textView.hasError
|
||||
}
|
||||
|
||||
onDemandRow.valueOptions = activateOnDemandOptions.map { TunnelViewModel.activateOnDemandOptionText(for: $0) }
|
||||
onDemandRow.selectedOptionIndex = activateOnDemandOptions.firstIndex(of: selectedActivateOnDemandOption)!
|
||||
singlePeerAllowedIPsObservationToken = textView.observe(\.singlePeerAllowedIPs) { [weak self] textView, _ in
|
||||
self?.updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: textView.singlePeerAllowedIPs)
|
||||
}
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
populateTextFields()
|
||||
populateFields()
|
||||
|
||||
scrollView.documentView = textView
|
||||
|
||||
@ -149,16 +149,22 @@ class TunnelEditViewController: NSViewController {
|
||||
discardButton.target = self
|
||||
discardButton.action = #selector(handleDiscardAction)
|
||||
|
||||
excludePrivateIPsCheckbox.target = self
|
||||
excludePrivateIPsCheckbox.action = #selector(excludePrivateIPsCheckboxToggled(sender:))
|
||||
|
||||
onDemandControlsRow.onDemandViewModel = onDemandViewModel
|
||||
|
||||
let margin: CGFloat = 20
|
||||
let internalSpacing: CGFloat = 10
|
||||
|
||||
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandRow, scrollView])
|
||||
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandControlsRow, scrollView])
|
||||
editorStackView.orientation = .vertical
|
||||
editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
editorStackView.spacing = internalSpacing
|
||||
|
||||
let buttonRowStackView = NSStackView()
|
||||
buttonRowStackView.setViews([discardButton, saveButton], in: .trailing)
|
||||
buttonRowStackView.addView(excludePrivateIPsCheckbox, in: .leading)
|
||||
buttonRowStackView.orientation = .horizontal
|
||||
buttonRowStackView.spacing = internalSpacing
|
||||
|
||||
@ -177,19 +183,22 @@ class TunnelEditViewController: NSViewController {
|
||||
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() {
|
||||
let name = nameRow.value
|
||||
guard !name.isEmpty else {
|
||||
ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self)
|
||||
return
|
||||
}
|
||||
let onDemandSetting: ActivateOnDemandSetting
|
||||
let onDemandOption = activateOnDemandOptions[onDemandRow.selectedOptionIndex]
|
||||
if onDemandOption == .none {
|
||||
onDemandSetting = ActivateOnDemandSetting.defaultSetting
|
||||
} else {
|
||||
onDemandSetting = ActivateOnDemandSetting(isActivateOnDemandEnabled: true, activateOnDemandOption: onDemandOption)
|
||||
}
|
||||
|
||||
onDemandControlsRow.saveToViewModel()
|
||||
let onDemandOption = onDemandViewModel.toOnDemandOption()
|
||||
|
||||
let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name)
|
||||
guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else {
|
||||
@ -197,7 +206,7 @@ class TunnelEditViewController: NSViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let tunnelConfiguration: TunnelConfiguration
|
||||
var tunnelConfiguration: TunnelConfiguration
|
||||
do {
|
||||
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value)
|
||||
} catch let error as WireGuardAppError {
|
||||
@ -207,9 +216,26 @@ class TunnelEditViewController: NSViewController {
|
||||
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 {
|
||||
// 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 {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
@ -219,16 +245,15 @@ class TunnelEditViewController: NSViewController {
|
||||
}
|
||||
} else {
|
||||
// We're creating a new tunnel
|
||||
AppStorePrivacyNotice.show(from: self, into: tunnelsManager) { [weak self] in
|
||||
self?.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, activateOnDemandSetting: onDemandSetting) { [weak self] result in
|
||||
if let error = result.error {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
}
|
||||
let tunnel: TunnelContainer = result.value!
|
||||
self?.dismiss(self)
|
||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
||||
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||
self?.setUserInteractionEnabled(true)
|
||||
if let error = result.error {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
}
|
||||
let tunnel: TunnelContainer = result.value!
|
||||
self?.dismiss(self)
|
||||
self?.delegate?.tunnelSaved(tunnel: tunnel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -237,4 +262,28 @@ class TunnelEditViewController: NSViewController {
|
||||
delegate?.tunnelEditingCancelled()
|
||||
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
|
||||
|
||||
protocol TunnelsListTableViewControllerDelegate: class {
|
||||
func tunnelSelected(tunnel: TunnelContainer)
|
||||
func tunnelsSelected(tunnelIndices: [Int])
|
||||
func tunnelsListEmpty()
|
||||
}
|
||||
|
||||
@ -12,41 +12,57 @@ class TunnelsListTableViewController: NSViewController {
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
weak var delegate: TunnelsListTableViewControllerDelegate?
|
||||
var isRemovingTunnels = false
|
||||
|
||||
let tableView: NSTableView = {
|
||||
let tableView = NSTableView()
|
||||
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
|
||||
tableView.headerView = nil
|
||||
tableView.rowSizeStyle = .medium
|
||||
tableView.allowsMultipleSelection = true
|
||||
return tableView
|
||||
}()
|
||||
|
||||
let buttonBar: NSSegmentedControl = {
|
||||
let addButtonImage = NSImage(named: NSImage.addTemplateName)!
|
||||
let removeButtonImage = NSImage(named: NSImage.removeTemplateName)!
|
||||
let actionButtonImage = NSImage(named: NSImage.actionTemplateName)!
|
||||
let buttonBar = NSSegmentedControl(images: [addButtonImage, removeButtonImage, actionButtonImage],
|
||||
trackingMode: .momentary, target: nil, action: #selector(buttonBarClicked(sender:)))
|
||||
buttonBar.segmentStyle = .smallSquare
|
||||
buttonBar.segmentDistribution = .fit
|
||||
buttonBar.setShowsMenuIndicator(true, forSegment: 0)
|
||||
buttonBar.setShowsMenuIndicator(false, forSegment: 1)
|
||||
buttonBar.setShowsMenuIndicator(true, forSegment: 2)
|
||||
return buttonBar
|
||||
let addButton: NSPopUpButton = {
|
||||
let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
|
||||
imageItem.image = NSImage(named: NSImage.addTemplateName)!
|
||||
|
||||
let menu = NSMenu()
|
||||
menu.addItem(imageItem)
|
||||
menu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
||||
menu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
|
||||
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
|
||||
}()
|
||||
|
||||
let addMenu: NSMenu = {
|
||||
let addMenu = NSMenu(title: "TunnelsListAdd")
|
||||
addMenu.addItem(withTitle: tr("macMenuAddEmptyTunnel"), action: #selector(handleAddEmptyTunnelAction), keyEquivalent: "n")
|
||||
addMenu.addItem(withTitle: tr("macMenuImportTunnels"), action: #selector(handleImportTunnelAction), keyEquivalent: "o")
|
||||
return addMenu
|
||||
let removeButton: NSButton = {
|
||||
let image = NSImage(named: NSImage.removeTemplateName)!
|
||||
let button = NSButton(image: image, target: self, action: #selector(handleRemoveTunnelAction))
|
||||
button.bezelStyle = .smallSquare
|
||||
button.imagePosition = .imageOnly
|
||||
return button
|
||||
}()
|
||||
|
||||
let actionMenu: NSMenu = {
|
||||
let actionMenu = NSMenu(title: "TunnelsListAction")
|
||||
actionMenu.addItem(withTitle: tr("macMenuExportLog"), action: #selector(handleExportLogAction), keyEquivalent: "")
|
||||
actionMenu.addItem(withTitle: tr("macMenuExportTunnels"), action: #selector(handleExportTunnelsAction), keyEquivalent: "")
|
||||
return actionMenu
|
||||
let actionButton: NSPopUpButton = {
|
||||
let imageItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
|
||||
imageItem.image = NSImage(named: NSImage.actionTemplateName)!
|
||||
|
||||
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) {
|
||||
@ -62,10 +78,13 @@ class TunnelsListTableViewController: NSViewController {
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
|
||||
tableView.doubleAction = #selector(listDoubleClicked(sender:))
|
||||
|
||||
let isSelected = selectTunnelInOperation() || selectTunnel(at: 0)
|
||||
if !isSelected {
|
||||
delegate?.tunnelsListEmpty()
|
||||
}
|
||||
tableView.allowsEmptySelection = false
|
||||
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.hasVerticalScroller = true
|
||||
@ -76,6 +95,16 @@ class TunnelsListTableViewController: NSViewController {
|
||||
clipView.documentView = tableView
|
||||
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 containerView = NSView()
|
||||
@ -100,13 +129,12 @@ class TunnelsListTableViewController: NSViewController {
|
||||
])
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 120),
|
||||
containerView.widthAnchor.constraint(equalToConstant: 180),
|
||||
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 120)
|
||||
])
|
||||
|
||||
buttonBar.target = self
|
||||
addMenu.items.forEach { $0.target = self }
|
||||
actionMenu.items.forEach { $0.target = self }
|
||||
addButton.menu?.items.forEach { $0.target = self }
|
||||
actionButton.menu?.items.forEach { $0.target = self }
|
||||
|
||||
view = containerView
|
||||
}
|
||||
@ -123,22 +151,6 @@ class TunnelsListTableViewController: NSViewController {
|
||||
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() {
|
||||
let tunnelEditVC = TunnelEditViewController(tunnelsManager: tunnelsManager, tunnel: nil)
|
||||
tunnelEditVC.delegate = self
|
||||
@ -151,59 +163,41 @@ class TunnelsListTableViewController: NSViewController {
|
||||
|
||||
@objc func handleRemoveTunnelAction() {
|
||||
guard let window = view.window else { return }
|
||||
let selectedTunnelIndex = tableView.selectedRow
|
||||
guard selectedTunnelIndex >= 0 && selectedTunnelIndex < tunnelsManager.numberOfTunnels() else { return }
|
||||
let selectedTunnel = tunnelsManager.tunnel(at: selectedTunnelIndex)
|
||||
let alert = NSAlert()
|
||||
alert.messageText = tr(format: "macDeleteTunnelConfirmationAlertMessage (%@)", selectedTunnel.name)
|
||||
|
||||
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted().filter { $0 >= 0 && $0 < tunnelsManager.numberOfTunnels() }
|
||||
guard !selectedTunnelIndices.isEmpty else { return }
|
||||
var nextSelection = selectedTunnelIndices.last! + 1
|
||||
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.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleDelete"))
|
||||
alert.addButton(withTitle: tr("macDeleteTunnelConfirmationAlertButtonTitleCancel"))
|
||||
alert.beginSheetModal(for: window) { [weak self] response in
|
||||
guard response == .alertFirstButtonReturn else { return }
|
||||
self?.buttonBar.setEnabled(false, forSegment: 1)
|
||||
self?.tunnelsManager.remove(tunnel: selectedTunnel) { [weak self] error in
|
||||
alert.onDeleteClicked = { [weak self] completion in
|
||||
guard let self = self else { return }
|
||||
self.selectTunnel(at: nextSelection)
|
||||
let selectedTunnels = selectedTunnelIndices.map { self.tunnelsManager.tunnel(at: $0) }
|
||||
self.tunnelsManager.removeMultiple(tunnels: selectedTunnels) { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
defer { self.buttonBar.setEnabled(true, forSegment: 1) }
|
||||
defer { completion() }
|
||||
if let error = error {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
alert.beginSheetModal(for: window)
|
||||
}
|
||||
|
||||
@objc func handleExportLogAction() {
|
||||
guard let window = view.window else { return }
|
||||
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"
|
||||
|
||||
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 handleViewLogAction() {
|
||||
let logVC = LogViewController()
|
||||
self.presentAsSheet(logVC)
|
||||
}
|
||||
|
||||
@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
|
||||
private func selectTunnel(at index: Int) -> Bool {
|
||||
if index < tunnelsManager.numberOfTunnels() {
|
||||
tableView.scrollRowToVisible(index)
|
||||
tableView.selectRowIndexes(IndexSet(integer: index), byExtendingSelection: false)
|
||||
return true
|
||||
}
|
||||
@ -275,15 +281,10 @@ extension TunnelsListTableViewController {
|
||||
}
|
||||
|
||||
func tunnelRemoved(at index: Int) {
|
||||
let selectedTunnelIndex = tableView.selectedRow
|
||||
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideLeft)
|
||||
if tunnelsManager.numberOfTunnels() == 0 {
|
||||
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) {
|
||||
guard tableView.selectedRow >= 0 else { return }
|
||||
let selectedTunnel = tunnelsManager.tunnel(at: tableView.selectedRow)
|
||||
delegate?.tunnelSelected(tunnel: selectedTunnel)
|
||||
let selectedTunnelIndices = tableView.selectedRowIndexes.sorted()
|
||||
if !selectedTunnelIndices.isEmpty {
|
||||
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 contents = input.contents
|
||||
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))
|
||||
}
|
||||
zipCloseFileInZip(zipFile)
|
||||
|
@ -37,7 +37,7 @@ class ZipImporter {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
unarchivedFiles.sort { $0.fileBaseName < $1.fileBaseName }
|
||||
unarchivedFiles.sort { TunnelsManager.tunnelNameIsLessThan($0.fileBaseName, $1.fileBaseName) }
|
||||
var configs: [TunnelConfiguration?] = Array(repeating: nil, count: unarchivedFiles.count)
|
||||
for (index, file) in unarchivedFiles.enumerated() {
|
||||
if index > 0 && file == unarchivedFiles[index - 1] {
|
||||
|
@ -117,6 +117,8 @@ extension Endpoint {
|
||||
hostname = "\(address)"
|
||||
case .ipv6(let address):
|
||||
hostname = "\(address)"
|
||||
@unknown default:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var resultPointer = UnsafeMutablePointer<addrinfo>(OpaquePointer(bitPattern: 0))
|
||||
|
@ -122,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
|
||||
private func configureLogger() {
|
||||
Logger.configureGlobal(withFilePath: FileManager.networkExtensionLogFileURL?.path)
|
||||
Logger.configureGlobal(tagged: "NET", withFilePath: FileManager.logFileURL?.path)
|
||||
wgSetLogger { level, msgC in
|
||||
guard let msgC = msgC else { return }
|
||||
let logType: OSLogType
|
||||
|
Submodule wireguard-go deleted from f7170e5de2
@ -26,61 +26,48 @@ export CGO_ENABLED := 1
|
||||
build: $(DESTDIR)/libwg-go.a
|
||||
version-header: $(DESTDIR)/wireguard-go-version.h
|
||||
|
||||
GOBUILDARCH := $(GOARCH_$(shell uname -m))
|
||||
GOBUILDOS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
GOBUILDVERSION := 1.11.5
|
||||
GOBUILDTARBALL := go$(GOBUILDVERSION).$(GOBUILDOS)-$(GOBUILDARCH).tar.gz
|
||||
GOBUILDTARBALLURL := https://dl.google.com/go/$(GOBUILDTARBALL)
|
||||
GOBUILDVERSION_NEEDED := go version go$(GOBUILDVERSION) $(GOBUILDOS)/$(GOBUILDARCH)
|
||||
GOBUILDVERSION_NEEDED := go version go1.12.1 darwin/amd64
|
||||
GOBUILDVERSION_CURRENT := $(shell go version 2>/dev/null)
|
||||
export REAL_GOROOT := $(shell go env GOROOT 2>/dev/null)
|
||||
export GOROOT := $(BUILDDIR)/goroot
|
||||
export GOPATH := $(BUILDDIR)/gopath
|
||||
export PATH := $(GOROOT)/bin:$(PATH)
|
||||
GOBUILDVERSION_CURRENT := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
||||
export PATH := $(GOPATH)/bin:$(PATH)
|
||||
GOBUILDVERSION_FAKE := $(shell $(GOROOT)/bin/go version 2>/dev/null)
|
||||
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
|
||||
.cache/$(GOBUILDTARBALL):
|
||||
mkdir -p $(dir $@)
|
||||
curl -o $@ $(GOBUILDTARBALLURL) || { rm -f $@; exit 1; }
|
||||
|
||||
$(GOROOT)/bin/go: .cache/$(GOBUILDTARBALL)
|
||||
rm -rf "$(GOROOT)"
|
||||
$(GOROOT)/.prepared:
|
||||
[ -n "$(REAL_GOROOT)" ]
|
||||
mkdir -p "$(GOROOT)"
|
||||
tar -C "$(GOROOT)" --strip-components=1 -xzf - < .cache/$(GOBUILDTARBALL) || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
patch -p1 -f -N -r- -d "$(GOROOT)" < goruntime-boottime-over-monotonic.diff || { rm -rf "$(GOROOT)"; exit 1; }
|
||||
rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/"
|
||||
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")
|
||||
|
||||
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
|
||||
$(BUILDDIR)/libwg-go-$(1).a: $(BUILDDIR)/.prepared
|
||||
cd "$(BUILDDIR)" || exit $$$$?; \
|
||||
$(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared
|
||||
CGO_CFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
||||
CGO_LDFLAGS="$(CFLAGS_PREFIX) $(ARCH)" \
|
||||
GOARCH="$(GOARCH_$(1))" \
|
||||
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=$$$$?; \
|
||||
rm -f "$(BUILDDIR)/libwg-go-$(1).h"; \
|
||||
exit $$$$ret
|
||||
endef
|
||||
$(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH))))
|
||||
|
||||
$(DESTDIR)/wireguard-go-version.h: ../wireguard-go/version.go
|
||||
sed -n 's/.*WireGuardGoVersion = "\(.*\)"/#define WIREGUARD_GO_VERSION "\1"/p' "$^" > "$@"
|
||||
$(DESTDIR)/wireguard-go-version.h: go.mod $(GOROOT)/.prepared
|
||||
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)
|
||||
@mkdir -vp "$(DESTDIR)"
|
||||
@ -89,9 +76,6 @@ $(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a)
|
||||
clean:
|
||||
rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h"
|
||||
|
||||
distclean: clean
|
||||
rm -rf .cache
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -17,8 +17,9 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"git.zx2c4.com/wireguard-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.zx2c4.com/wireguard/device"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
@ -45,12 +46,16 @@ func (l *CLogger) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var tunnelHandles map[int32]*Device
|
||||
type tunnelHandle struct {
|
||||
*device.Device
|
||||
*device.Logger
|
||||
}
|
||||
|
||||
var tunnelHandles = make(map[int32]tunnelHandle)
|
||||
|
||||
func init() {
|
||||
versionString = C.CString(WireGuardGoVersion)
|
||||
roamingDisabled = true
|
||||
tunnelHandles = make(map[int32]*Device)
|
||||
versionString = C.CString(device.WireGuardGoVersion)
|
||||
device.RoamingDisabled = true
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, unix.SIGUSR2)
|
||||
go func() {
|
||||
@ -61,7 +66,7 @@ func init() {
|
||||
n := runtime.Stack(buf, true)
|
||||
buf[n] = 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
|
||||
func wgEnableRoaming(enabled bool) {
|
||||
roamingDisabled = !enabled
|
||||
device.RoamingDisabled = !enabled
|
||||
}
|
||||
|
||||
//export wgSetLogger
|
||||
@ -80,21 +85,26 @@ func wgSetLogger(loggerFn uintptr) {
|
||||
|
||||
//export wgTurnOn
|
||||
func wgTurnOn(settings string, tunFd int32) int32 {
|
||||
logger := &Logger{
|
||||
logger := &device.Logger{
|
||||
Debug: log.New(&CLogger{level: 0}, "", 0),
|
||||
Info: log.New(&CLogger{level: 1}, "", 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 {
|
||||
logger.Error.Println(err)
|
||||
return -1
|
||||
}
|
||||
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 {
|
||||
logger.Error.Println(setError)
|
||||
return -1
|
||||
@ -112,7 +122,7 @@ func wgTurnOn(settings string, tunFd int32) int32 {
|
||||
if i == math.MaxInt32 {
|
||||
return -1
|
||||
}
|
||||
tunnelHandles[i] = device
|
||||
tunnelHandles[i] = tunnelHandle{device, logger}
|
||||
return i
|
||||
}
|
||||
|
||||
@ -132,10 +142,10 @@ func wgSetConfig(tunnelHandle int32, settings string) int64 {
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
err := ipcSetOperation(device, bufio.NewReader(strings.NewReader(settings)))
|
||||
err := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings)))
|
||||
if err != nil {
|
||||
device.log.Error.Println(err)
|
||||
return err.int64
|
||||
device.Error.Println(err)
|
||||
return err.ErrorCode()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@ -148,7 +158,7 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
}
|
||||
settings := new(bytes.Buffer)
|
||||
writer := bufio.NewWriter(settings)
|
||||
err := ipcGetOperation(device, writer)
|
||||
err := device.IpcGetOperation(writer)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@ -158,46 +168,18 @@ func wgGetConfig(tunnelHandle int32) *C.char {
|
||||
|
||||
//export wgBindInterfaceScope
|
||||
func wgBindInterfaceScope(tunnelHandle int32, ifscope int32) {
|
||||
var operr error
|
||||
device, ok := tunnelHandles[tunnelHandle]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
device.log.Info.Printf("Binding sockets to interface %d\n", ifscope)
|
||||
bind := device.net.bind.(*NativeBind)
|
||||
for bind.ipv4 != nil {
|
||||
fd, err := bind.ipv4.SyscallConn()
|
||||
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
|
||||
device.Info.Printf("Binding sockets to interface %d\n", ifscope)
|
||||
err := device.BindSocketToInterface4(uint32(ifscope))
|
||||
if err != nil {
|
||||
device.Error.Printf("Unable to bind v4 socket to interface:", err)
|
||||
}
|
||||
for bind.ipv6 != nil {
|
||||
fd, err := bind.ipv6.SyscallConn()
|
||||
if err != nil {
|
||||
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
|
||||
err = device.BindSocketToInterface6(uint32(ifscope))
|
||||
if err != nil {
|
||||
device.Error.Printf("Unable to bind v6 socket to interface:", err)
|
||||
}
|
||||
}
|
||||
|
8
wireguard-go-bridge/go.mod
Normal file
8
wireguard-go-bridge/go.mod
Normal 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
wireguard-go-bridge/go.sum
Normal file
13
wireguard-go-bridge/go.sum
Normal 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
|
||||
--- go/src/runtime/sys_darwin_386.s 2018-10-01 23:02:54.000000000 +0200
|
||||
+++ go-modified/src/runtime/sys_darwin_386.s 2018-11-01 23:18:04.383055355 +0100
|
||||
@@ -184,7 +184,7 @@
|
||||
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 -r -u go/src/runtime/sys_darwin_amd64.s go-modified/src/runtime/sys_darwin_amd64.s
|
||||
--- go/src/runtime/sys_darwin_amd64.s 2018-10-01 23:02:54.000000000 +0200
|
||||
+++ go-modified/src/runtime/sys_darwin_amd64.s 2018-11-01 23:18:04.382055360 +0100
|
||||
@@ -85,7 +85,7 @@
|
||||
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 -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 @@
|
||||
From 74523c5a12d37fa792e77a252bcc569484c3d41a Mon Sep 17 00:00:00 2001
|
||||
From: "Jason A. Donenfeld" <Jason@zx2c4.com>
|
||||
Date: Wed, 27 Feb 2019 05:33:01 +0100
|
||||
Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin
|
||||
|
||||
This makes timers account for having expired while a computer was
|
||||
asleep, which is quite common on mobile devices. Note that
|
||||
continuous_time absolute_time, except that it takes into account
|
||||
time spent in suspend.
|
||||
|
||||
Fixes #24595
|
||||
---
|
||||
src/runtime/sys_darwin.go | 2 +-
|
||||
src/runtime/sys_darwin_386.s | 2 +-
|
||||
src/runtime/sys_darwin_amd64.s | 2 +-
|
||||
src/runtime/sys_darwin_arm.s | 2 +-
|
||||
src/runtime/sys_darwin_arm64.s | 2 +-
|
||||
5 files changed, 5 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go
|
||||
index f34ac88352..416fcb673f 100644
|
||||
--- a/src/runtime/sys_darwin.go
|
||||
+++ b/src/runtime/sys_darwin.go
|
||||
@@ -403,7 +403,7 @@ func closeonexec(fd int32) {
|
||||
//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"
|
||||
@ -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_sigaction sigaction "/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
|
||||
|
||||
|
279
wireguard-go-bridge/goruntime-syscall-remove-getdirentries.diff
Normal file
279
wireguard-go-bridge/goruntime-syscall-remove-getdirentries.diff
Normal file
@ -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
|
||||
|
@ -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
|
||||
)
|
@ -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
|
||||
}
|
143
wireguard-go-bridge/sys-unix-remove-getdirentries.diff
Normal file
143
wireguard-go-bridge/sys-unix-remove-getdirentries.diff
Normal file
@ -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
|
||||
|
Reference in New Issue
Block a user