orchestrator.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. package vowifi
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. const defaultCleanupTimeout = 10 * time.Second
  12. type runtimeResources struct {
  13. cancel context.CancelFunc
  14. radio RadioSnapshot
  15. radioChanged bool
  16. tunnel TunnelSession
  17. ims IMSSession
  18. }
  19. // Orchestrator serializes lifecycle mutations while allowing concurrent state
  20. // readers and subscribers. Disable cancels an in-flight Enable before waiting
  21. // for the mutation lock, so a blocked provider cannot deadlock shutdown.
  22. type Orchestrator struct {
  23. deps Dependencies
  24. options Options
  25. operation chan struct{}
  26. mu sync.Mutex
  27. state State
  28. resources *runtimeResources
  29. subscribers map[uint64]chan State
  30. nextSubscriber uint64
  31. }
  32. func New(deps Dependencies, options Options) (*Orchestrator, error) {
  33. if err := deps.validate(); err != nil {
  34. return nil, err
  35. }
  36. if err := options.validate(); err != nil {
  37. return nil, err
  38. }
  39. if options.CleanupTimeout == 0 {
  40. options.CleanupTimeout = defaultCleanupTimeout
  41. }
  42. options.DeviceID = strings.TrimSpace(options.DeviceID)
  43. now := time.Now().UTC()
  44. orchestrator := &Orchestrator{
  45. deps: deps,
  46. options: options,
  47. operation: make(chan struct{}, 1),
  48. state: State{
  49. DeviceID: strings.TrimSpace(options.DeviceID),
  50. Phase: PhaseIdle,
  51. Sequence: 1,
  52. UpdatedAt: now,
  53. Security: SecurityAudit{
  54. ResponderAUTH: ResponderAUTHUnknown,
  55. },
  56. },
  57. subscribers: make(map[uint64]chan State),
  58. }
  59. orchestrator.operation <- struct{}{}
  60. return orchestrator, nil
  61. }
  62. // State returns a detached snapshot safe for mutation by the caller.
  63. func (orchestrator *Orchestrator) State() State {
  64. orchestrator.mu.Lock()
  65. defer orchestrator.mu.Unlock()
  66. return orchestrator.state.clone()
  67. }
  68. // Subscribe returns the current state immediately and then the newest state on
  69. // every mutation. Slow subscribers lose intermediate snapshots rather than
  70. // blocking the modem lifecycle.
  71. func (orchestrator *Orchestrator) Subscribe(buffer int) (<-chan State, func()) {
  72. if buffer < 1 {
  73. buffer = 1
  74. }
  75. channel := make(chan State, buffer)
  76. orchestrator.mu.Lock()
  77. id := orchestrator.nextSubscriber
  78. orchestrator.nextSubscriber++
  79. orchestrator.subscribers[id] = channel
  80. channel <- orchestrator.state.clone()
  81. orchestrator.mu.Unlock()
  82. var once sync.Once
  83. cancel := func() {
  84. once.Do(func() {
  85. orchestrator.mu.Lock()
  86. if existing, ok := orchestrator.subscribers[id]; ok {
  87. delete(orchestrator.subscribers, id)
  88. close(existing)
  89. }
  90. orchestrator.mu.Unlock()
  91. })
  92. }
  93. return channel, cancel
  94. }
  95. // Enable executes one evidence-backed transaction. The order intentionally
  96. // follows the working Linux/QMI path: live identity and home PLMN, AKA
  97. // availability, ePDG derivation, runtime-owned RF off, cellular-data stop,
  98. // country proxy resolution, SWu tunnel, IMS registration, and SMS readiness.
  99. func (orchestrator *Orchestrator) Enable(ctx context.Context) (State, error) {
  100. if ctx == nil {
  101. ctx = context.Background()
  102. }
  103. if err := orchestrator.lockOperation(ctx); err != nil {
  104. return orchestrator.State(), err
  105. }
  106. defer orchestrator.unlockOperation()
  107. current := orchestrator.State()
  108. switch current.Phase {
  109. case PhaseSIMReady, PhaseAccessReady, PhaseTunnelReady, PhaseIMSReady, PhaseSMSReady, PhaseStopping:
  110. return current, ErrAlreadyEnabled
  111. }
  112. now := time.Now().UTC()
  113. orchestrator.mutate(func(state *State) {
  114. attempt := state.Attempt + 1
  115. sequence := state.Sequence
  116. *state = State{
  117. DeviceID: orchestrator.options.DeviceID,
  118. Phase: PhaseIdle,
  119. Enabled: true,
  120. Attempt: attempt,
  121. Sequence: sequence,
  122. StartedAt: &now,
  123. UpdatedAt: now,
  124. LastReason: "enable_requested",
  125. Security: SecurityAudit{
  126. ResponderAUTH: ResponderAUTHUnknown,
  127. },
  128. }
  129. })
  130. runtimeContext, runtimeCancel := context.WithCancel(context.Background())
  131. resources := &runtimeResources{cancel: runtimeCancel}
  132. orchestrator.mu.Lock()
  133. orchestrator.resources = resources
  134. orchestrator.mu.Unlock()
  135. setupContext, stopSetup := mergedContext(ctx, runtimeContext)
  136. defer stopSetup()
  137. fail := func(stage Phase, cause error) (State, error) {
  138. runtimeCancel()
  139. cleanupErrors := orchestrator.cleanup(resources)
  140. orchestrator.mu.Lock()
  141. orchestrator.resources = nil
  142. orchestrator.mu.Unlock()
  143. orchestrator.mutate(func(state *State) {
  144. state.Phase = PhaseFailed
  145. state.Active = false
  146. state.TunnelReady = false
  147. state.IMSReady = false
  148. state.SMSReady = false
  149. state.LastErrorClass = classifyError(stage, cause)
  150. state.LastError = cause.Error()
  151. state.LastReason = "enable_failed"
  152. state.CleanupErrors = append([]string(nil), cleanupErrors...)
  153. })
  154. stageError := error(&StageError{Stage: stage, Err: cause})
  155. if len(cleanupErrors) > 0 {
  156. stageError = errors.Join(
  157. stageError,
  158. fmt.Errorf("vowifi cleanup: %s", strings.Join(cleanupErrors, "; ")),
  159. )
  160. }
  161. return orchestrator.State(), stageError
  162. }
  163. identity, err := orchestrator.deps.SIM.ReadIdentity(setupContext, orchestrator.options.DeviceID)
  164. if err != nil {
  165. return fail(PhaseSIMReady, err)
  166. }
  167. if err := identity.validate(); err != nil {
  168. return fail(PhaseSIMReady, err)
  169. }
  170. akaEvidence, err := orchestrator.deps.AKA.CheckReady(setupContext, identity)
  171. if err != nil {
  172. return fail(PhaseSIMReady, err)
  173. }
  174. if !akaEvidence.Ready {
  175. return fail(PhaseSIMReady, errors.New("AKA application is not ready"))
  176. }
  177. if reader, ok := orchestrator.deps.SIM.(SMSCenterReader); ok {
  178. if smsc, smscErr := reader.ReadSMSCenter(setupContext, orchestrator.options.DeviceID); smscErr == nil {
  179. identity.SMSC = strings.TrimSpace(smsc)
  180. } else {
  181. orchestrator.addWarning("SIM SMS service-centre address is unavailable; IMS receive remains available: " + smscErr.Error())
  182. }
  183. }
  184. orchestrator.mutate(func(state *State) {
  185. state.Phase = PhaseSIMReady
  186. state.SIMReady = true
  187. state.HomeMCC = strings.TrimSpace(identity.HomeMCC)
  188. state.HomeMNC = strings.TrimSpace(identity.HomeMNC)
  189. state.LastReason = "sim_and_aka_ready"
  190. })
  191. epdg, err := DeriveEPDG(identity)
  192. if err != nil {
  193. return fail(PhaseAccessReady, err)
  194. }
  195. resources.radio, err = orchestrator.deps.Radio.Snapshot(setupContext, orchestrator.options.DeviceID)
  196. if err != nil {
  197. return fail(PhaseAccessReady, err)
  198. }
  199. orchestrator.mutate(func(state *State) {
  200. state.PureAirplanePolicy = resources.radio.PureAirplanePolicy
  201. })
  202. // Mark the radio transaction before the first mutating call: a provider
  203. // may return an error after partially changing the modem.
  204. resources.radioChanged = true
  205. // Enter RF-off before reconciling PDP contexts. Some QMI-capable EC20
  206. // firmware automatically owns CID 1 while CFUN=1 and rejects a direct
  207. // CGACT=0 command even though the Linux data interface is down. CFUN=4
  208. // tears down packet service at the baseband; StopCellularData then acts as
  209. // a fail-closed verification and removes any context that unexpectedly
  210. // survived RF-off.
  211. if err := orchestrator.deps.Radio.EnterVoWiFiRFOff(setupContext, orchestrator.options.DeviceID); err != nil {
  212. return fail(PhaseAccessReady, err)
  213. }
  214. if err := orchestrator.deps.Radio.StopCellularData(setupContext, orchestrator.options.DeviceID); err != nil {
  215. return fail(PhaseAccessReady, err)
  216. }
  217. proxy, err := orchestrator.deps.Proxy.Resolve(setupContext, ProxyRequest{
  218. DeviceID: orchestrator.options.DeviceID,
  219. HomeMCC: strings.TrimSpace(identity.HomeMCC),
  220. HomeMNC: strings.TrimSpace(identity.HomeMNC),
  221. CountryCode: strings.ToUpper(strings.TrimSpace(identity.HomeCountryCode)),
  222. })
  223. if err != nil {
  224. return fail(PhaseAccessReady, err)
  225. }
  226. proxy, err = normalizeProxyRoute(proxy)
  227. if err != nil {
  228. return fail(PhaseAccessReady, err)
  229. }
  230. orchestrator.mutate(func(state *State) {
  231. state.Phase = PhaseAccessReady
  232. state.AccessReady = true
  233. state.EPDG = epdg
  234. state.ProxyMode = proxy.Mode
  235. state.ProxyID = proxy.ID
  236. state.LastReason = "epdg_access_ready"
  237. })
  238. tunnel, err := orchestrator.deps.Tunnel.Start(setupContext, TunnelRequest{
  239. DeviceID: orchestrator.options.DeviceID,
  240. Identity: identity,
  241. EPDG: epdg,
  242. Proxy: proxy,
  243. AKA: orchestrator.deps.AKA,
  244. Security: TunnelSecurityPolicy{
  245. AllowMissingResponderAUTH: orchestrator.options.AllowMissingResponderAUTH,
  246. },
  247. })
  248. if err != nil {
  249. return fail(PhaseTunnelReady, err)
  250. }
  251. if tunnel == nil {
  252. return fail(PhaseTunnelReady, errors.New("tunnel provider returned a nil session"))
  253. }
  254. resources.tunnel = tunnel
  255. tunnelEvidence := tunnel.Evidence()
  256. if !tunnelEvidence.Established {
  257. orchestrator.mutate(func(state *State) {
  258. state.Security = securityAuditFromEvidence(tunnelEvidence)
  259. })
  260. return fail(PhaseTunnelReady, ErrTunnelNotEstablished)
  261. }
  262. securityAudit, err := orchestrator.validateTunnelEvidence(tunnelEvidence)
  263. orchestrator.mutate(func(state *State) {
  264. state.Security = securityAudit
  265. })
  266. if err != nil {
  267. return fail(PhaseTunnelReady, err)
  268. }
  269. orchestrator.mutate(func(state *State) {
  270. state.Phase = PhaseTunnelReady
  271. state.Active = true
  272. state.TunnelReady = true
  273. state.TunnelName = strings.TrimSpace(tunnelEvidence.Name)
  274. state.DataplaneMode = strings.TrimSpace(tunnelEvidence.DataplaneMode)
  275. state.LastReason = "ipsec_tunnel_ready"
  276. })
  277. orchestrator.watchRuntimeTunnel(runtimeContext, resources, tunnel)
  278. ims, err := orchestrator.deps.IMS.Start(setupContext, IMSRequest{
  279. DeviceID: orchestrator.options.DeviceID,
  280. Identity: identity,
  281. Tunnel: tunnel,
  282. })
  283. if err != nil {
  284. return fail(PhaseIMSReady, err)
  285. }
  286. if ims == nil {
  287. return fail(PhaseIMSReady, errors.New("IMS provider returned a nil session"))
  288. }
  289. resources.ims = ims
  290. orchestrator.watchRuntimeIMS(runtimeContext, resources, ims)
  291. imsEvidence := ims.Evidence()
  292. if !imsEvidence.Registered {
  293. return fail(PhaseIMSReady, ErrIMSNotRegistered)
  294. }
  295. orchestrator.mutate(func(state *State) {
  296. state.Phase = PhaseIMSReady
  297. state.IMSReady = true
  298. state.IMSRegistration = strings.TrimSpace(imsEvidence.RegistrationState)
  299. state.LastReason = "ims_registered"
  300. })
  301. if number, source, ok := ExtractAssociatedMSISDN(imsEvidence); ok {
  302. record := PhoneRecord{
  303. ICCID: strings.TrimSpace(identity.ICCID),
  304. Number: number,
  305. Source: source,
  306. UpdatedAt: time.Now().UTC(),
  307. }
  308. if err := orchestrator.deps.Phones.SaveAssociatedNumber(setupContext, record); err != nil {
  309. orchestrator.addWarning("IMS associated number is valid but could not be persisted: " + err.Error())
  310. } else {
  311. orchestrator.mutate(func(state *State) {
  312. state.PhoneNumber = number
  313. state.PhoneNumberSource = source
  314. })
  315. }
  316. } else {
  317. orchestrator.addWarning("IMS did not publish an associated MSISDN; the number was not inferred from IMSI")
  318. }
  319. smsEvidence, err := ims.EnableSMS(setupContext)
  320. if err != nil {
  321. if orchestrator.options.AllowIMSWithoutSMS {
  322. orchestrator.addWarning("IMS is registered but SMS capability was not confirmed: " + err.Error())
  323. orchestrator.mutate(func(state *State) {
  324. state.LastReason = "ims_registered_sms_unavailable"
  325. state.LastError = ""
  326. state.LastErrorClass = ""
  327. state.CleanupErrors = nil
  328. })
  329. return orchestrator.State(), nil
  330. }
  331. return fail(PhaseSMSReady, err)
  332. }
  333. if !smsEvidence.Ready {
  334. if orchestrator.options.AllowIMSWithoutSMS {
  335. orchestrator.addWarning("IMS is registered but SMS capability was not confirmed")
  336. orchestrator.mutate(func(state *State) {
  337. state.LastReason = "ims_registered_sms_unavailable"
  338. state.LastError = ""
  339. state.LastErrorClass = ""
  340. state.CleanupErrors = nil
  341. })
  342. return orchestrator.State(), nil
  343. }
  344. return fail(PhaseSMSReady, ErrSMSNotReady)
  345. }
  346. orchestrator.mutate(func(state *State) {
  347. state.Phase = PhaseSMSReady
  348. state.SMSReady = true
  349. state.LastReason = "sms_ready"
  350. state.LastError = ""
  351. state.LastErrorClass = ""
  352. state.CleanupErrors = nil
  353. })
  354. return orchestrator.State(), nil
  355. }
  356. // Disable is idempotent. It interrupts setup when necessary and closes IMS,
  357. // tunnel, then restores the captured radio state.
  358. func (orchestrator *Orchestrator) Disable(ctx context.Context) (State, error) {
  359. if ctx == nil {
  360. ctx = context.Background()
  361. }
  362. orchestrator.cancelCurrentRuntime()
  363. if err := orchestrator.lockOperation(ctx); err != nil {
  364. return orchestrator.State(), err
  365. }
  366. defer orchestrator.unlockOperation()
  367. orchestrator.mu.Lock()
  368. resources := orchestrator.resources
  369. orchestrator.mu.Unlock()
  370. current := orchestrator.State()
  371. if resources == nil && current.Phase == PhaseIdle {
  372. return current, nil
  373. }
  374. orchestrator.mutate(func(state *State) {
  375. state.Phase = PhaseStopping
  376. state.Enabled = false
  377. state.LastReason = "disable_requested"
  378. })
  379. if resources != nil && resources.cancel != nil {
  380. resources.cancel()
  381. }
  382. cleanupErrors := orchestrator.cleanup(resources)
  383. orchestrator.mu.Lock()
  384. orchestrator.resources = nil
  385. orchestrator.mu.Unlock()
  386. if len(cleanupErrors) > 0 {
  387. cause := fmt.Errorf("%w: %s", ErrCleanupIncomplete, strings.Join(cleanupErrors, "; "))
  388. orchestrator.mutate(func(state *State) {
  389. // cleanup() has already released every local resource and restored
  390. // the radio. A rejected best-effort SIP deregistration is useful
  391. // diagnostic evidence, but it must not leave a disabled runtime in
  392. // Failed/Stopping or prevent a later cellular/VoWiFi transition.
  393. state.Phase = PhaseIdle
  394. state.Enabled = false
  395. state.Active = false
  396. state.SIMReady = false
  397. state.AccessReady = false
  398. state.TunnelReady = false
  399. state.IMSReady = false
  400. state.SMSReady = false
  401. state.TunnelName = ""
  402. state.DataplaneMode = ""
  403. state.IMSRegistration = ""
  404. state.LastErrorClass = "cleanup_warning"
  405. state.LastError = cause.Error()
  406. state.LastReason = "disabled_with_cleanup_errors"
  407. state.CleanupErrors = append([]string(nil), cleanupErrors...)
  408. state.StartedAt = nil
  409. })
  410. return orchestrator.State(), cause
  411. }
  412. orchestrator.mutate(func(state *State) {
  413. state.Phase = PhaseIdle
  414. state.Enabled = false
  415. state.Active = false
  416. state.SIMReady = false
  417. state.AccessReady = false
  418. state.TunnelReady = false
  419. state.IMSReady = false
  420. state.SMSReady = false
  421. state.TunnelName = ""
  422. state.DataplaneMode = ""
  423. state.IMSRegistration = ""
  424. state.LastErrorClass = ""
  425. state.LastError = ""
  426. state.LastReason = "disabled"
  427. state.CleanupErrors = nil
  428. state.StartedAt = nil
  429. })
  430. return orchestrator.State(), nil
  431. }
  432. func (orchestrator *Orchestrator) Retry(ctx context.Context) (State, error) {
  433. if orchestrator.State().Phase != PhaseFailed {
  434. return orchestrator.State(), ErrRetryRequiresFailure
  435. }
  436. return orchestrator.Enable(ctx)
  437. }
  438. func (orchestrator *Orchestrator) Reconnect(ctx context.Context) (State, error) {
  439. current := orchestrator.State()
  440. if !current.Enabled && current.Phase == PhaseIdle {
  441. return current, ErrNotRunning
  442. }
  443. // Teardown during a reconnect is best-effort. Disable already releases the
  444. // local IMS, tunnel, and radio resources, so a non-fatal cleanup error
  445. // (e.g. the network rejecting SIP deregistration) must not block the
  446. // rebuild — otherwise the device wedges in PhaseFailed. Only propagate
  447. // errors that prevented the teardown itself (e.g. the operation lock).
  448. if _, err := orchestrator.Disable(ctx); err != nil && !errors.Is(err, ErrCleanupIncomplete) {
  449. return orchestrator.State(), err
  450. }
  451. return orchestrator.Enable(ctx)
  452. }
  453. // SendSMS submits through the currently registered IMS session. The lifecycle
  454. // operation lock prevents teardown from closing the session mid-transaction.
  455. func (orchestrator *Orchestrator) SendSMS(
  456. ctx context.Context,
  457. request SMSSubmitRequest,
  458. ) (SMSSubmitResult, error) {
  459. if ctx == nil {
  460. ctx = context.Background()
  461. }
  462. if err := orchestrator.lockOperation(ctx); err != nil {
  463. return SMSSubmitResult{}, err
  464. }
  465. defer orchestrator.unlockOperation()
  466. orchestrator.mu.Lock()
  467. resources := orchestrator.resources
  468. ready := orchestrator.state.IMSReady && orchestrator.state.SMSReady
  469. orchestrator.mu.Unlock()
  470. if resources == nil || resources.ims == nil || !ready {
  471. return SMSSubmitResult{}, ErrSMSNotReady
  472. }
  473. sender, ok := resources.ims.(SMSSender)
  474. if !ok {
  475. return SMSSubmitResult{}, ErrSMSNotReady
  476. }
  477. return sender.SendSMS(ctx, request)
  478. }
  479. func (orchestrator *Orchestrator) Close(ctx context.Context) error {
  480. _, err := orchestrator.Disable(ctx)
  481. return err
  482. }
  483. // DeriveEPDG uses an explicitly provided carrier endpoint or the 3GPP standard
  484. // home-PLMN form. It never derives a phone number or MNC length from IMSI.
  485. func DeriveEPDG(identity SIMIdentity) (string, error) {
  486. if configured := strings.TrimSpace(identity.EPDG); configured != "" {
  487. if strings.ContainsAny(configured, " \t\r\n/:") || len(configured) > 253 {
  488. return "", errors.New("vowifi: configured ePDG must be a hostname")
  489. }
  490. return strings.ToLower(configured), nil
  491. }
  492. if err := identity.validate(); err != nil {
  493. return "", err
  494. }
  495. mnc := strings.TrimSpace(identity.HomeMNC)
  496. for len(mnc) < 3 {
  497. mnc = "0" + mnc
  498. }
  499. return fmt.Sprintf(
  500. "epdg.epc.mnc%s.mcc%s.pub.3gppnetwork.org",
  501. mnc,
  502. strings.TrimSpace(identity.HomeMCC),
  503. ), nil
  504. }
  505. func normalizeProxyRoute(route ProxyRoute) (ProxyRoute, error) {
  506. if route.Mode == "" {
  507. route.Mode = ProxyModeDirect
  508. }
  509. switch route.Mode {
  510. case ProxyModeDirect:
  511. route.Address = ""
  512. route.Username = ""
  513. route.Password = ""
  514. case ProxyModeSOCKS5:
  515. if strings.TrimSpace(route.Address) == "" {
  516. return ProxyRoute{}, errors.New("vowifi: SOCKS5 proxy address is empty")
  517. }
  518. default:
  519. return ProxyRoute{}, fmt.Errorf("vowifi: unsupported proxy mode %q", route.Mode)
  520. }
  521. route.ID = strings.TrimSpace(route.ID)
  522. route.Address = strings.TrimSpace(route.Address)
  523. return route, nil
  524. }
  525. func (orchestrator *Orchestrator) validateTunnelEvidence(evidence TunnelEvidence) (SecurityAudit, error) {
  526. audit := securityAuditFromEvidence(evidence)
  527. switch evidence.ResponderAUTH {
  528. case ResponderAUTHVerified:
  529. return audit, nil
  530. case ResponderAUTHMissing:
  531. if !orchestrator.options.AllowMissingResponderAUTH {
  532. return audit, ErrResponderAUTHRequired
  533. }
  534. audit.CompatibilityOverride = true
  535. audit.HighRisk = true
  536. audit.Level = AuditLevelHigh
  537. audit.Code = AuditCodeMissingResponderAUTH
  538. audit.Message = "IKE responder AUTH was missing and accepted by explicit compatibility policy"
  539. return audit, nil
  540. case ResponderAUTHInvalid:
  541. return audit, fmt.Errorf("%w: responder AUTH is invalid", ErrResponderAUTHRequired)
  542. default:
  543. return audit, fmt.Errorf("%w: responder AUTH evidence is unknown", ErrResponderAUTHRequired)
  544. }
  545. }
  546. func securityAuditFromEvidence(evidence TunnelEvidence) SecurityAudit {
  547. return SecurityAudit{
  548. ResponderAUTH: evidence.ResponderAUTH,
  549. IKEEncryption: strings.TrimSpace(evidence.IKEEncryption),
  550. IKEIntegrity: strings.TrimSpace(evidence.IKEIntegrity),
  551. IKEDHGroup: strings.TrimSpace(evidence.IKEDHGroup),
  552. ESPEncryption: strings.TrimSpace(evidence.ESPEncryption),
  553. ESPIntegrity: strings.TrimSpace(evidence.ESPIntegrity),
  554. }
  555. }
  556. func (orchestrator *Orchestrator) cleanup(resources *runtimeResources) []string {
  557. if resources == nil {
  558. return nil
  559. }
  560. var cleanupErrors []string
  561. if resources.ims != nil {
  562. if err := orchestrator.cleanupCall(resources.ims.Close); err != nil {
  563. cleanupErrors = append(cleanupErrors, "close IMS: "+err.Error())
  564. }
  565. resources.ims = nil
  566. }
  567. if resources.tunnel != nil {
  568. if err := orchestrator.cleanupCall(resources.tunnel.Close); err != nil {
  569. cleanupErrors = append(cleanupErrors, "close tunnel: "+err.Error())
  570. }
  571. resources.tunnel = nil
  572. }
  573. if resources.radioChanged {
  574. if err := orchestrator.cleanupCall(func(ctx context.Context) error {
  575. return orchestrator.deps.Radio.Restore(ctx, orchestrator.options.DeviceID, resources.radio)
  576. }); err != nil {
  577. cleanupErrors = append(cleanupErrors, "restore radio: "+err.Error())
  578. }
  579. resources.radioChanged = false
  580. }
  581. return cleanupErrors
  582. }
  583. func (orchestrator *Orchestrator) cleanupCall(call func(context.Context) error) error {
  584. ctx, cancel := context.WithTimeout(context.Background(), orchestrator.options.CleanupTimeout)
  585. defer cancel()
  586. return call(ctx)
  587. }
  588. func (orchestrator *Orchestrator) cancelCurrentRuntime() {
  589. orchestrator.mu.Lock()
  590. resources := orchestrator.resources
  591. orchestrator.mu.Unlock()
  592. if resources != nil && resources.cancel != nil {
  593. resources.cancel()
  594. }
  595. }
  596. func (orchestrator *Orchestrator) watchRuntimeTunnel(
  597. runtimeContext context.Context,
  598. resources *runtimeResources,
  599. tunnel TunnelSession,
  600. ) {
  601. notifier, ok := tunnel.(RuntimeFailureNotifier)
  602. if !ok {
  603. return
  604. }
  605. orchestrator.watchRuntimeFailure(
  606. runtimeContext,
  607. resources,
  608. notifier,
  609. "tunnel_runtime",
  610. "runtime_tunnel_failed",
  611. )
  612. }
  613. func (orchestrator *Orchestrator) watchRuntimeIMS(
  614. runtimeContext context.Context,
  615. resources *runtimeResources,
  616. ims IMSSession,
  617. ) {
  618. notifier, ok := ims.(RuntimeFailureNotifier)
  619. if !ok {
  620. return
  621. }
  622. orchestrator.watchRuntimeFailure(
  623. runtimeContext,
  624. resources,
  625. notifier,
  626. "ims_runtime",
  627. "runtime_ims_failed",
  628. )
  629. }
  630. func (orchestrator *Orchestrator) watchRuntimeFailure(
  631. runtimeContext context.Context,
  632. resources *runtimeResources,
  633. notifier RuntimeFailureNotifier,
  634. errorClass string,
  635. reason string,
  636. ) {
  637. failures := notifier.Failures()
  638. if failures == nil {
  639. return
  640. }
  641. go func() {
  642. select {
  643. case <-runtimeContext.Done():
  644. return
  645. case cause := <-failures:
  646. if cause == nil {
  647. cause = errors.New("VoWiFi runtime session stopped")
  648. }
  649. // Interrupt any still-running IMS setup before waiting for the
  650. // serialized lifecycle lock.
  651. if resources.cancel != nil {
  652. resources.cancel()
  653. }
  654. if err := orchestrator.lockOperation(context.Background()); err != nil {
  655. return
  656. }
  657. defer orchestrator.unlockOperation()
  658. orchestrator.mu.Lock()
  659. current := orchestrator.resources == resources
  660. orchestrator.mu.Unlock()
  661. if !current {
  662. return
  663. }
  664. cleanupErrors := orchestrator.cleanup(resources)
  665. orchestrator.mu.Lock()
  666. if orchestrator.resources == resources {
  667. orchestrator.resources = nil
  668. }
  669. orchestrator.mu.Unlock()
  670. orchestrator.mutate(func(state *State) {
  671. state.Phase = PhaseFailed
  672. state.Active = false
  673. state.TunnelReady = false
  674. state.IMSReady = false
  675. state.SMSReady = false
  676. state.LastErrorClass = errorClass
  677. state.LastError = cause.Error()
  678. state.LastReason = reason
  679. state.CleanupErrors = append([]string(nil), cleanupErrors...)
  680. })
  681. }
  682. }()
  683. }
  684. func (orchestrator *Orchestrator) lockOperation(ctx context.Context) error {
  685. select {
  686. case <-ctx.Done():
  687. return ctx.Err()
  688. case <-orchestrator.operation:
  689. return nil
  690. }
  691. }
  692. func (orchestrator *Orchestrator) unlockOperation() {
  693. orchestrator.operation <- struct{}{}
  694. }
  695. func (orchestrator *Orchestrator) mutate(change func(*State)) {
  696. orchestrator.mu.Lock()
  697. change(&orchestrator.state)
  698. orchestrator.state.Sequence++
  699. orchestrator.state.UpdatedAt = time.Now().UTC()
  700. snapshot := orchestrator.state.clone()
  701. for _, subscriber := range orchestrator.subscribers {
  702. select {
  703. case subscriber <- snapshot:
  704. default:
  705. select {
  706. case <-subscriber:
  707. default:
  708. }
  709. select {
  710. case subscriber <- snapshot:
  711. default:
  712. }
  713. }
  714. }
  715. orchestrator.mu.Unlock()
  716. }
  717. func (orchestrator *Orchestrator) addWarning(warning string) {
  718. orchestrator.mutate(func(state *State) {
  719. state.Warnings = append(state.Warnings, warning)
  720. })
  721. }
  722. func classifyError(stage Phase, err error) string {
  723. switch {
  724. case errors.Is(err, context.Canceled):
  725. return "canceled"
  726. case errors.Is(err, context.DeadlineExceeded):
  727. return "timeout"
  728. case isTimeoutError(err):
  729. return "network_timeout"
  730. case errors.Is(err, ErrInvalidIdentity):
  731. return "sim_identity"
  732. case errors.Is(err, ErrEAPAuthenticationRejected):
  733. return "eap_authentication_rejected"
  734. case errors.Is(err, ErrResponderAUTHRequired):
  735. return "responder_auth"
  736. case errors.Is(err, ErrTunnelNotEstablished):
  737. return "tunnel"
  738. case errors.Is(err, ErrIMSNotRegistered):
  739. return "ims_registration"
  740. case errors.Is(err, ErrSMSNotReady):
  741. return "sms"
  742. default:
  743. return string(stage)
  744. }
  745. }
  746. func isTimeoutError(err error) bool {
  747. var networkError net.Error
  748. return errors.As(err, &networkError) && networkError.Timeout()
  749. }
  750. func mergedContext(caller context.Context, runtime context.Context) (context.Context, func()) {
  751. merged, cancel := context.WithCancel(caller)
  752. stop := context.AfterFunc(runtime, cancel)
  753. return merged, func() {
  754. stop()
  755. cancel()
  756. }
  757. }