Skip to content

Commit

Permalink
Merge pull request #1272 from doronz88/bugfix/default-tunnel-protocol…
Browse files Browse the repository at this point in the history
…-tcp

remote: use TCP as the default tunneling protocol on python3.13+
  • Loading branch information
doronz88 authored Nov 4, 2024
2 parents ad3e91d + 903103b commit 8efde88
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 27 deletions.
6 changes: 4 additions & 2 deletions pymobiledevice3/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
ConnectionFailedToUsbmuxdError, DeprecationError, DeveloperModeError, DeveloperModeIsNotEnabledError, \
DeviceHasPasscodeSetError, DeviceNotFoundError, FeatureNotSupportedError, InternalError, InvalidServiceError, \
MessageNotSupportedError, MissingValueError, NoDeviceConnectedError, NotEnoughDiskSpaceError, NotPairedError, \
OSNotSupportedError, PairingDialogResponsePendingError, PasswordRequiredError, RSDRequiredError, \
SetProhibitedError, TunneldConnectionError, UserDeniedPairingError
OSNotSupportedError, PairingDialogResponsePendingError, PasswordRequiredError, QuicProtocolNotSupportedError, \
RSDRequiredError, SetProhibitedError, TunneldConnectionError, UserDeniedPairingError
from pymobiledevice3.osu.os_utils import get_os_utils

coloredlogs.install(level=logging.INFO)
Expand Down Expand Up @@ -244,6 +244,8 @@ def main() -> None:
logger.error(
f'Missing implementation of `{e.feature}` on `{e.os_name}`. To add support, consider contributing at '
f'https://github.com/doronz88/pymobiledevice3.')
except QuicProtocolNotSupportedError as e:
logger.error(str(e))


if __name__ == '__main__':
Expand Down
9 changes: 5 additions & 4 deletions pymobiledevice3/cli/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ def remote_cli() -> None:
@click.option('--port', type=click.INT, default=TUNNELD_DEFAULT_ADDRESS[1])
@click.option('-d', '--daemonize', is_flag=True)
@click.option('-p', '--protocol', type=click.Choice([e.value for e in TunnelProtocol]),
default=TunnelProtocol.QUIC.value)
help='Transport protocol. If python version >= 3.13 will default to TCP. Otherwise will default to QUIC',
default=TunnelProtocol.DEFAULT.value)
@click.option('--usb/--no-usb', default=True, help='Enable usb monitoring')
@click.option('--wifi/--no-wifi', default=True, help='Enable wifi monitoring')
@click.option('--usbmux/--no-usbmux', default=True, help='Enable usbmux monitoring')
Expand Down Expand Up @@ -112,7 +113,7 @@ def rsd_info(service_provider: RemoteServiceDiscoveryService):

async def tunnel_task(
service, secrets: Optional[TextIO] = None, script_mode: bool = False,
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.QUIC) -> None:
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
async with start_tunnel(
service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as tunnel_result:
logger.info('tunnel created')
Expand Down Expand Up @@ -152,7 +153,7 @@ async def tunnel_task(

async def start_tunnel_task(
connection_type: ConnectionType, secrets: TextIO, udid: Optional[str] = None, script_mode: bool = False,
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.QUIC) -> None:
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
if start_tunnel is None:
raise NotImplementedError('failed to start the tunnel on your platform')
get_tunnel_services = {
Expand Down Expand Up @@ -185,7 +186,7 @@ async def start_tunnel_task(
help='Maximum QUIC idle time (ping interval)')
@click.option('-p', '--protocol',
type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
default=TunnelProtocol.QUIC.value)
default=TunnelProtocol.DEFAULT.value)
@sudo_required
def cli_start_tunnel(
connection_type: ConnectionType, udid: Optional[str], secrets: TextIO, script_mode: bool,
Expand Down
7 changes: 6 additions & 1 deletion pymobiledevice3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
'LaunchingApplicationError', 'BadCommandError', 'BadDevError', 'ConnectionFailedError', 'CoreDeviceError',
'AccessDeniedError', 'RSDRequiredError', 'SysdiagnoseTimeoutError', 'GetProhibitedError',
'FeatureNotSupportedError', 'OSNotSupportedError', 'DeprecationError', 'NotEnoughDiskSpaceError',
'CloudConfigurationAlreadyPresentError'
'CloudConfigurationAlreadyPresentError', 'QuicProtocolNotSupportedError',
]

from typing import Optional
Expand Down Expand Up @@ -390,3 +390,8 @@ class FeatureNotSupportedError(SupportError):
def __init__(self, os_name, feature):
super().__init__(os_name)
self.feature = feature


class QuicProtocolNotSupportedError(PyMobileDevice3Exception):
""" QUIC tunnel support was removed on iOS 18.2+ """
pass
4 changes: 4 additions & 0 deletions pymobiledevice3/remote/common.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from enum import Enum


Expand All @@ -9,3 +10,6 @@ class ConnectionType(Enum):
class TunnelProtocol(Enum):
TCP = 'tcp'
QUIC = 'quic'

# TODO: make only TCP the default once 3.12 becomes deprecated
DEFAULT = TCP if sys.version_info >= (3, 13) else QUIC
43 changes: 24 additions & 19 deletions pymobiledevice3/remote/tunnel_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@

from pymobiledevice3.bonjour import DEFAULT_BONJOUR_TIMEOUT, browse_remotepairing
from pymobiledevice3.ca import make_cert
from pymobiledevice3.exceptions import PairingError, PyMobileDevice3Exception, UserDeniedPairingError
from pymobiledevice3.exceptions import PairingError, PyMobileDevice3Exception, QuicProtocolNotSupportedError, \
UserDeniedPairingError
from pymobiledevice3.pair_records import PAIRING_RECORD_EXT, create_pairing_records_cache_folder, generate_host_id, \
get_remote_pairing_record_filename, iter_remote_paired_identifiers
from pymobiledevice3.remote.common import TunnelProtocol
Expand Down Expand Up @@ -417,24 +418,28 @@ async def start_quic_tunnel(
port = parameters['port']

self.logger.debug(f'Connecting to {host}:{port}')
async with aioquic_connect(
host,
port,
configuration=configuration,
create_protocol=RemotePairingQuicTunnel,
) as client:
self.logger.debug('quic connected')
client = cast(RemotePairingQuicTunnel, client)
await client.wait_connected()
handshake_response = await client.request_tunnel_establish()
client.start_tunnel(handshake_response['clientParameters']['address'],
handshake_response['clientParameters']['mtu'])
try:
yield TunnelResult(
client.tun.name, handshake_response['serverAddress'], handshake_response['serverRSDPort'],
TunnelProtocol.QUIC, client)
finally:
await client.stop_tunnel()
try:
async with aioquic_connect(
host,
port,
configuration=configuration,
create_protocol=RemotePairingQuicTunnel,
) as client:
self.logger.debug('quic connected')
client = cast(RemotePairingQuicTunnel, client)
await client.wait_connected()
handshake_response = await client.request_tunnel_establish()
client.start_tunnel(handshake_response['clientParameters']['address'],
handshake_response['clientParameters']['mtu'])
try:
yield TunnelResult(
client.tun.name, handshake_response['serverAddress'], handshake_response['serverRSDPort'],
TunnelProtocol.QUIC, client)
finally:
await client.stop_tunnel()
except ConnectionError:
raise QuicProtocolNotSupportedError(
'iOS 18.2+ removed QUIC protocol support. Use TCP instead (requires python3.13+)')

@asynccontextmanager
async def start_tcp_tunnel(self) -> AsyncGenerator[TunnelResult, None]:
Expand Down
2 changes: 1 addition & 1 deletion pymobiledevice3/tunneld.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class TunnelTask:


class TunneldCore:
def __init__(self, protocol: TunnelProtocol = TunnelProtocol.QUIC, wifi_monitor: bool = True,
def __init__(self, protocol: TunnelProtocol = TunnelProtocol.DEFAULT, wifi_monitor: bool = True,
usb_monitor: bool = True, usbmux_monitor: bool = True, mobdev2_monitor: bool = True) -> None:
self.protocol = protocol
self.tasks: list[asyncio.Task] = []
Expand Down

0 comments on commit 8efde88

Please sign in to comment.