84689b208e
- reference/esp32-agent/: DESIGN, HARDWARE, API_COMPAT, ARCHITECTURE, README - reference/ios-agent/: DESIGN, API_COMPAT, UI_DESIGN, ARCHITECTURE, README - Both mirror Android agent architecture, UI design, and heartbeat flow - Architecture-only (no code) — avoids submodule conflicts Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
14 KiB
14 KiB
iOS Agent — Technical Design Document
Version: 1.0.0
Date: 2026-07-06
Status: Draft
Mirror: Android Agent (apps/android-agent/)
1. Executive Summary
NexusGuard iOS Agent is an iOS-based WireGuard client for mobile devices. It mirrors the Android agent's architecture, UI design, and heartbeat flow, using iOS-native APIs (NetworkExtension, CryptoKit, Keychain).
2. Goals & Non-Goals
Goals
- iOS-based WireGuard tunnel (NetworkExtension)
- Auto-provisioning via HTTP (same API as Android/ESP32)
- Heartbeat monitoring with config sync (30s interval)
- Keychain-based config storage (hardware-backed encryption)
- SwiftUI UI matching Android agent design
- Background operation via NetworkExtension
Non-Goals
- gRPC support (not needed for mobile)
- Port forwarding (userspace agent only)
- Jailbroken device support
- macOS/iPadOS support (iPhone only for v1)
3. Architecture
3.1 System Architecture
┌─────────────────────────────────────────────────────────┐
│ iOS (SwiftUI) │
├─────────────────────────────────────────────────────────┤
│ App Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ContentView│ │SettingsView│ │LogView │ │PortView │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ AgentManager │ │
│ │ (orchestrates all components) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Service Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │Provisioning│ │Heartbeat│ │TunnelMgr │ │ConfigMgr│ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ ┌────┴─────────────┴────────────┴─────────────┴────┐ │
│ │ CryptoManager │ │
│ │ AES-256-GCM (CryptoKit) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ NetworkExtension Layer │
│ ┌──────────────────────────────────────────────────┐ │
│ │ PacketTunnelProvider │ │
│ │ (WireGuard tunnel via wireguard-apple) │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Storage Layer │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Keychain │ │ UserDefaults│ │ CoreData│ │ FileMgr │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────┘
3.2 Task Architecture
iOS Threads/Tasks
├── Main Thread # UI updates
├── AgentManager # Orchestration (async/await)
├── HeartbeatTask # HTTP heartbeat (30s interval)
├── TunnelManager # WireGuard tunnel management
└── BackgroundTask # BGAppRefreshTask (background refresh)
3.3 State Machine
┌─────────────┐
│ BOOT │
└──────┬──────┘
│
▼
┌─────────────┐
│ APP_INIT │ ◄──────────────────────────────┐
└──────┬──────┘ │
│ loaded │
▼ │
┌─────────────┐ fail ┌─────────────┐│
│ PROVISION │───────────────►│ APP_RETRY ││
└──────┬──────┘ └──────┬──────┘│
│ success │ │
▼ └───────┘
┌─────────────┐
│ TUNNEL_UP │
└──────┬──────┘
│
▼
┌─────────────┐
│ HEARTBEAT │ ◄─── 30s interval
└──────┬──────┘
│ config_changed
▼
┌─────────────┐
│ TUNNEL_REBUILD │
└──────┬──────┘
│
└──► HEARTBEAT
4. Module Design
4.1 AgentManager
Responsibilities:
- Orchestrate all agent components
- Manage agent lifecycle (start/stop)
- Handle state transitions
Key Properties:
class AgentManager: ObservableObject {
@Published var state: AgentState = .disconnected
@Published var tunnelInfo: TunnelInfo?
@Published var logs: [LogEntry] = []
private let provisioning: ProvisioningService
private let heartbeat: HeartbeatService
private let tunnel: TunnelManager
private let config: KeychainStorage
}
Key Methods:
func startAgent() async
func stopAgent()
func rebuildTunnel(with config: WireGuardConfig) async
4.2 PacketTunnelProvider
Responsibilities:
- Handle NetworkExtension tunnel lifecycle
- Start/stop WireGuard tunnel
- Report tunnel status
Key Methods:
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void)
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void)
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?)
4.3 TunnelManager
Responsibilities:
- Build WireGuard config from stored keys
- Start/stop tunnel via PacketTunnelProvider
- Monitor tunnel state
- Track last handshake time
Key Methods:
func startTunnel(config: WireGuardConfig) async throws
func stopTunnel()
func isTunnelUp() -> Bool
func getLastHandshake() -> Date?
4.4 HeartbeatService
Responsibilities:
- HTTP POST to
/api/v1/heartbeatevery 30s - Send device status + tunnel state
- Receive config sync (detect changes)
- Trigger tunnel rebuild if config changed
Key Methods:
func startHeartbeatLoop()
func stopHeartbeat()
func sendHeartbeat(config: WireGuardConfig, tunnelUp: Bool, lastHandshake: Date?) async -> WireGuardConfig?
4.5 ProvisioningService
Responsibilities:
- HTTP POST to
/api/v1/provision - Parse encrypted config response
- Decrypt config via CryptoManager
- Store config in Keychain
Key Methods:
func provision(serverUrl: String, token: String, hwid: String) async -> WireGuardConfig?
func decryptConfig(encrypted: String, hwid: String, salt: String) -> WireGuardConfig?
4.6 CryptoManager
Responsibilities:
- Key derivation: SHA-256(hwid + salt)
- AES-256-GCM decryption
- Secure memory handling
Key Methods:
func deriveKey(token: String, hwid: String) -> SymmetricKey
func decrypt(encoded: String, key: SymmetricKey) -> Data?
4.7 KeychainStorage
Responsibilities:
- Store WireGuard keys in Keychain
- Store agent config in UserDefaults
- Secure key access
Key Methods:
func saveConfig(_ config: AgentConfig)
func loadConfig() -> AgentConfig?
func saveWireGuardConfig(_ config: WireGuardConfig)
func loadWireGuardConfig() -> WireGuardConfig?
func clearAll()
4.8 LogStore
Responsibilities:
- Buffer log entries in memory
- Persist to CoreData (optional)
- Provide log stream for UI
Key Methods:
func add(level: LogLevel, tag: String, message: String)
func clear()
func getLogs() -> [LogEntry]
5. Data Structures
5.1 WireGuardConfig
struct WireGuardConfig: Codable {
let deviceId: String
let privateKey: String
let presharedKey: String
let internalIp: String
let serverPub: String
let endpoint: String
let dns: String
let allowedIps: String
let serverWgIp: String
let configHash: String
let forwardsHash: String
}
5.2 AgentConfig
struct AgentConfig: Codable {
let serverUrl: String
let registrationToken: String
let isProvisioned: Bool
}
5.3 AgentState
enum AgentState: String {
case disconnected = "disconnected"
case connecting = "connecting"
case connected = "connected"
case failed = "failed"
}
5.4 LogEntry
struct LogEntry: Identifiable {
let id = UUID()
let timestamp: Date
let level: LogLevel
let tag: String
let message: String
}
6. Error Handling
6.1 Error Types
enum AgentError: Error {
case wifiNotConnected
case provisioningFailed(String)
case heartbeatFailed(String)
case cryptoDecryptionFailed
case configInvalid
case tunnelInitFailed
case keychainError(OSStatus)
}
6.2 Recovery Strategy
| Error | Recovery |
|---|---|
| WiFi not connected | Retry 3x, then pause heartbeat |
| HTTP provision fail | Retry 3x, then notify user |
| HTTP heartbeat fail | Log only, continue |
| Crypto decrypt fail | Clear config, re-provision |
| Tunnel init fail | Retry 3x, then notify user |
| Keychain error | Use UserDefaults fallback |
7. Background Tasks
7.1 BGAppRefreshTask
iOS doesn't have boot receiver like Android. Use Background Tasks:
// Register task
BGAppRefreshTaskRequest.register(forTaskWithIdentifier: "com.nexusguard.agent.refresh", using: nil) {
task in
self.handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}
// Schedule task
let request = BGAppRefreshTaskRequest(identifier: "com.nexusguard.agent.refresh")
request.earliestBeginDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes
try BGTaskScheduler.shared.submit(request)
7.2 Background Modes
Add to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<string>vpn-api</string>
</array>
8. Security Considerations
8.1 Key Storage
- WireGuard private keys stored in iOS Keychain (hardware-backed)
- Non-sensitive config in UserDefaults
- Never log keys or tokens
8.2 TLS
- Server communication over HTTPS (TLS 1.2+)
- Use
URLSessionwith default TLS settings - Certificate pinning (optional, future)
8.3 Hardware ID
- Use
identifierForVendorfor device identification - Persists across app reinstalls until device factory reset
- Used for key derivation (HWID + salt)
9. Testing Strategy
9.1 Unit Tests
- Crypto: Decrypt known ciphertext with known key
- Config: Keychain read/write cycle
- Heartbeat: Parse server response
9.2 Integration Tests
- Provision → Tunnel → Heartbeat cycle
- Config change detection
- Tunnel rebuild on config change
9.3 UI Tests
- VPN toggle interaction
- Tab switching
- Settings save/load
9.4 Device Tests
- iPhone 12+ running iOS 16+
- NetworkExtension background operation
- Keychain access in background
10. Future Enhancements
10.1 Phase 2
- iPadOS support
- Widget for quick status
- Shortcuts integration
- Siri commands
10.2 Phase 3
- macOS Catalyst support
- Apple Watch companion
- CarPlay integration