Skip to content

Commit 3194a12

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

File tree

4 files changed

+258
-69
lines changed

4 files changed

+258
-69
lines changed

pymobiledevice3/cli/lockdown.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import click
88

9-
from pymobiledevice3.cli.cli_common import Command, CommandWithoutAutopair, print_json, sudo_required
9+
from pymobiledevice3.cli.cli_common import Command, CommandWithoutAutopair, print_json # sudo_required
1010
from pymobiledevice3.cli.remote import tunnel_task
1111
from pymobiledevice3.lockdown import LockdownClient
1212
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
@@ -164,12 +164,14 @@ def lockdown_wifi_connections(service_provider: LockdownClient, state):
164164
@lockdown_group.command('start-tunnel', cls=Command)
165165
@click.option('--script-mode', is_flag=True,
166166
help='Show only HOST and port number to allow easy parsing from external shell scripts')
167-
@sudo_required
167+
@click.option('--userspace-host', help='Specify a host to listen on to enable userspace TUN')
168+
# @sudo_required
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 """
171172
service = CoreDeviceTunnelProxy(service_provider)
172-
asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None, protocol=TunnelProtocol.TCP), debug=True)
173+
asyncio.run(tunnel_task(service, script_mode=script_mode, secrets=None,
174+
protocol=TunnelProtocol.TCP, userspace_host=userspace_host), debug=True)
173175

174176

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

pymobiledevice3/cli/remote.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,30 +113,38 @@ def rsd_info(service_provider: RemoteServiceDiscoveryService):
113113

114114

115115
async def tunnel_task(
116-
service, secrets: Optional[TextIO] = None, script_mode: bool = False,
116+
service, secrets: Optional[TextIO] = None, script_mode: bool = False, userspace_host: Optional[str] = None,
117117
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT) -> None:
118118
async with start_tunnel(
119-
service, secrets=secrets, max_idle_timeout=max_idle_timeout, protocol=protocol) as tunnel_result:
119+
service, secrets=secrets, max_idle_timeout=max_idle_timeout,
120+
protocol=protocol, userspace_host=userspace_host) as tunnel_result:
120121
logger.info('tunnel created')
122+
use_userspace = userspace_host is not None
121123
if script_mode:
122-
print(f'{tunnel_result.address} {tunnel_result.port}')
124+
if userspace_host:
125+
print(f'{tunnel_result.address} {tunnel_result.port} {tunnel_result.interface}')
126+
else:
127+
print(f'{tunnel_result.address} {tunnel_result.port}')
123128
else:
129+
interface_name = "Userspace TUN" if use_userspace else tunnel_result.interface
130+
userspace_param = f"--userspace-wrapper {tunnel_result.interface} " if use_userspace else ""
124131
if user_requested_colored_output():
125132
if secrets is not None:
126133
print(click.style('Secrets: ', bold=True, fg='magenta') +
127134
click.style(secrets.name, bold=True, fg='white'))
128135
print(click.style('Identifier: ', bold=True, fg='yellow') +
129136
click.style(service.remote_identifier, bold=True, fg='white'))
130137
print(click.style('Interface: ', bold=True, fg='yellow') +
131-
click.style(tunnel_result.interface, bold=True, fg='white'))
138+
click.style(interface_name, bold=True, fg='white'))
132139
print(click.style('Protocol: ', bold=True, fg='yellow') +
133140
click.style(tunnel_result.protocol, bold=True, fg='white'))
134141
print(click.style('RSD Address: ', bold=True, fg='yellow') +
135142
click.style(tunnel_result.address, bold=True, fg='white'))
136143
print(click.style('RSD Port: ', bold=True, fg='yellow') +
137144
click.style(tunnel_result.port, bold=True, fg='white'))
138145
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'))
146+
click.style(userspace_param + f'--rsd {tunnel_result.address} {tunnel_result.port}',
147+
bold=True, fg='cyan'))
140148
else:
141149
if secrets is not None:
142150
print(f'Secrets: {secrets.name}')
@@ -145,16 +153,17 @@ async def tunnel_task(
145153
print(f'Protocol: {tunnel_result.protocol}')
146154
print(f'RSD Address: {tunnel_result.address}')
147155
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}')
156+
print('Use the follow connection option:\n' +
157+
userspace_param + f'--rsd {tunnel_result.address} {tunnel_result.port}')
150158
sys.stdout.flush()
151159
await tunnel_result.client.wait_closed()
152160
logger.info('tunnel was closed')
153161

154162

155163
async def start_tunnel_task(
156164
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:
165+
max_idle_timeout: float = MAX_IDLE_TIMEOUT, protocol: TunnelProtocol = TunnelProtocol.DEFAULT,
166+
userspace_host: Optional[str] = None) -> None:
158167
if start_tunnel is None:
159168
raise NotImplementedError('failed to start the tunnel on your platform')
160169
get_tunnel_services = {
@@ -173,13 +182,14 @@ async def start_tunnel_task(
173182
service = prompt_device_list(tunnel_services)
174183

175184
await tunnel_task(service, secrets=secrets, script_mode=script_mode, max_idle_timeout=max_idle_timeout,
176-
protocol=protocol)
185+
protocol=protocol, userspace_host=userspace_host)
177186

178187

179188
@remote_cli.command('start-tunnel', cls=BaseCommand)
180189
@click.option('-t', '--connection-type', type=click.Choice([e.value for e in ConnectionType], case_sensitive=False),
181190
default=ConnectionType.USB.value)
182191
@click.option('--udid', help='UDID for a specific device to look for')
192+
@click.option('--userspace-host', help='Specify a host to listen on to enable userspace TUN')
183193
@click.option('--secrets', type=click.File('wt'), help='TLS keyfile for decrypting with Wireshark')
184194
@click.option('--script-mode', is_flag=True,
185195
help='Show only HOST and port number to allow easy parsing from external shell scripts')
@@ -188,17 +198,17 @@ async def start_tunnel_task(
188198
@click.option('-p', '--protocol',
189199
type=click.Choice([e.value for e in TunnelProtocol], case_sensitive=False),
190200
default=TunnelProtocol.DEFAULT.value)
191-
@sudo_required
201+
# @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
198208
asyncio.run(
199209
start_tunnel_task(
200210
ConnectionType(connection_type), secrets, udid, script_mode, max_idle_timeout=max_idle_timeout,
201-
protocol=TunnelProtocol(protocol)), debug=True)
211+
protocol=TunnelProtocol(protocol), userspace_host=userspace_host), debug=True)
202212

203213

204214
@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)