Как вызвать внутренний API из приложения iPhone через VPN
Классифицируйте неудавшийся запрос по его коду NSURLError, прежде чем трогать туннель, а затем исправьте именно тот слой, который действительно сломался.
API отвечает в Safari на том же телефоне, а запрос приложения завершается тайм-аутом. Этот разрыв обычно не в туннеле: приложение, обращающееся к внутреннему сервису, делает обычный HTTPS-запрос, а существующий VPN на устройстве решает только, есть ли у этого запроса путь. Встроить в приложение второй туннель — это продуктовое решение, а не починка тайм-аута. Далее неудача классифицируется по месту возникновения с использованием той поверхности ошибок, которая у приложения уже есть.
Начинайте с ошибки, а не с туннеля
URLSession сообщает о транспортных сбоях как о NSError значениях в NSURLErrorDomain, и код — самое дешёвое доступное доказательство. Прежде чем менять любую конфигурацию, залогируйте этот код вместе с именем хоста, которое сбоит, и прошедшим временем задачи. Тайм-аут, сбой имени и сбой доверия выглядят на экране одинаково и требуют трёх разных исправлений. Сначала классифицируйте, затем читайте раздел для этого класса.
Метрики задачи отвечают на следующий вопрос, было ли соединение вообще установлено, что отделяет запрос, у которого никогда не было маршрута, от того, на который сервер отвечал слишком долго. Ничего из этого не требует менять туннель; требуется напечатать диагностику самого запроса там, где вы сможете её прочитать.
Разрешение имён сбоит раньше всего остального
Короткое офисное имя вроде api.corp.example обычно зависит от домена поиска, которого нет на телефоне, поэтому приложение разрешает имя, существующее только внутри здания. NSURLErrorCannotFindHost — это подпись. Полное имя хоста должно разрешаться через любой резолвер, который установит VPN, и приложение должно запрашивать полностью квалифицированную форму всякий раз, когда домен поиска не гарантирован. Замена имени на адрес и отключение проверок сертификатов перемещает сбой, а не исправляет его.
Приватная запись DNS и сертификат, выпущенный именно на это имя, — поддерживаемая пара. Всё остальное — обходной путь, который доходит до пользователей.
Тайм-аут — это вопрос пути, а не имени
NSURLErrorTimedOut означает, что имя разрешилось и никто не ответил, что указывает на маршрут, политику пакетов или сервис. Когда туннель назначается для каждого приложения, другое приложение, достигающее того же сервера, ничего не говорит о вашей сборке, потому что это сопоставление не является общим. Проверьте, что запускаемый пакет находится внутри этого назначения, затем проверьте, что путь к сервису для него разрешён. Сборка для разработки, установленная вне назначения, может находиться полностью вне туннеля и выглядеть как сломанный API.
Сравнение в браузере показывает, что у какого-то приложения на устройстве есть путь. Оно никогда не доказывает, что он есть у вашей сборки, поэтому оба теста нужно проводить отдельно.
Сбои доверия связаны с именем в сертификате
NSURLErrorServerCertificateUntrusted вместе с ошибкой TLS под ним означает, что цепочка или проверка подлинности не прошла. Внутренний CA должен быть доверенным на устройстве, а сертификат, выпущенный на другое имя хоста, остаётся неверным после любой перенастройки сети. Руководство App Transport Security от Apple по-прежнему применяется к этим соединениям, загружающим URL, и туннель вокруг простого HTTP-сервиса не делает этот сервис безопасным от края до края.
Исправьте идентичность или якорь доверия и держите список исключений пустым. Общее исключение для произвольной загрузки скрывает сбой во время разработки и оставляет его на месте для каждого пользователя после.
Отказ HTTP принадлежит приложению, а не сети
401 или 403 после завершённого рукопожатия означает, что запрос дошёл и сервис его отклонил, поэтому оставшийся вопрос — это учётная запись, аудитория токена, область или ключ. Записывайте код состояния и имя хоста, но никогда не bearer-токен и не тело ответа. В этот момент туннель делает свою работу, а дальнейшие изменения сети только откладывают настоящее исправление.
Таблица — это классификация, которую стоит держать рядом с терминалом: одна строка на каждый сбой, о котором приложение действительно может сообщить. Напечатайте код прежде, чем читать его, потому что выбранная строка определяет, какой раздел вы проработаете.
| Where it fails | What the app sees | First check |
|---|---|---|
| name resolution | NSURLErrorCannotFindHost (-1003) | resolve the fully qualified name from the device itself |
| no path, or no answer | NSURLErrorTimedOut (-1001) | is this bundle inside the per-app mapping, and is the service path allowed |
| TLS trust | NSURLErrorServerCertificateUntrusted (-1202) | does the certificate name match the host, and is the internal CA trusted |
| service refusal | HTTP 401 or 403 after a completed handshake | account, token audience, scope or key |
Каждую строку дешевле проверить, чем ту, что под ней, и каждая исключает свой слой того же запроса. Спускайтесь по таблице, пока строка не совпадёт, затем прекратите менять сеть.