Skip to content

Commit 33239d2

Browse files
committed
cli,remote: Initial userspace tunnel server support
1 parent ed98111 commit 33239d2

File tree

4 files changed

+260
-68
lines changed

4 files changed

+260
-68
lines changed

pymobiledevice3/cli/lockdown.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import logging
3+
import platform
34
import plistlib
45
from pathlib import Path
56
from typing import Optional
@@ -164,12 +165,15 @@ def lockdown_wifi_connections(service_provider: LockdownClient, state):
164165
@lockdown_group.command('start-tunnel', cls=Command)
165166
@click.option('--script-mode', is_flag=True,
166167
help='Show only HOST and port number to allow easy parsing from external shell scripts')
167-
@sudo_required
168+
@click.option('--userspace-host', help='Specify a host to listen on to enable userspace TUN')
168169
def cli_start_tunnel(
169-
service_provider: LockdownServiceProvider, script_mode: bool) -> None:
170+
service_provider: LockdownServiceProvider, script_mode: bool, userspace_host: Optional[str] = None) -> None:
170171
""" start tunnel """
172+
if platform.system() == 'Darwin':
173+
sudo_required(lambda: None)()
171174
service = CoreDeviceTunnelProxy(service_provider)
172-
asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None, protocol=TunnelProtocol.TCP), debug=True)
175+
asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None,
176+
protocol=TunnelProtocol.TCP, userspace_host=userspace_host), debug=True)
173177

174178

175179
@lockdown_group.command('assistive-touch', cls=Command)

pymobiledevice3/cli/remote.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import dataclasses
33
import logging
4+
import platform
45
import sys
56
import tempfile
67
from functools import partial
@@ -113,30 +114,38 @@ def rsd_info(service_provider: RemoteServiceDiscoveryService):
113114

114115

115116
async def tunnel_task(
116-
service, secrets: Optional[TextIO] = None, script_mode: bool = False,
117+
service, secrets: Optional[TextIO] = None, script_mode: bool = False, userspace_host: Optional[str] = None,
117118
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
118119
async with start_tunnel(
119-
service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as tunnel_result:
120+
service, secrets=secrets, max_idle_timeout=max_idle_timeout,
121+
protocol=protocol, userspace_host=userspace_host) as tunnel_result:
120122
logger.info('tunnel created')
123+
use_userspace = userspace_host is not None
121124
if script_mode:
122-
print(f'{tunnel_result.address} {tunnel_result.port}')
125+
if userspace_host:
126+
print(f'{tunnel_result.address} {tunnel_result.port} {tunnel_result.interface}')
127+
else:
128+
print(f'{tunnel_result.address} {tunnel_result.port}')
123129
else:
130+
interface_name = "Userspace TUN" if use_userspace else tunnel_result.interface
131+
userspace_param = f"--userspace-wrapper {tunnel_result.interface} " if use_userspace else ""
124132
if user_requested_colored_output():
125133
if secrets is not None:
126134
print(click.style('Secrets: ', bold=True, fg='magenta') +
127135
click.style(secrets.name, bold=True, fg='white'))
128136
print(click.style('Identifier: ', bold=True, fg='yellow') +
129137
click.style(service.remote_identifier, bold=True, fg='white'))
130138
print(click.style('Interface: ', bold=True, fg='yellow') +
131-
click.style(tunnel_result.interface, bold=True, fg='white'))
139+
click.style(interface_name, bold=True, fg='white'))
132140
print(click.style('Protocol: ', bold=True, fg='yellow') +
133141
click.style(tunnel_result.protocol, bold=True, fg='white'))
134142
print(click.style('RSD Address: ', bold=True, fg='yellow') +
135143
click.style(tunnel_result.address, bold=True, fg='white'))
136144
print(click.style('RSD Port: ', bold=True, fg='yellow') +
137145
click.style(tunnel_result.port, bold=True, fg='white'))
138146
print(click.style('Use the follow connection option:\n', bold=True, fg='yellow') +
139-
click.style(f'--rsd {tunnel_result.address} {tunnel_result.port}', bold=True, fg='cyan'))
147+
click.style(userspace_param + f'--rsd {tunnel_result.address} {tunnel_result.port}',
148+
bold=True, fg='cyan'))
140149
else:
141150
if secrets is not None:
142151
print(f'Secrets: {secrets.name}')
@@ -145,16 +154,17 @@ async def tunnel_task(
145154
print(f'Protocol: {tunnel_result.protocol}')
146155
print(f'RSD Address: {tunnel_result.address}')
147156
print(f'RSD Port: {tunnel_result.port}')
148-
print(f'Use the follow connection option:\n'
149-
f'--rsd {tunnel_result.address} {tunnel_result.port}')
157+
print('Use the follow connection option:\n' +
158+
userspace_param + f'--rsd {tunnel_result.address} {tunnel_result.port}')
150159
sys.stdout.flush()
151160
await tunnel_result.client.wait_closed()
152161
logger.info('tunnel was closed')
153162

154163

155164
async def start_tunnel_task(
156165
connection_type: ConnectionType, secrets: TextIO, udid: Optional[str] = None, script_mode: bool = False,
157-
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
166+
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
167+
userspace_host: Optional[str] = None) -> None:
158168
if start_tunnel is None:
159169
raise NotImplementedError('failed to start the tunnel on your platform')
160170
get_tunnel_services = {
@@ -173,13 +183,14 @@ async def start_tunnel_task(
173183
service = prompt_device_list(tunnel_services)
174184

175185
await tunnel_task(service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout,
176-
protocol=protocol)
186+
protocol=protocol, userspace_host=userspace_host)
177187

178188

179189
@remote_cli.command('start-tunnel', cls=BaseCommand)
180190
@click.option('-t', '--connection-type', type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
181191
default=ConnectionType.USB.value)
182192
@click.option('--udid', help='UDID for a specific device to look for')
193+
@click.option('--userspace-host', help='Specify a host to listen on to enable userspace TUN')
183194
@click.option('--secrets', type=click.File('wt'), help='TLS keyfile for decrypting with Wireshark')
184195
@click.option('--script-mode', is_flag=True,
185196
help='Show only HOST and port number to allow easy parsing from external shell scripts')
@@ -188,17 +199,18 @@ async def start_tunnel_task(
188199
@click.option('-p', '--protocol',
189200
type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
190201
default=TunnelProtocol.DEFAULT.value)
191-
@sudo_required
192202
def cli_start_tunnel(
193-
connection_type: ConnectionType, udid: Optional[str], secrets: TextIO, script_mode: bool,
194-
max_idle_timeout: float, protocol: str) -> None:
203+
connection_type: ConnectionType, udid: Optional[str], userspace_host: Optional[str],
204+
secrets: TextIO, script_mode: bool, max_idle_timeout: float, protocol: str) -> None:
195205
""" start tunnel """
196206
if not verify_tunnel_imports():
197207
return
208+
if platform.system() == 'Darwin':
209+
sudo_required(lambda: None)()
198210
asyncio.run(
199211
start_tunnel_task(
200212
ConnectionType(connection_type), secrets, udid, script_mode, max_idle_timeout=max_idle_timeout,
201-
protocol=TunnelProtocol(protocol)), debug=True)
213+
protocol=TunnelProtocol(protocol), userspace_host=userspace_host), debug=True)
202214

203215

204216
@dataclasses.dataclass

pymobiledevice3/remote/module_imports.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
logger = logging.getLogger(__name__)
55

66
try:
7-
from pymobiledevice3.remote.tunnel_service import RemotePairingQuicTunnel, start_tunnel
7+
from pymobiledevice3.remote.tunnel_service import _RemotePairingQuicTunnelMixin, start_tunnel
88

9-
MAX_IDLE_TIMEOUT = RemotePairingQuicTunnel.MAX_IDLE_TIMEOUT
9+
MAX_IDLE_TIMEOUT = _RemotePairingQuicTunnelMixin.MAX_IDLE_TIMEOUT
1010
except ImportError:
1111
start_tunnel: Optional[Callable] = None
1212
MAX_IDLE_TIMEOUT = None

0 commit comments

Comments
 (0)