Skip to content

Commit

Permalink
Inform user about invalid tunnel configurations, refactor tunnel dict…
Browse files Browse the repository at this point in the history
… to array. Decouple interface state from config details.
  • Loading branch information
Johan Bloemberg committed Jan 2, 2019
1 parent 84c095d commit abc688d
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 45 deletions.
50 changes: 33 additions & 17 deletions UnitTests/AppTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
import XCTest

class AppTests: XCTestCase {
let testTunnels = [
"Tunnel Name": Tunnel(config: TunnelConfig(
address: "192.0.2.0/32",
peers: [Peer(
endpoint: "192.0.2.1/32:51820",
allowedIps: ["198.51.100.0/24"]
)]
)),
let testTunnels: [Tunnel] = [
Tunnel(
name: "1 Tunnel Name",
config: TunnelConfig(
address: "192.0.2.0/32",
peers: [Peer(
endpoint: "192.0.2.1/32:51820",
allowedIps: ["198.51.100.0/24"]
)]
)
),
Tunnel(name: "2 Invalid Config", config: nil),
]

let testConfig = """
Expand Down Expand Up @@ -44,22 +48,22 @@ class AppTests: XCTestCase {
var tunnels = testTunnels

XCTAssertEqual(menuImage(tunnels: tunnels).name(), "dragon")
tunnels["Tunnel Name"]!.interface = "utun1"
tunnels[0].interface = "utun1"
XCTAssertEqual(menuImage(tunnels: tunnels).name(), "silhouette")
}

func testMenu() {
let menu = buildMenu(tunnels: testTunnels)
XCTAssertEqual(menu.items[0].title, "Tunnel Name")
XCTAssertEqual(menu.items[0].title, "1 Tunnel Name")
XCTAssertEqual(menu.items[0].state, NSControl.StateValue.off)
}

func testMenuEnabledTunnel() {
var tunnels = testTunnels
tunnels["Tunnel Name"]!.interface = "utun1"
tunnels[0].interface = "utun1"

let menu = buildMenu(tunnels: tunnels)
XCTAssertEqual(menu.items[0].title, "Tunnel Name")
XCTAssertEqual(menu.items[0].title, "1 Tunnel Name")
XCTAssertEqual(menu.items[0].state, NSControl.StateValue.on)
XCTAssertEqual(menu.items[1].title, " Interface: utun1")
XCTAssertEqual(menu.items[2].title, " Address: 192.0.2.0/32")
Expand All @@ -69,26 +73,38 @@ class AppTests: XCTestCase {

func testMenuDetails() {
var tunnels = testTunnels
tunnels["Tunnel Name"]!.interface = "utun1"
tunnels[0].interface = "utun1"

let menu = buildMenu(tunnels: tunnels, details: true)
XCTAssertEqual(menu.items[0].title, "Tunnel Name")
XCTAssertEqual(menu.items[0].title, "1 Tunnel Name")
XCTAssertEqual(menu.items[0].state, NSControl.StateValue.on)
XCTAssertEqual(menu.items[1].title, " Interface: utun1")
XCTAssertEqual(menu.items[2].title, " Address: 192.0.2.0/32")
XCTAssertEqual(menu.items[3].title, " Endpoint: 192.0.2.1/32:51820")
XCTAssertEqual(menu.items[4].title, " Allowed IPs: 198.51.100.0/24")
}

func testMenuDetailsInvalidConfig() {
var tunnels = testTunnels
tunnels[1].interface = "utun1"

let menu = buildMenu(tunnels: tunnels, details: true)
let offset = 4
XCTAssertEqual(menu.items[0 + offset].title, "2 Invalid Config")
XCTAssertEqual(menu.items[0 + offset].state, NSControl.StateValue.on)
XCTAssertEqual(menu.items[1 + offset].title, " Interface: utun1")
XCTAssertEqual(menu.items[2 + offset].title, " Could not parse tunnel configuration!")
}

func testMenuNoTunnels() {
let menu = buildMenu(tunnels: Tunnels())
XCTAssertEqual(menu.items[0].title, "No tunnel configurations found")
}

func testMenuSorting() {
let tunnels = [
"Z Tunnel Name": Tunnel(),
"A Tunnel Name": Tunnel(),
let tunnels: [Tunnel] = [
Tunnel(name: "Z Tunnel Name"),
Tunnel(name: "A Tunnel Name"),
]
let menu = buildMenu(tunnels: tunnels)
// tunnels should be sorted alphabetically
Expand Down
6 changes: 3 additions & 3 deletions WireGuardStatusbar/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, AppProtocol {
} as? HelperProtocol

xpcService?.getTunnels(reply: { tunnelInfo in
self.tunnels = tunnelInfo.mapValues { interfaceAndConfigData in
return Tunnel(fromTunnelInfo: interfaceAndConfigData)
self.tunnels = tunnelInfo.map { name, interfaceAndConfigData in
return Tunnel(name: name, fromTunnelInfo: interfaceAndConfigData)
}
DispatchQueue.main.async { self.statusItem.image = menuImage(tunnels: self.tunnels) }
})
Expand All @@ -90,7 +90,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, AppProtocol {
// bring tunnel up/down
@objc func toggleTunnel(_ sender: NSMenuItem) {
if let tunnelName = sender.representedObject as? String {
let tunnel = tunnels[tunnelName]!
let tunnel = tunnels.filter { $0.name == tunnelName }[0]

let xpcService = privilegedHelper!.helperConnection()?.remoteObjectProxyWithErrorHandler { error -> Void in
NSLog("XPCService error: \(error)")
Expand Down
2 changes: 1 addition & 1 deletion WireGuardStatusbar/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.12</string>
<string>1.13</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
Expand Down
37 changes: 23 additions & 14 deletions WireGuardStatusbar/Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func buildMenu(tunnels: Tunnels, details: Bool = false, showInstallInstructions:

// WireGaurd missing is a big problem, user should fix this first. TODO, include WireGuard with the App
if showInstallInstructions {
NSLog("WireGuard binary not found at \(wireguardBin)")
NSLog("WireGuard binary not found at '\(wireguardBin)'")
statusMenu.insertItem(NSMenuItem(title: "WireGuard not installed! Click here for instructions",
action: #selector(AppDelegate.showInstallInstructions(_:)),
keyEquivalent: ""), at: 0)
Expand All @@ -26,27 +26,36 @@ func buildMenu(tunnels: Tunnels, details: Bool = false, showInstallInstructions:
statusMenu.insertItem(NSMenuItem(title: "No tunnel configurations found",
action: nil, keyEquivalent: ""), at: 0)
} else {
for (tunnelName, tunnel) in tunnels.sorted(by: { $0.0.lowercased() > $1.0.lowercased() }) {
let item = NSMenuItem(title: "\(tunnelName)",
for tunnel in tunnels.sorted(by: { $0.name.lowercased() > $1.name.lowercased() }) {
let item = NSMenuItem(title: "\(tunnel.name)",
action: #selector(AppDelegate.toggleTunnel(_:)), keyEquivalent: "")
item.representedObject = tunnelName
item.representedObject = tunnel.name
if tunnel.connected {
item.state = NSControl.StateValue.on
}
if tunnel.connected || details, let config = tunnel.config {
for peer in config.peers {
statusMenu.insertItem(NSMenuItem(title: " Allowed IPs: \(peer.allowedIps.joined(separator: ", "))",
if tunnel.connected || details {
if let config = tunnel.config {
for peer in config.peers {
statusMenu.insertItem(
NSMenuItem(title: " Allowed IPs: \(peer.allowedIps.joined(separator: ", "))",
action: nil, keyEquivalent: ""), at: 0
)
statusMenu.insertItem(NSMenuItem(title: " Endpoint: \(peer.endpoint)",
action: nil, keyEquivalent: ""), at: 0)
}
statusMenu.insertItem(NSMenuItem(title: " Address: \(config.address)",
action: nil, keyEquivalent: ""), at: 0)
statusMenu.insertItem(NSMenuItem(title: " Endpoint: \(peer.endpoint)",
} else {
statusMenu.insertItem(NSMenuItem(title: " Could not parse tunnel configuration!",
action: nil, keyEquivalent: ""), at: 0)
}
statusMenu.insertItem(NSMenuItem(title: " Address: \(config.address)",
}

if tunnel.connected, let interface = tunnel.interface {
statusMenu.insertItem(NSMenuItem(title: " Interface: \(interface)",
action: nil, keyEquivalent: ""), at: 0)
if tunnel.interface != nil && tunnel.interface != "" {
statusMenu.insertItem(NSMenuItem(title: " Interface: \(tunnel.interface!)",
action: nil, keyEquivalent: ""), at: 0)
}
}

statusMenu.insertItem(item, at: 0)
}
}
Expand All @@ -55,7 +64,7 @@ func buildMenu(tunnels: Tunnels, details: Bool = false, showInstallInstructions:
}

func menuImage(tunnels: Tunnels) -> NSImage {
let connectedTunnels = tunnels.filter { $1.connected }
let connectedTunnels = tunnels.filter { $0.connected }
if connectedTunnels.isEmpty {
let icon = NSImage(named: .disabled)!
icon.isTemplate = true
Expand Down
16 changes: 11 additions & 5 deletions WireGuardStatusbar/Tunnel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import Foundation

// List of all tunnels known by this application, used to build menu
// Tunnels are referenced by their configuration file name similar to how wg-quick would
typealias Tunnels = [TunnelName: Tunnel]
// Tunnels are referenced by their configuration file name similar to how wg-quick would.
typealias Tunnels = [Tunnel]

struct Tunnel {
let name: String

// name of the interface associated with this tunnel (if connected)
var interface: TunnelInterface?

Expand All @@ -29,13 +31,18 @@ struct Peer {
}

extension Tunnel {
init(config: TunnelConfig?) {
init(name: String, config: TunnelConfig? = nil) {
self.name = name
self.config = config
}

init(fromTunnelInfo tunnelInfo: [TunnelInterfaceOrConfigData]) {
init(name: String, fromTunnelInfo tunnelInfo: [TunnelInterfaceOrConfigData]) {
self.name = name
interface = tunnelInfo[0] != "" ? tunnelInfo[0] : nil
config = TunnelConfig(fromConfig: tunnelInfo[1])
if config == nil {
NSLog("Failed to read configuration file for tunnel '\(name)'")
}
}
}

Expand All @@ -57,7 +64,6 @@ extension TunnelConfig {
address = interface["Address"] ?? ""
}
} else {
NSLog("Failed to read configuration file")
return nil
}
}
Expand Down
11 changes: 8 additions & 3 deletions WireGuardStatusbarHelper/Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Helper: NSObject, HelperProtocol, SKQueueDelegate {
NSLog("Watching \(directory) for changes")
queue.addPath(directory)
} else {
NSLog("Not watching \(directory) as it doesn't exist")
NSLog("Not watching '\(directory)' as it doesn't exist")
}
}
}
Expand Down Expand Up @@ -69,18 +69,23 @@ class Helper: NSObject, HelperProtocol, SKQueueDelegate {
}

let tunnelName = configFile.replacingOccurrences(of: ".conf", with: "")
tunnels[tunnelName] = []
if tunnels[tunnelName] != nil {
NSLog("Skipping '\(configFile)' as this tunnel already exists from a higher configuration path.")
continue
}

NSLog("Reading interface for tunnel \(tunnelName)")
var interfaceName = try? String(contentsOfFile: runPath + "/" + tunnelName + ".name", encoding: .utf8)
if interfaceName == nil {
// tunnel is not connected
interfaceName = ""
}

// TODO: read configuration data from wg showconf as well
NSLog("Reading config file: \(configPath)/\(configFile)")
var configData = try? String(contentsOfFile: configPath + "/" + configFile, encoding: .utf8)
if configData == nil {
NSLog("Failed to read configuration file '\(configPath)/\(configFile)'")
configData = ""
}

Expand All @@ -96,7 +101,7 @@ class Helper: NSObject, HelperProtocol, SKQueueDelegate {
let state = enable ? "up" : "down"

if !validateTunnelName(tunnelName: tunnelName) {
NSLog("Invalid tunnel name \(tunnelName)")
NSLog("Invalid tunnel name '\(tunnelName)'")
reply(1)
return
}
Expand Down
4 changes: 2 additions & 2 deletions WireGuardStatusbarHelper/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
<key>CFBundleName</key>
<string>WireGuardStatusbarHelper</string>
<key>CFBundleShortVersionString</key>
<string>1.12</string>
<string>1.13</string>
<key>CFBundleVersion</key>
<string>1.0.10</string>
<string>1.0.11</string>
<key>SMAuthorizedClients</key>
<array>
<string>anchor apple generic and identifier &quot;WireGuardStatusbar&quot;</string>
Expand Down

0 comments on commit abc688d

Please sign in to comment.