pymobiledevice3 5.0.0__py3-none-any.whl → 5.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pymobiledevice3 might be problematic. Click here for more details.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- pymobiledevice3/__main__.py +128 -102
- pymobiledevice3/_version.py +2 -2
- pymobiledevice3/bonjour.py +26 -49
- pymobiledevice3/ca.py +32 -24
- pymobiledevice3/cli/activation.py +7 -7
- pymobiledevice3/cli/afc.py +19 -19
- pymobiledevice3/cli/amfi.py +4 -4
- pymobiledevice3/cli/apps.py +51 -39
- pymobiledevice3/cli/backup.py +58 -32
- pymobiledevice3/cli/bonjour.py +25 -18
- pymobiledevice3/cli/cli_common.py +112 -81
- pymobiledevice3/cli/companion_proxy.py +4 -4
- pymobiledevice3/cli/completions.py +10 -10
- pymobiledevice3/cli/crash.py +37 -31
- pymobiledevice3/cli/developer.py +602 -520
- pymobiledevice3/cli/diagnostics.py +38 -33
- pymobiledevice3/cli/lockdown.py +79 -74
- pymobiledevice3/cli/mounter.py +85 -68
- pymobiledevice3/cli/notification.py +10 -10
- pymobiledevice3/cli/pcap.py +19 -14
- pymobiledevice3/cli/power_assertion.py +12 -10
- pymobiledevice3/cli/processes.py +10 -10
- pymobiledevice3/cli/profile.py +88 -77
- pymobiledevice3/cli/provision.py +17 -17
- pymobiledevice3/cli/remote.py +186 -110
- pymobiledevice3/cli/restore.py +43 -40
- pymobiledevice3/cli/springboard.py +30 -28
- pymobiledevice3/cli/syslog.py +85 -58
- pymobiledevice3/cli/usbmux.py +21 -20
- pymobiledevice3/cli/version.py +3 -2
- pymobiledevice3/cli/webinspector.py +157 -79
- pymobiledevice3/common.py +1 -1
- pymobiledevice3/exceptions.py +154 -60
- pymobiledevice3/irecv.py +49 -53
- pymobiledevice3/irecv_devices.py +1489 -492
- pymobiledevice3/lockdown.py +394 -241
- pymobiledevice3/lockdown_service_provider.py +5 -7
- pymobiledevice3/osu/os_utils.py +18 -9
- pymobiledevice3/osu/posix_util.py +28 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +19 -19
- pymobiledevice3/remote/common.py +4 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +10 -8
- pymobiledevice3/remote/core_device/file_service.py +47 -33
- pymobiledevice3/remote/remote_service_discovery.py +53 -35
- pymobiledevice3/remote/remotexpc.py +62 -41
- pymobiledevice3/remote/tunnel_service.py +371 -293
- pymobiledevice3/remote/utils.py +12 -11
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +16 -16
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +90 -47
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +11 -11
- pymobiledevice3/restore/fdr.py +46 -46
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +130 -133
- pymobiledevice3/restore/mbn.py +35 -54
- pymobiledevice3/restore/recovery.py +125 -135
- pymobiledevice3/restore/restore.py +524 -523
- pymobiledevice3/restore/restore_options.py +122 -115
- pymobiledevice3/restore/restored_client.py +25 -22
- pymobiledevice3/restore/tss.py +378 -270
- pymobiledevice3/service_connection.py +50 -46
- pymobiledevice3/services/accessibilityaudit.py +136 -126
- pymobiledevice3/services/afc.py +350 -291
- pymobiledevice3/services/amfi.py +21 -18
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +60 -46
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +55 -47
- pymobiledevice3/services/diagnostics.py +971 -968
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +11 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +25 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +40 -50
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +8 -7
- pymobiledevice3/services/house_arrest.py +12 -15
- pymobiledevice3/services/installation_proxy.py +119 -100
- pymobiledevice3/services/lockdown_service.py +12 -5
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +84 -72
- pymobiledevice3/services/mobile_config.py +330 -301
- pymobiledevice3/services/mobile_image_mounter.py +137 -116
- pymobiledevice3/services/mobilebackup2.py +188 -150
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +69 -51
- pymobiledevice3/services/pcapd.py +306 -306
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +16 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +13 -10
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +3 -3
- pymobiledevice3/services/web_protocol/automation_session.py +180 -176
- pymobiledevice3/services/web_protocol/cdp_screencast.py +44 -36
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +47 -45
- pymobiledevice3/services/web_protocol/element.py +74 -63
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +2 -2
- pymobiledevice3/services/web_protocol/session_protocol.py +15 -10
- pymobiledevice3/services/web_protocol/switch_to.py +11 -12
- pymobiledevice3/services/webinspector.py +127 -116
- pymobiledevice3/tcp_forwarder.py +35 -22
- pymobiledevice3/tunneld/api.py +20 -15
- pymobiledevice3/tunneld/server.py +212 -133
- pymobiledevice3/usbmux.py +183 -138
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/METADATA +1 -1
- pymobiledevice3-5.0.2.dist-info/RECORD +173 -0
- pymobiledevice3-5.0.0.dist-info/RECORD +0 -173
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/WHEEL +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/licenses/LICENSE +0 -0
- {pymobiledevice3-5.0.0.dist-info → pymobiledevice3-5.0.2.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,7 @@ from pymobiledevice3.bonjour import browse_remoted
|
|
|
16
16
|
|
|
17
17
|
with warnings.catch_warnings():
|
|
18
18
|
# Ignore: "Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater."
|
|
19
|
-
warnings.simplefilter(
|
|
19
|
+
warnings.simplefilter("ignore", category=UserWarning)
|
|
20
20
|
import fastapi
|
|
21
21
|
|
|
22
22
|
import uvicorn
|
|
@@ -25,16 +25,30 @@ from fastapi import FastAPI
|
|
|
25
25
|
from packaging.version import Version
|
|
26
26
|
|
|
27
27
|
from pymobiledevice3 import usbmux
|
|
28
|
-
from pymobiledevice3.exceptions import
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
from pymobiledevice3.exceptions import (
|
|
29
|
+
ConnectionFailedError,
|
|
30
|
+
ConnectionFailedToUsbmuxdError,
|
|
31
|
+
DeviceNotFoundError,
|
|
32
|
+
GetProhibitedError,
|
|
33
|
+
IncorrectModeError,
|
|
34
|
+
InvalidServiceError,
|
|
35
|
+
LockdownError,
|
|
36
|
+
MuxException,
|
|
37
|
+
PairingError,
|
|
38
|
+
StreamClosedError,
|
|
39
|
+
)
|
|
31
40
|
from pymobiledevice3.lockdown import create_using_usbmux, get_mobdev2_lockdowns
|
|
32
41
|
from pymobiledevice3.osu.os_utils import get_os_utils
|
|
33
42
|
from pymobiledevice3.remote.common import TunnelProtocol
|
|
34
43
|
from pymobiledevice3.remote.module_imports import start_tunnel
|
|
35
44
|
from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService
|
|
36
|
-
from pymobiledevice3.remote.tunnel_service import
|
|
37
|
-
|
|
45
|
+
from pymobiledevice3.remote.tunnel_service import (
|
|
46
|
+
CoreDeviceTunnelProxy,
|
|
47
|
+
RemotePairingProtocol,
|
|
48
|
+
TunnelResult,
|
|
49
|
+
create_core_device_tunnel_service_using_rsd,
|
|
50
|
+
get_remote_pairing_tunnel_services,
|
|
51
|
+
)
|
|
38
52
|
from pymobiledevice3.remote.utils import get_rsds, stop_remoted
|
|
39
53
|
from pymobiledevice3.utils import asyncio_print_traceback
|
|
40
54
|
|
|
@@ -59,8 +73,14 @@ class TunnelTask:
|
|
|
59
73
|
|
|
60
74
|
|
|
61
75
|
class TunneldCore:
|
|
62
|
-
def __init__(
|
|
63
|
-
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
|
|
79
|
+
wifi_monitor: bool = True,
|
|
80
|
+
usb_monitor: bool = True,
|
|
81
|
+
usbmux_monitor: bool = True,
|
|
82
|
+
mobdev2_monitor: bool = True,
|
|
83
|
+
) -> None:
|
|
64
84
|
self.protocol = protocol
|
|
65
85
|
self.tasks: list[asyncio.Task] = []
|
|
66
86
|
self.tunnel_tasks: dict[str, TunnelTask] = {}
|
|
@@ -70,24 +90,24 @@ class TunneldCore:
|
|
|
70
90
|
self.mobdev2_monitor = mobdev2_monitor
|
|
71
91
|
|
|
72
92
|
def start(self) -> None:
|
|
73
|
-
"""
|
|
93
|
+
"""Register all tasks"""
|
|
74
94
|
self.tasks = []
|
|
75
95
|
if self.usb_monitor:
|
|
76
|
-
self.tasks.append(asyncio.create_task(self.monitor_usb_task(), name=
|
|
96
|
+
self.tasks.append(asyncio.create_task(self.monitor_usb_task(), name="monitor-usb-task"))
|
|
77
97
|
if self.wifi_monitor:
|
|
78
|
-
self.tasks.append(asyncio.create_task(self.monitor_wifi_task(), name=
|
|
98
|
+
self.tasks.append(asyncio.create_task(self.monitor_wifi_task(), name="monitor-wifi-task"))
|
|
79
99
|
if self.usbmux_monitor:
|
|
80
|
-
self.tasks.append(asyncio.create_task(self.monitor_usbmux_task(), name=
|
|
100
|
+
self.tasks.append(asyncio.create_task(self.monitor_usbmux_task(), name="monitor-usbmux-task"))
|
|
81
101
|
if self.mobdev2_monitor:
|
|
82
|
-
self.tasks.append(asyncio.create_task(self.monitor_mobdev2_task(), name=
|
|
102
|
+
self.tasks.append(asyncio.create_task(self.monitor_mobdev2_task(), name="monitor-mobdev2-task"))
|
|
83
103
|
|
|
84
104
|
def tunnel_exists_for_udid(self, udid: str) -> bool:
|
|
85
105
|
for task in self.tunnel_tasks.values():
|
|
86
106
|
# Linux implementations of `usbmuxd` may report an incorrect value of UDID, dismissing the `-` character.
|
|
87
107
|
# For such cases, we also check for a UDID without it.
|
|
88
108
|
# See: <https://github.com/doronz88/pymobiledevice3/issues/1388#issuecomment-2782249770>
|
|
89
|
-
task_udid = task.udid or
|
|
90
|
-
if ((task_udid == udid) or (task_udid.replace(
|
|
109
|
+
task_udid = task.udid or ""
|
|
110
|
+
if ((task_udid == udid) or (task_udid.replace("-", "") == udid)) and (task.tunnel is not None):
|
|
91
111
|
return True
|
|
92
112
|
|
|
93
113
|
return False
|
|
@@ -116,16 +136,18 @@ class TunneldCore:
|
|
|
116
136
|
# A new interface was attached
|
|
117
137
|
for answer in await browse_remoted():
|
|
118
138
|
for address in answer.addresses:
|
|
119
|
-
if address.iface.startswith(
|
|
139
|
+
if address.iface.startswith("utun"):
|
|
120
140
|
# Skip already established tunnels
|
|
121
141
|
continue
|
|
122
|
-
if address.full_ip in self.tunnel_tasks
|
|
142
|
+
if address.full_ip in self.tunnel_tasks:
|
|
123
143
|
# Skip already established tunnels
|
|
124
144
|
continue
|
|
125
145
|
self.tunnel_tasks[address.full_ip] = TunnelTask(
|
|
126
146
|
task=asyncio.create_task(
|
|
127
147
|
self.handle_new_potential_usb_cdc_ncm_interface_task(address.full_ip),
|
|
128
|
-
name=f
|
|
148
|
+
name=f"handle-new-potential-usb-cdc-ncm-interface-task-{address.full_ip}",
|
|
149
|
+
)
|
|
150
|
+
)
|
|
129
151
|
|
|
130
152
|
# wait before re-iterating
|
|
131
153
|
await asyncio.sleep(1)
|
|
@@ -148,9 +170,11 @@ class TunneldCore:
|
|
|
148
170
|
await service.close()
|
|
149
171
|
continue
|
|
150
172
|
self.tunnel_tasks[service.hostname] = TunnelTask(
|
|
151
|
-
task=asyncio.create_task(
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
task=asyncio.create_task(
|
|
174
|
+
self.start_tunnel_task(service.hostname, service),
|
|
175
|
+
name=f"start-tunnel-task-wifi-{service.hostname}",
|
|
176
|
+
),
|
|
177
|
+
udid=service.remote_identifier,
|
|
154
178
|
)
|
|
155
179
|
except asyncio.exceptions.IncompleteReadError:
|
|
156
180
|
continue
|
|
@@ -158,7 +182,9 @@ class TunneldCore:
|
|
|
158
182
|
# Raise and cancel gracefully
|
|
159
183
|
raise
|
|
160
184
|
except Exception:
|
|
161
|
-
logger.
|
|
185
|
+
logger.exception(
|
|
186
|
+
f"Got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}"
|
|
187
|
+
)
|
|
162
188
|
continue
|
|
163
189
|
await asyncio.sleep(REMOTEPAIRING_INTERVAL)
|
|
164
190
|
except asyncio.CancelledError:
|
|
@@ -171,7 +197,7 @@ class TunneldCore:
|
|
|
171
197
|
while True:
|
|
172
198
|
try:
|
|
173
199
|
for mux_device in usbmux.list_devices():
|
|
174
|
-
task_identifier = f
|
|
200
|
+
task_identifier = f"usbmux-{mux_device.serial}-{mux_device.connection_type}"
|
|
175
201
|
if self.tunnel_exists_for_udid(mux_device.serial):
|
|
176
202
|
# Skip if already established a tunnel for this udid
|
|
177
203
|
continue
|
|
@@ -182,23 +208,31 @@ class TunneldCore:
|
|
|
182
208
|
try:
|
|
183
209
|
with create_using_usbmux(mux_device.serial) as lockdown:
|
|
184
210
|
service = await CoreDeviceTunnelProxy.create(lockdown)
|
|
185
|
-
except (
|
|
186
|
-
|
|
187
|
-
|
|
211
|
+
except (
|
|
212
|
+
MuxException,
|
|
213
|
+
InvalidServiceError,
|
|
214
|
+
GetProhibitedError,
|
|
215
|
+
construct.core.StreamError,
|
|
216
|
+
ConnectionAbortedError,
|
|
217
|
+
DeviceNotFoundError,
|
|
218
|
+
LockdownError,
|
|
219
|
+
IncorrectModeError,
|
|
220
|
+
SSLEOFError,
|
|
221
|
+
):
|
|
188
222
|
if service is not None:
|
|
189
223
|
await service.close()
|
|
190
224
|
continue
|
|
191
225
|
self.tunnel_tasks[task_identifier] = TunnelTask(
|
|
192
226
|
udid=mux_device.serial,
|
|
193
227
|
task=asyncio.create_task(
|
|
194
|
-
self.start_tunnel_task(task_identifier,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
228
|
+
self.start_tunnel_task(task_identifier, service, protocol=TunnelProtocol.TCP),
|
|
229
|
+
name=f"start-tunnel-task-{task_identifier}",
|
|
230
|
+
),
|
|
231
|
+
)
|
|
198
232
|
except ConnectionFailedToUsbmuxdError:
|
|
199
233
|
# This is exception is expected to occur repeatedly on linux running usbmuxd
|
|
200
234
|
# as long as there isn't any physical iDevice connected
|
|
201
|
-
logger.debug(
|
|
235
|
+
logger.debug("failed to connect to usbmux. waiting for it to restart")
|
|
202
236
|
finally:
|
|
203
237
|
await asyncio.sleep(USBMUX_INTERVAL)
|
|
204
238
|
except asyncio.CancelledError:
|
|
@@ -211,7 +245,7 @@ class TunneldCore:
|
|
|
211
245
|
async for ip, lockdown in get_mobdev2_lockdowns(only_paired=True):
|
|
212
246
|
with lockdown:
|
|
213
247
|
udid = lockdown.udid
|
|
214
|
-
task_identifier = f
|
|
248
|
+
task_identifier = f"mobdev2-{udid}-{ip}"
|
|
215
249
|
if self.tunnel_exists_for_udid(udid):
|
|
216
250
|
# Skip tunnel if already exists for this udid
|
|
217
251
|
continue
|
|
@@ -221,12 +255,14 @@ class TunneldCore:
|
|
|
221
255
|
try:
|
|
222
256
|
tunnel_service = await CoreDeviceTunnelProxy.create(lockdown)
|
|
223
257
|
except InvalidServiceError:
|
|
224
|
-
logger.warning(f
|
|
258
|
+
logger.warning(f"[{task_identifier}] failed to start CoreDeviceTunnelProxy - skipping")
|
|
225
259
|
continue
|
|
226
260
|
self.tunnel_tasks[task_identifier] = TunnelTask(
|
|
227
|
-
task=asyncio.create_task(
|
|
228
|
-
|
|
229
|
-
|
|
261
|
+
task=asyncio.create_task(
|
|
262
|
+
self.start_tunnel_task(task_identifier, tunnel_service),
|
|
263
|
+
name=f"start-tunnel-task-{task_identifier}",
|
|
264
|
+
),
|
|
265
|
+
udid=udid,
|
|
230
266
|
)
|
|
231
267
|
await asyncio.sleep(MOBDEV2_INTERVAL)
|
|
232
268
|
except asyncio.CancelledError:
|
|
@@ -234,8 +270,12 @@ class TunneldCore:
|
|
|
234
270
|
|
|
235
271
|
@asyncio_print_traceback
|
|
236
272
|
async def start_tunnel_task(
|
|
237
|
-
|
|
238
|
-
|
|
273
|
+
self,
|
|
274
|
+
task_identifier: str,
|
|
275
|
+
protocol_handler: Union[RemotePairingProtocol, CoreDeviceTunnelProxy],
|
|
276
|
+
queue: Optional[asyncio.Queue] = None,
|
|
277
|
+
protocol: Optional[TunnelProtocol] = None,
|
|
278
|
+
) -> None:
|
|
239
279
|
if protocol is None:
|
|
240
280
|
protocol = self.protocol
|
|
241
281
|
if isinstance(protocol_handler, CoreDeviceTunnelProxy):
|
|
@@ -255,37 +295,42 @@ class TunneldCore:
|
|
|
255
295
|
queue.put_nowait(tun)
|
|
256
296
|
# avoid sending another message if succeeded
|
|
257
297
|
queue = None
|
|
258
|
-
logger.info(f
|
|
298
|
+
logger.info(f"[{asyncio.current_task().get_name()}] Created tunnel --rsd {tun.address} {tun.port}")
|
|
259
299
|
await tun.client.wait_closed()
|
|
260
300
|
else:
|
|
261
301
|
bailed_out = True
|
|
262
302
|
logger.debug(
|
|
263
|
-
f
|
|
264
|
-
f
|
|
303
|
+
f"[{asyncio.current_task().get_name()}] Not establishing tunnel since there is already an "
|
|
304
|
+
f"active one for same udid"
|
|
305
|
+
)
|
|
265
306
|
except asyncio.CancelledError:
|
|
266
307
|
pass
|
|
267
|
-
except (
|
|
268
|
-
|
|
308
|
+
except (
|
|
309
|
+
asyncio.exceptions.IncompleteReadError,
|
|
310
|
+
TimeoutError,
|
|
311
|
+
OSError,
|
|
312
|
+
ConnectionResetError,
|
|
313
|
+
StreamError,
|
|
314
|
+
InvalidServiceError,
|
|
315
|
+
) as e:
|
|
269
316
|
if tun is None:
|
|
270
|
-
logger.debug(f
|
|
317
|
+
logger.debug(f"got {e.__class__.__name__} from {asyncio.current_task().get_name()}")
|
|
271
318
|
else:
|
|
272
|
-
logger.debug(f
|
|
319
|
+
logger.debug(f"got {e.__class__.__name__} from tunnel --rsd {tun.address} {tun.port}")
|
|
273
320
|
except Exception:
|
|
274
|
-
logger.
|
|
321
|
+
logger.exception(f"got exception from {asyncio.current_task().get_name()}: {traceback.format_exc()}")
|
|
275
322
|
finally:
|
|
276
323
|
if queue is not None:
|
|
277
324
|
# notify something went wrong
|
|
278
325
|
queue.put_nowait(None)
|
|
279
326
|
|
|
280
327
|
if tun is not None and not bailed_out:
|
|
281
|
-
logger.info(f
|
|
328
|
+
logger.info(f"disconnected from tunnel --rsd {tun.address} {tun.port}")
|
|
282
329
|
await tun.client.stop_tunnel()
|
|
283
330
|
|
|
284
331
|
if protocol_handler is not None:
|
|
285
|
-
|
|
332
|
+
with suppress(OSError):
|
|
286
333
|
await protocol_handler.close()
|
|
287
|
-
except OSError:
|
|
288
|
-
pass
|
|
289
334
|
|
|
290
335
|
if task_identifier in self.tunnel_tasks:
|
|
291
336
|
# in case the tunnel was removed just now
|
|
@@ -309,47 +354,46 @@ class TunneldCore:
|
|
|
309
354
|
# Could be on first try because of remoted race
|
|
310
355
|
if first_time:
|
|
311
356
|
retry = True
|
|
312
|
-
except (ConnectionRefusedError, TimeoutError, OSError):
|
|
313
|
-
raise asyncio.CancelledError()
|
|
357
|
+
except (ConnectionRefusedError, TimeoutError, OSError) as e:
|
|
358
|
+
raise asyncio.CancelledError() from e
|
|
314
359
|
finally:
|
|
315
360
|
first_time = False
|
|
316
361
|
|
|
317
|
-
if (self.protocol == TunnelProtocol.QUIC) and (Version(rsd.product_version) < Version(
|
|
362
|
+
if (self.protocol == TunnelProtocol.QUIC) and (Version(rsd.product_version) < Version("17.0.0")):
|
|
318
363
|
await rsd.close()
|
|
319
364
|
rsd = None
|
|
320
365
|
raise asyncio.CancelledError()
|
|
321
366
|
|
|
322
367
|
await asyncio.create_task(
|
|
323
368
|
self.start_tunnel_task(ip, await create_core_device_tunnel_service_using_rsd(rsd)),
|
|
324
|
-
name=f
|
|
369
|
+
name=f"start-tunnel-task-usb-{ip}",
|
|
370
|
+
)
|
|
325
371
|
except asyncio.CancelledError:
|
|
326
372
|
pass
|
|
327
|
-
except PairingError
|
|
328
|
-
logger.
|
|
373
|
+
except PairingError:
|
|
374
|
+
logger.exception(f"Failed to pair with {ip}")
|
|
329
375
|
except RuntimeError:
|
|
330
|
-
logger.debug(f
|
|
376
|
+
logger.debug(f"Got RuntimeError from: {asyncio.current_task().get_name()}")
|
|
331
377
|
except Exception:
|
|
332
|
-
logger.
|
|
378
|
+
logger.exception(f"Error raised from: {asyncio.current_task().get_name()}: {traceback.format_exc()}")
|
|
333
379
|
finally:
|
|
334
380
|
if rsd is not None:
|
|
335
|
-
|
|
381
|
+
with suppress(OSError):
|
|
336
382
|
await rsd.close()
|
|
337
|
-
except OSError:
|
|
338
|
-
pass
|
|
339
383
|
|
|
340
384
|
if ip in self.tunnel_tasks:
|
|
341
385
|
# In case the tunnel was removed just now
|
|
342
386
|
self.tunnel_tasks.pop(ip)
|
|
343
387
|
|
|
344
388
|
async def close(self) -> None:
|
|
345
|
-
"""
|
|
389
|
+
"""close all tasks"""
|
|
346
390
|
for task in self.tasks + [tunnel_task.task for tunnel_task in self.tunnel_tasks.values()]:
|
|
347
391
|
task.cancel()
|
|
348
392
|
with suppress(asyncio.CancelledError):
|
|
349
393
|
await task
|
|
350
394
|
|
|
351
395
|
def get_tunnels_ips(self) -> dict:
|
|
352
|
-
"""
|
|
396
|
+
"""Retrieve the available tunnel tasks and format them as {UDID: [IP]}"""
|
|
353
397
|
tunnels_ips = {}
|
|
354
398
|
for ip, active_tunnel in self.tunnel_tasks.items():
|
|
355
399
|
if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
|
|
@@ -361,47 +405,75 @@ class TunneldCore:
|
|
|
361
405
|
return tunnels_ips
|
|
362
406
|
|
|
363
407
|
def cancel(self, udid: str) -> None:
|
|
364
|
-
"""
|
|
408
|
+
"""Cancel active tunnels"""
|
|
365
409
|
for tunnel_ip in self.get_tunnels_ips().get(udid, []):
|
|
366
410
|
self.tunnel_tasks.pop(tunnel_ip).task.cancel()
|
|
367
|
-
logger.info(f
|
|
411
|
+
logger.info(f"Canceling tunnel {tunnel_ip}")
|
|
368
412
|
|
|
369
413
|
def clear(self) -> None:
|
|
370
|
-
"""
|
|
371
|
-
for
|
|
372
|
-
logger.info(f
|
|
414
|
+
"""Clear active tunnels"""
|
|
415
|
+
for _udid, tunnel in self.tunnel_tasks.items():
|
|
416
|
+
logger.info(f"Removing tunnel {tunnel}")
|
|
373
417
|
tunnel.task.cancel()
|
|
374
418
|
self.tunnel_tasks = {}
|
|
375
419
|
|
|
376
420
|
|
|
377
421
|
class TunneldRunner:
|
|
378
|
-
"""
|
|
422
|
+
"""TunneldRunner orchestrate between the webserver and TunneldCore"""
|
|
379
423
|
|
|
380
424
|
@classmethod
|
|
381
|
-
def create(
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
425
|
+
def create(
|
|
426
|
+
cls,
|
|
427
|
+
host: str,
|
|
428
|
+
port: int,
|
|
429
|
+
protocol: TunnelProtocol = TunnelProtocol.QUIC,
|
|
430
|
+
usb_monitor: bool = True,
|
|
431
|
+
wifi_monitor: bool = True,
|
|
432
|
+
usbmux_monitor: bool = True,
|
|
433
|
+
mobdev2_monitor: bool = True,
|
|
434
|
+
) -> None:
|
|
435
|
+
cls(
|
|
436
|
+
host,
|
|
437
|
+
port,
|
|
438
|
+
protocol=protocol,
|
|
439
|
+
usb_monitor=usb_monitor,
|
|
440
|
+
wifi_monitor=wifi_monitor,
|
|
441
|
+
usbmux_monitor=usbmux_monitor,
|
|
442
|
+
mobdev2_monitor=mobdev2_monitor,
|
|
443
|
+
)._run_app()
|
|
444
|
+
|
|
445
|
+
def __init__(
|
|
446
|
+
self,
|
|
447
|
+
host: str,
|
|
448
|
+
port: int,
|
|
449
|
+
protocol: TunnelProtocol = TunnelProtocol.QUIC,
|
|
450
|
+
usb_monitor: bool = True,
|
|
451
|
+
wifi_monitor: bool = True,
|
|
452
|
+
usbmux_monitor: bool = True,
|
|
453
|
+
mobdev2_monitor: bool = True,
|
|
454
|
+
):
|
|
388
455
|
@asynccontextmanager
|
|
389
456
|
async def lifespan(app: FastAPI):
|
|
390
457
|
self._tunneld_core.start()
|
|
391
458
|
yield
|
|
392
|
-
logger.info(
|
|
459
|
+
logger.info("Closing tunneld tasks...")
|
|
393
460
|
await self._tunneld_core.close()
|
|
394
461
|
|
|
395
462
|
self.host = host
|
|
396
463
|
self.port = port
|
|
397
464
|
self.protocol = protocol
|
|
398
465
|
self._app = FastAPI(lifespan=lifespan)
|
|
399
|
-
self._tunneld_core = TunneldCore(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
466
|
+
self._tunneld_core = TunneldCore(
|
|
467
|
+
protocol=protocol,
|
|
468
|
+
wifi_monitor=wifi_monitor,
|
|
469
|
+
usb_monitor=usb_monitor,
|
|
470
|
+
usbmux_monitor=usbmux_monitor,
|
|
471
|
+
mobdev2_monitor=mobdev2_monitor,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
@self._app.get("/")
|
|
403
475
|
async def list_tunnels() -> dict[str, list[dict]]:
|
|
404
|
-
"""
|
|
476
|
+
"""Retrieve the available tunnels and format them as {UUID: TUNNEL_ADDRESS}"""
|
|
405
477
|
tunnels = {}
|
|
406
478
|
for ip, active_tunnel in self._tunneld_core.tunnel_tasks.items():
|
|
407
479
|
if (active_tunnel.udid is None) or (active_tunnel.tunnel is None):
|
|
@@ -409,52 +481,53 @@ class TunneldRunner:
|
|
|
409
481
|
if active_tunnel.udid not in tunnels:
|
|
410
482
|
tunnels[active_tunnel.udid] = []
|
|
411
483
|
tunnels[active_tunnel.udid].append({
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
484
|
+
"tunnel-address": active_tunnel.tunnel.address,
|
|
485
|
+
"tunnel-port": active_tunnel.tunnel.port,
|
|
486
|
+
"interface": ip,
|
|
487
|
+
})
|
|
415
488
|
return tunnels
|
|
416
489
|
|
|
417
|
-
@self._app.get(
|
|
490
|
+
@self._app.get("/shutdown")
|
|
418
491
|
async def shutdown() -> fastapi.Response:
|
|
419
|
-
"""
|
|
492
|
+
"""Shutdown Tunneld"""
|
|
420
493
|
os.kill(os.getpid(), signal.SIGINT)
|
|
421
|
-
data = {
|
|
494
|
+
data = {"operation": "shutdown", "data": True, "message": "Server shutting down..."}
|
|
422
495
|
return generate_http_response(data)
|
|
423
496
|
|
|
424
|
-
@self._app.get(
|
|
497
|
+
@self._app.get("/clear_tunnels")
|
|
425
498
|
async def clear_tunnels() -> fastapi.Response:
|
|
426
499
|
self._tunneld_core.clear()
|
|
427
|
-
data = {
|
|
500
|
+
data = {"operation": "clear_tunnels", "data": True, "message": "Cleared tunnels..."}
|
|
428
501
|
return generate_http_response(data)
|
|
429
502
|
|
|
430
|
-
@self._app.get(
|
|
503
|
+
@self._app.get("/cancel")
|
|
431
504
|
async def cancel_tunnel(udid: str) -> fastapi.Response:
|
|
432
505
|
self._tunneld_core.cancel(udid=udid)
|
|
433
|
-
data = {
|
|
506
|
+
data = {"operation": "cancel", "udid": udid, "data": True, "message": f"tunnel {udid} Canceled ..."}
|
|
434
507
|
return generate_http_response(data)
|
|
435
508
|
|
|
436
|
-
@self._app.get(
|
|
509
|
+
@self._app.get("/hello")
|
|
437
510
|
async def hello() -> fastapi.Response:
|
|
438
|
-
data = {
|
|
511
|
+
data = {"message": "Hello, I'm alive"}
|
|
439
512
|
return generate_http_response(data)
|
|
440
513
|
|
|
441
514
|
def generate_http_response(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
media_type=media_type,
|
|
446
|
-
content=json.dumps(data))
|
|
515
|
+
data: dict, status_code: int = 200, media_type: str = "application/json"
|
|
516
|
+
) -> fastapi.Response:
|
|
517
|
+
return fastapi.Response(status_code=status_code, media_type=media_type, content=json.dumps(data))
|
|
447
518
|
|
|
448
|
-
@self._app.get(
|
|
519
|
+
@self._app.get("/start-tunnel")
|
|
449
520
|
async def start_tunnel(
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
521
|
+
udid: str, ip: Optional[str] = None, connection_type: Optional[str] = None
|
|
522
|
+
) -> fastapi.Response:
|
|
523
|
+
udid_tunnels = [
|
|
524
|
+
t.tunnel for t in self._tunneld_core.tunnel_tasks.values() if t.udid == udid and t.tunnel is not None
|
|
525
|
+
]
|
|
453
526
|
if len(udid_tunnels) > 0:
|
|
454
527
|
data = {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
528
|
+
"interface": udid_tunnels[0].interface,
|
|
529
|
+
"port": udid_tunnels[0].port,
|
|
530
|
+
"address": udid_tunnels[0].address,
|
|
458
531
|
}
|
|
459
532
|
return generate_http_response(data)
|
|
460
533
|
|
|
@@ -462,33 +535,36 @@ class TunneldRunner:
|
|
|
462
535
|
created_task = False
|
|
463
536
|
|
|
464
537
|
try:
|
|
465
|
-
if not created_task and connection_type in (
|
|
466
|
-
task_identifier = f
|
|
538
|
+
if not created_task and connection_type in ("usbmux", None):
|
|
539
|
+
task_identifier = f"usbmux-{udid}"
|
|
467
540
|
try:
|
|
468
541
|
with create_using_usbmux(udid) as lockdown:
|
|
469
542
|
service = await CoreDeviceTunnelProxy.create(lockdown)
|
|
470
543
|
task = asyncio.create_task(
|
|
471
|
-
self._tunneld_core.start_tunnel_task(
|
|
472
|
-
|
|
473
|
-
|
|
544
|
+
self._tunneld_core.start_tunnel_task(
|
|
545
|
+
task_identifier, service, protocol=TunnelProtocol.TCP, queue=queue
|
|
546
|
+
),
|
|
547
|
+
name=f"start-tunnel-task-{task_identifier}",
|
|
548
|
+
)
|
|
474
549
|
self._tunneld_core.tunnel_tasks[task_identifier] = TunnelTask(task=task, udid=udid)
|
|
475
550
|
created_task = True
|
|
476
551
|
except (ConnectionFailedError, InvalidServiceError, MuxException):
|
|
477
552
|
pass
|
|
478
|
-
if connection_type in (
|
|
553
|
+
if connection_type in ("usb", None):
|
|
479
554
|
for rsd in await get_rsds(udid=udid):
|
|
480
555
|
rsd_ip = rsd.service.address[0]
|
|
481
556
|
if ip is not None and rsd_ip != ip:
|
|
482
557
|
await rsd.close()
|
|
483
558
|
continue
|
|
484
559
|
task = asyncio.create_task(
|
|
485
|
-
self._tunneld_core.start_tunnel_task(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
name=f
|
|
560
|
+
self._tunneld_core.start_tunnel_task(
|
|
561
|
+
rsd_ip, await create_core_device_tunnel_service_using_rsd(rsd), queue=queue
|
|
562
|
+
),
|
|
563
|
+
name=f"start-tunnel-usb-{rsd_ip}",
|
|
564
|
+
)
|
|
489
565
|
self._tunneld_core.tunnel_tasks[rsd_ip] = TunnelTask(task=task, udid=rsd.udid)
|
|
490
566
|
created_task = True
|
|
491
|
-
if not created_task and connection_type in (
|
|
567
|
+
if not created_task and connection_type in ("wifi", None):
|
|
492
568
|
for remotepairing in await get_remote_pairing_tunnel_services(udid=udid):
|
|
493
569
|
remotepairing_ip = remotepairing.hostname
|
|
494
570
|
if ip is not None and remotepairing_ip != ip:
|
|
@@ -496,31 +572,34 @@ class TunneldRunner:
|
|
|
496
572
|
continue
|
|
497
573
|
task = asyncio.create_task(
|
|
498
574
|
self._tunneld_core.start_tunnel_task(remotepairing_ip, remotepairing, queue=queue),
|
|
499
|
-
name=f
|
|
575
|
+
name=f"start-tunnel-wifi-{remotepairing_ip}",
|
|
576
|
+
)
|
|
500
577
|
self._tunneld_core.tunnel_tasks[remotepairing_ip] = TunnelTask(
|
|
501
|
-
task=task, udid=remotepairing.remote_identifier
|
|
578
|
+
task=task, udid=remotepairing.remote_identifier
|
|
579
|
+
)
|
|
502
580
|
created_task = True
|
|
503
581
|
except Exception as e:
|
|
504
|
-
return fastapi.Response(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
582
|
+
return fastapi.Response(
|
|
583
|
+
status_code=501,
|
|
584
|
+
content=json.dumps({
|
|
585
|
+
"error": {
|
|
586
|
+
"exception": e.__class__.__name__,
|
|
587
|
+
"traceback": traceback.format_exc(),
|
|
588
|
+
}
|
|
589
|
+
}),
|
|
590
|
+
)
|
|
509
591
|
|
|
510
592
|
if not created_task:
|
|
511
|
-
return fastapi.Response(status_code=501, content=json.dumps({
|
|
593
|
+
return fastapi.Response(status_code=501, content=json.dumps({"error": "task not created"}))
|
|
512
594
|
|
|
513
595
|
tunnel: Optional[TunnelResult] = await queue.get()
|
|
514
596
|
if tunnel is not None:
|
|
515
|
-
data = {
|
|
516
|
-
'interface': tunnel.interface,
|
|
517
|
-
'port': tunnel.port,
|
|
518
|
-
'address': tunnel.address
|
|
519
|
-
}
|
|
597
|
+
data = {"interface": tunnel.interface, "port": tunnel.port, "address": tunnel.address}
|
|
520
598
|
return generate_http_response(data)
|
|
521
599
|
else:
|
|
522
|
-
return fastapi.Response(
|
|
523
|
-
|
|
600
|
+
return fastapi.Response(
|
|
601
|
+
status_code=404, content=json.dumps({"error": "something went wrong during tunnel creation"})
|
|
602
|
+
)
|
|
524
603
|
|
|
525
604
|
def _run_app(self) -> None:
|
|
526
|
-
uvicorn.run(self._app, host=self.host, port=self.port, loop=
|
|
605
|
+
uvicorn.run(self._app, host=self.host, port=self.port, loop="asyncio")
|