Skip to content

Commit

Permalink
add remote services support
Browse files Browse the repository at this point in the history
  • Loading branch information
doronz88 committed Jul 6, 2023
1 parent 5a405a3 commit e66b7c0
Show file tree
Hide file tree
Showing 12 changed files with 1,047 additions and 2 deletions.
6 changes: 5 additions & 1 deletion pymobiledevice3/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from pymobiledevice3.cli.processes import cli as ps_cli
from pymobiledevice3.cli.profile import cli as profile_cli
from pymobiledevice3.cli.provision import cli as provision_cli
from pymobiledevice3.cli.remote import cli as remote_cli
from pymobiledevice3.cli.restore import cli as restore_cli
from pymobiledevice3.cli.springboard import cli as springboard_cli
from pymobiledevice3.cli.syslog import cli as syslog_cli
Expand All @@ -35,7 +36,9 @@

coloredlogs.install(level=logging.INFO)

logging.getLogger('quic').disabled = True
logging.getLogger('asyncio').disabled = True
logging.getLogger('zeroconf').disabled = True
logging.getLogger('parso.cache').disabled = True
logging.getLogger('parso.cache.pickle').disabled = True
logging.getLogger('parso.python.diff').disabled = True
Expand All @@ -50,7 +53,8 @@ def cli():
cli_commands = click.CommandCollection(sources=[
developer_cli, mounter_cli, apps_cli, profile_cli, lockdown_cli, diagnostics_cli, syslog_cli, pcap_cli,
crash_cli, afc_cli, ps_cli, notification_cli, usbmux_cli, power_assertion_cli, springboard_cli,
provision_cli, backup_cli, restore_cli, activation_cli, companion_cli, webinspector_cli, amfi_cli, bonjour_cli
provision_cli, backup_cli, restore_cli, activation_cli, companion_cli, webinspector_cli, amfi_cli, bonjour_cli,
remote_cli
])
cli_commands.context_settings = dict(help_option_names=['-h', '--help'])
try:
Expand Down
61 changes: 61 additions & 0 deletions pymobiledevice3/cli/remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import asyncio
import logging

import click
from cryptography.hazmat.primitives.asymmetric import rsa

from pymobiledevice3.cli.cli_common import print_json
from pymobiledevice3.remote.bonjour import get_iphone_address
from pymobiledevice3.remote.core_device_tunnel_service import create_core_device_tunnel_service
from pymobiledevice3.remote.remote_service_discovery import RSD_PORT, RemoteServiceDiscoveryService

logger = logging.getLogger(__name__)


@click.group()
def cli():
""" remote cli """
pass


@cli.group('remote')
def remote_cli():
""" remote options """
pass


@remote_cli.command('rsd-info')
@click.option('--color/--no-color', default=True)
def rsd_info(color: bool):
""" show info extracted from RSD peer """
hostname = asyncio.run(get_iphone_address())

with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
print_json(rsd.peer_info, colored=color)


@remote_cli.command('create-listener')
@click.option('-p', '--protocol', type=click.Choice(['quic', 'udp']))
@click.option('--color/--no-color', default=True)
def create_listener(protocol: str, color: bool):
""" start a remote listener """
hostname = asyncio.run(get_iphone_address())

private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
with create_core_device_tunnel_service(rsd, autopair=True) as service:
print_json(service.create_listener(private_key, protocol=protocol), colored=color)


@remote_cli.command('start-quic-tunnel')
@click.option('--color/--no-color', default=True)
def start_quic_tunnel(color: bool):
""" start quic tunnel """
logger.critical('This is a WIP command. Will only print the required parameters for the quic connection')

hostname = asyncio.run(get_iphone_address())

private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
with RemoteServiceDiscoveryService((hostname, RSD_PORT)) as rsd:
with create_core_device_tunnel_service(rsd, autopair=True) as service:
print_json(asyncio.run(service.start_quic_tunnel(private_key)), colored=color)
5 changes: 5 additions & 0 deletions pymobiledevice3/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ class ConnectionTerminatedError(PyMobileDevice3Exception):
pass


class StreamClosedError(ConnectionTerminatedError):
""" Raise trying to send a message on a closed stream. """
pass


class WebInspectorNotEnabledError(PyMobileDevice3Exception):
""" Raise when Web Inspector is not enabled. """
pass
Expand Down
Empty file.
40 changes: 40 additions & 0 deletions pymobiledevice3/remote/bonjour.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import asyncio
from socket import AF_INET6, inet_ntop

from ifaddr import get_adapters
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
from zeroconf.const import _TYPE_AAAA


class RemotedListener(ServiceListener):
def __init__(self):
super().__init__()
self.is_finished = asyncio.Event()

def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
if name == 'ncm._remoted._tcp.local.':
service_info = zc.get_service_info(type_, name)
records = zc.cache.async_entries_with_name(service_info.server)
for record in records:
if record.type == _TYPE_AAAA:
self.record = record
self.is_finished.set()


async def try_get_iphone_address(adapter):
ip = adapter.ips[0].ip[0]
zeroconf = Zeroconf(interfaces=[ip])
waiter_task = asyncio.create_task(zeroconf.notify_event.wait())
listener = RemotedListener()
ServiceBrowser(zeroconf, '_remoted._tcp.local.', listener)
await waiter_task
await listener.is_finished.wait()
return inet_ntop(AF_INET6, listener.record.address) + '%' + adapter.nice_name


async def get_iphone_address():
adapters = get_adapters()
adapters = [adapter for adapter in adapters if adapter.ips[0].is_IPv6]
tasks = [asyncio.create_task(try_get_iphone_address(adapter)) for adapter in adapters]
finished, unfinished = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
return list(finished)[0].result()
Loading

0 comments on commit e66b7c0

Please sign in to comment.