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>
426 lines
14 KiB
Markdown
426 lines
14 KiB
Markdown
# 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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
func startTunnel(config: WireGuardConfig) async throws
|
|
func stopTunnel()
|
|
func isTunnelUp() -> Bool
|
|
func getLastHandshake() -> Date?
|
|
```
|
|
|
|
### 4.4 HeartbeatService
|
|
|
|
**Responsibilities**:
|
|
- HTTP POST to `/api/v1/heartbeat` every 30s
|
|
- Send device status + tunnel state
|
|
- Receive config sync (detect changes)
|
|
- Trigger tunnel rebuild if config changed
|
|
|
|
**Key Methods**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
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**:
|
|
```swift
|
|
func add(level: LogLevel, tag: String, message: String)
|
|
func clear()
|
|
func getLogs() -> [LogEntry]
|
|
```
|
|
|
|
## 5. Data Structures
|
|
|
|
### 5.1 WireGuardConfig
|
|
|
|
```swift
|
|
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
|
|
|
|
```swift
|
|
struct AgentConfig: Codable {
|
|
let serverUrl: String
|
|
let registrationToken: String
|
|
let isProvisioned: Bool
|
|
}
|
|
```
|
|
|
|
### 5.3 AgentState
|
|
|
|
```swift
|
|
enum AgentState: String {
|
|
case disconnected = "disconnected"
|
|
case connecting = "connecting"
|
|
case connected = "connected"
|
|
case failed = "failed"
|
|
}
|
|
```
|
|
|
|
### 5.4 LogEntry
|
|
|
|
```swift
|
|
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
|
|
|
|
```swift
|
|
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:
|
|
|
|
```swift
|
|
// 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`:
|
|
```xml
|
|
<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 `URLSession` with default TLS settings
|
|
- Certificate pinning (optional, future)
|
|
|
|
### 8.3 Hardware ID
|
|
|
|
- Use `identifierForVendor` for 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
|
|
|
|
## 11. References
|
|
|
|
- [Apple NetworkExtension Documentation](https://developer.apple.com/documentation/networkextension)
|
|
- [WireGuard for iOS](https://github.com/WireGuard/wireguard-apple)
|
|
- [CryptoKit Documentation](https://developer.apple.com/cryptokit/)
|
|
- [Background Tasks](https://developer.apple.com/documentation/backgroundtasks)
|
|
- [NexusGuard Server API](../../server-core/docs/)
|