mirror of
https://github.com/yggdrasil-network/yggdrasil-ios.git
synced 2025-04-27 13:45:10 +03:00
312 lines
13 KiB
Swift
312 lines
13 KiB
Swift
//
|
|
// PeersViewController.swift
|
|
// YggdrasilNetwork
|
|
//
|
|
// Created by Neil Alexander on 07/01/2019.
|
|
//
|
|
|
|
import UIKit
|
|
import NetworkExtension
|
|
import CoreTelephony
|
|
|
|
class PeersViewController: UITableViewController {
|
|
var app = UIApplication.shared.delegate as! AppDelegate
|
|
var config: [String: Any]? = nil
|
|
|
|
@IBOutlet var peerTable: UITableView!
|
|
@IBOutlet weak var addButtonItem: UIBarButtonItem!
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
if let proto = self.app.vpnManager.protocolConfiguration as? NETunnelProviderProtocol {
|
|
config = proto.providerConfiguration ?? nil
|
|
}
|
|
|
|
self.navigationItem.rightBarButtonItems = [
|
|
self.editButtonItem,
|
|
self.addButtonItem
|
|
]
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(self.onYggdrasilPeersUpdated), name: NSNotification.Name.YggdrasilPeersUpdated, object: nil)
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.YggdrasilPeersUpdated, object: nil)
|
|
}
|
|
|
|
@objc func onYggdrasilPeersUpdated(notification: NSNotification) {
|
|
peerTable.reloadData()
|
|
}
|
|
|
|
// MARK: - Table view data source
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
return 3
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
switch section {
|
|
case 0: return app.yggdrasilSwitchPeers.count
|
|
case 1:
|
|
if let config = self.app.yggdrasilConfig {
|
|
if let peers = config.get("Peers") as? [String] {
|
|
return peers.count
|
|
}
|
|
}
|
|
return 0
|
|
case 2:
|
|
if UIDevice.current.hasCellularCapabilites {
|
|
return 3
|
|
}
|
|
return 2
|
|
default: return 0
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
switch indexPath.section {
|
|
case 0:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "discoveredPeerPrototype", for: indexPath)
|
|
let peers = app.yggdrasilSwitchPeers.sorted { (a, b) -> Bool in
|
|
return (a["Port"] as! Int) < (b["Port"] as! Int)
|
|
}
|
|
|
|
if indexPath.row < peers.count {
|
|
let value = peers[indexPath.row]
|
|
let proto = value["Protocol"] as? String ?? "tcp"
|
|
let sent = value["BytesSent"] as? Double ?? 0
|
|
let recvd = value["BytesRecvd"] as? Double ?? 0
|
|
let rx = self.format(bytes: sent)
|
|
let tx = self.format(bytes: recvd)
|
|
|
|
cell.textLabel?.text = "\(value["Endpoint"] ?? "unknown")"
|
|
cell.detailTextLabel?.text = "\(proto.uppercased()) peer on port \(value["Port"] ?? "unknown"), sent \(tx), received \(rx)"
|
|
}
|
|
return cell
|
|
case 1:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "configuredPeerPrototype", for: indexPath)
|
|
if let config = self.app.yggdrasilConfig {
|
|
if let peers = config.get("Peers") as? [String] {
|
|
cell.textLabel?.text = peers[indexPath.last!]
|
|
} else {
|
|
cell.textLabel?.text = "(unknown)"
|
|
}
|
|
}
|
|
return cell
|
|
case 2:
|
|
switch indexPath.last {
|
|
case 0:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "togglePrototype", for: indexPath) as! ToggleTableViewCell
|
|
cell.isUserInteractionEnabled = true
|
|
cell.label?.text = "Search for multicast peers"
|
|
cell.label?.isEnabled = true
|
|
cell.toggle?.addTarget(self, action: #selector(toggledMulticast), for: .valueChanged)
|
|
cell.toggle?.isEnabled = true
|
|
if let config = self.app.yggdrasilConfig {
|
|
let interfaces = config.get("MulticastInterfaces") as? [String] ?? []
|
|
cell.toggle?.isOn = interfaces.contains("en*")
|
|
}
|
|
return cell
|
|
case 1:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "togglePrototype", for: indexPath) as! ToggleTableViewCell
|
|
cell.isUserInteractionEnabled = false
|
|
cell.label?.text = "Search for nearby iOS peers"
|
|
cell.label?.isEnabled = false
|
|
cell.toggle?.addTarget(self, action: #selector(toggledAWDL), for: .valueChanged)
|
|
cell.toggle?.setOn(false, animated: false)
|
|
cell.toggle?.isEnabled = false
|
|
/*if let config = self.app.yggdrasilConfig {
|
|
let interfaces = config.get("MulticastInterfaces") as? [String] ?? []
|
|
cell.toggle?.isOn = interfaces.contains("awdl0")
|
|
}*/
|
|
return cell
|
|
case 2:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "menuPrototype", for: indexPath)
|
|
cell.isUserInteractionEnabled = true
|
|
cell.textLabel?.text = "Device settings"
|
|
cell.textLabel?.isEnabled = true
|
|
return cell
|
|
default:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "menuPrototype", for: indexPath)
|
|
cell.isUserInteractionEnabled = false
|
|
cell.textLabel?.text = "Unknown"
|
|
cell.textLabel?.isEnabled = true
|
|
return cell
|
|
}
|
|
default:
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: "configuredPeerPrototype", for: indexPath)
|
|
cell.textLabel?.text = "(unknown)"
|
|
return cell
|
|
}
|
|
}
|
|
|
|
func format(bytes: Double) -> String {
|
|
guard bytes > 0 else {
|
|
return "0 bytes"
|
|
}
|
|
|
|
// Adapted from http://stackoverflow.com/a/18650828
|
|
let suffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
let k: Double = 1000
|
|
let i = floor(log(bytes) / log(k))
|
|
|
|
// Format number with thousands separator and everything below 1 GB with no decimal places.
|
|
let numberFormatter = NumberFormatter()
|
|
numberFormatter.maximumFractionDigits = i < 3 ? 0 : 1
|
|
numberFormatter.numberStyle = .decimal
|
|
|
|
let numberString = numberFormatter.string(from: NSNumber(value: bytes / pow(k, i))) ?? "Unknown"
|
|
let suffix = suffixes[Int(i)]
|
|
return "\(numberString) \(suffix)"
|
|
}
|
|
|
|
@objc func toggledMulticast(_ sender: UISwitch) {
|
|
if let config = self.app.yggdrasilConfig {
|
|
var interfaces = config.get("MulticastInterfaces") as! [String]
|
|
if sender.isOn {
|
|
interfaces.append("en*")
|
|
} else {
|
|
interfaces.removeAll(where: { $0 == "en*" })
|
|
}
|
|
config.set("MulticastInterfaces", to: interfaces as [String])
|
|
try? config.save(to: &app.vpnManager)
|
|
}
|
|
}
|
|
|
|
@objc func toggledAWDL(_ sender: UISwitch) {
|
|
if let config = self.app.yggdrasilConfig {
|
|
var interfaces = config.get("MulticastInterfaces") as! [String]
|
|
if sender.isOn {
|
|
interfaces.append("awdl0")
|
|
} else {
|
|
interfaces.removeAll(where: { $0 == "awdl0" })
|
|
}
|
|
config.set("MulticastInterfaces", to: interfaces as [String])
|
|
try? config.save(to: &app.vpnManager)
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
|
switch section {
|
|
case 0:
|
|
if self.app.yggdrasilPeers.count > 0 {
|
|
return "Connected Peers"
|
|
}
|
|
return "No peers currently connected"
|
|
case 1:
|
|
if let config = self.app.yggdrasilConfig {
|
|
if let peers = config.get("Peers") as? [String] {
|
|
if peers.count > 0 {
|
|
return "Configured Peers"
|
|
}
|
|
}
|
|
}
|
|
return "No peers currently configured"
|
|
case 2:
|
|
return "Peer Connectivity"
|
|
default: return "(Unknown)"
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
|
switch section {
|
|
case 1:
|
|
return "Yggdrasil will automatically attempt to connect to configured peers when started."
|
|
case 2:
|
|
var str = "Multicast peers will be discovered on the same Wi-Fi network or via USB."
|
|
if UIDevice.current.hasCellularCapabilites {
|
|
str += " Data charges may apply when using mobile data. You can prevent mobile data usage in the device settings."
|
|
}
|
|
return str
|
|
default: return nil
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
return indexPath.first == 1
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
|
switch indexPath.first {
|
|
case 0:
|
|
return [UITableViewRowAction(style: UITableViewRowAction.Style.default, title: "Disconnect", handler: { (action, index) in
|
|
|
|
})]
|
|
case 1:
|
|
return [UITableViewRowAction(style: UITableViewRowAction.Style.normal, title: "Remove", handler: { (action, index) in
|
|
print(action, index)
|
|
if let config = self.app.yggdrasilConfig {
|
|
config.remove(index: index.last!, from: "Peers")
|
|
do {
|
|
try config.save(to: &self.app.vpnManager)
|
|
tableView.reloadSections(IndexSet(integer: 1), with: UITableView.RowAnimation.automatic)
|
|
} catch {
|
|
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
|
self.parent?.present(alert, animated: true, completion: nil)
|
|
print("Error removing: \(error)")
|
|
}
|
|
}
|
|
})]
|
|
default:
|
|
return []
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
switch indexPath.first {
|
|
case 2:
|
|
if let last = indexPath.last, last == 2 {
|
|
UIApplication.shared.open(NSURL(string:UIApplication.openSettingsURLString)! as URL, options: [:]) { (result) in
|
|
NSLog("Result " + result.description)
|
|
}
|
|
}
|
|
default:
|
|
break
|
|
}
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
}
|
|
|
|
@IBAction func addNewPeerButtonPressed(_ sender: UIBarButtonItem) {
|
|
let alert = UIAlertController(title: "Add Configured Peer", message: """
|
|
Enter the full URI of the peer to add. Yggdrasil will automatically connect to this peer when started.
|
|
""", preferredStyle: UIAlertController.Style.alert)
|
|
let action = UIAlertAction(title: "Add", style: .default) { (alertAction) in
|
|
let textField = alert.textFields![0] as UITextField
|
|
if let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines) {
|
|
if let config = self.app.yggdrasilConfig {
|
|
if let peers = config.get("Peers") as? [String], !peers.contains(text) {
|
|
config.add(text, in: "Peers")
|
|
do {
|
|
try config.save(to: &self.app.vpnManager)
|
|
if let index = config.get("Peers") as? [String] {
|
|
self.peerTable.insertRows(at: [IndexPath(indexes: [1, index.count-1])], with: .automatic)
|
|
self.peerTable.reloadSections(IndexSet(integer: 1), with: UITableView.RowAnimation.automatic)
|
|
}
|
|
} catch {
|
|
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
|
self.parent?.present(alert, animated: true, completion: nil)
|
|
print("Add error: \(error)")
|
|
}
|
|
} else {
|
|
let alert = UIAlertController(title: "Error", message: "Peer already exists", preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
|
self.parent?.present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
let cancel = UIAlertAction(title: "Cancel", style: .cancel)
|
|
alert.addTextField { (textField) in
|
|
textField.placeholder = "tcp://hostname:port"
|
|
}
|
|
alert.addAction(action)
|
|
alert.addAction(cancel)
|
|
self.present(alert, animated: true, completion: nil)
|
|
}
|
|
|
|
}
|