mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-10-02 08:04:32 +08:00
Merge vphoned spawn proxy
This commit is contained in:
@@ -220,8 +220,23 @@ message.
|
||||
## Connection failure behavior
|
||||
|
||||
A dropped HTTP or WebSocket connection closes only that request channel. The
|
||||
guest launchd plist keeps vphoned alive and restarts it if the daemon itself
|
||||
exits. Host socket writes use `F_SETNOSIGPIPE`, so a guest disconnect becomes
|
||||
guest launchd plist starts a small vphoned proxy. It uses `posix_spawn` to
|
||||
start the same signed executable with `--io`, then waits for and reaps that
|
||||
worker. The worker owns VSOCK 1338 and 1339 and all API state. The proxy
|
||||
restarts an unexpectedly exited worker with bounded backoff, and forwards
|
||||
shutdown to it. A pipe makes the worker exit if launchd kills the proxy, so
|
||||
the old worker cannot retain the ports after launchd starts a replacement.
|
||||
The proxy never initializes NIO, IcliKit, or the camera server under its
|
||||
6 MB per-process Jetsam limit. A successful `agent.apply_update` worker exit
|
||||
makes the proxy exit so launchd can restart the updated cached binary. If a
|
||||
cached worker fails before binding, the bundled-binary fallback remains in
|
||||
effect. On a 26.6.2 VM, the proxy's physical footprint stayed near 1.4 MB
|
||||
through 30 health requests and six app listings; the worker served those
|
||||
requests without a PID change. Killing the proxy caused the worker to leave
|
||||
and launchd to start one new proxy/worker pair. The worker's Jetsam snapshot
|
||||
reported no per-process limit.
|
||||
|
||||
Host socket writes use `F_SETNOSIGPIPE`, so a guest disconnect becomes
|
||||
an ordinary error instead of terminating `vphone-vm`. The host HTTP client
|
||||
also times out stalled reads and writes. Camera frames use a duplicated
|
||||
descriptor for each in-flight send; the original descriptor remains owned by
|
||||
|
||||
@@ -5,7 +5,13 @@ import NIOPosix
|
||||
import NIOWebSocket
|
||||
import VphonedNative
|
||||
|
||||
vp_native_bootstrap_cached_binary()
|
||||
let mode = vp_native_process_mode()
|
||||
if mode != 1 {
|
||||
guard mode == 0 else { exit(64) }
|
||||
vp_native_bootstrap_cached_binary()
|
||||
exit(vp_native_run_proxy())
|
||||
}
|
||||
guard vp_native_watch_proxy() == 0 else { exit(1) }
|
||||
vp_vcam_start()
|
||||
GuestIrisinInstaller.refreshBootstrapOnStartup()
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#ifdef __OBJC__
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
/// Sign all executable code in an extracted app. Returns a malloc-owned error or NULL.
|
||||
char *vp_sign_app_for_install(const char *appPath, const char *certificatePath);
|
||||
void vp_native_bootstrap_cached_binary(void);
|
||||
void vp_native_confirm_cached_binary(void);
|
||||
/// 0 = launchd proxy, 1 = --io worker, -1 = invalid arguments.
|
||||
int vp_native_process_mode(void);
|
||||
int vp_native_run_proxy(void);
|
||||
int vp_native_watch_proxy(void);
|
||||
void vp_vcam_start(void);
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -1,31 +1,59 @@
|
||||
#import "Include/VphonedNative.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#include <errno.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static const char *cache = "/var/root/Library/Caches/vphoned";
|
||||
static const char *marker = "/var/root/Library/Caches/vphoned.api-v2";
|
||||
static const char *pending = "/var/root/Library/Caches/vphoned.api-v2.pending";
|
||||
|
||||
void vp_native_bootstrap_cached_binary(void) {
|
||||
// A cached update gets one attempt to bind. If it fails, launchd restarts
|
||||
// the bundled daemon, which remains the fallback.
|
||||
if (access(cache, X_OK) != 0 || access(marker, R_OK) != 0) return;
|
||||
NSData *binary = [NSData dataWithContentsOfFile:@(cache) options:NSDataReadingMappedIfSafe error:nil];
|
||||
NSString *expected = [NSString stringWithContentsOfFile:@(marker) encoding:NSUTF8StringEncoding error:nil];
|
||||
if (!binary || expected.length != CC_SHA256_DIGEST_LENGTH * 2) return;
|
||||
static bool cached_binary_matches_marker(void) {
|
||||
FILE *image = fopen(cache, "rb");
|
||||
if (!image) return false;
|
||||
CC_SHA256_CTX context;
|
||||
CC_SHA256_Init(&context);
|
||||
unsigned char buffer[64 * 1024];
|
||||
size_t count;
|
||||
while ((count = fread(buffer, 1, sizeof(buffer), image)) > 0)
|
||||
CC_SHA256_Update(&context, buffer, (CC_LONG)count);
|
||||
bool complete = !ferror(image);
|
||||
fclose(image);
|
||||
if (!complete) return false;
|
||||
|
||||
FILE *record = fopen(marker, "rb");
|
||||
if (!record) return false;
|
||||
char expected[65];
|
||||
count = fread(expected, 1, sizeof(expected), record);
|
||||
fclose(record);
|
||||
if (count != 64) return false;
|
||||
expected[64] = '\0';
|
||||
|
||||
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(binary.bytes, (CC_LONG)binary.length, digest);
|
||||
NSMutableString *actual = [NSMutableString string];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) [actual appendFormat:@"%02x", digest[i]];
|
||||
if (![actual isEqualToString:expected]) return;
|
||||
CC_SHA256_Final(digest, &context);
|
||||
static const char hex[] = "0123456789abcdef";
|
||||
char actual[65];
|
||||
for (int index = 0; index < CC_SHA256_DIGEST_LENGTH; index++) {
|
||||
actual[index * 2] = hex[digest[index] >> 4];
|
||||
actual[index * 2 + 1] = hex[digest[index] & 15];
|
||||
}
|
||||
actual[64] = '\0';
|
||||
return strcmp(actual, expected) == 0;
|
||||
}
|
||||
|
||||
void vp_native_bootstrap_cached_binary(void) {
|
||||
// Keep the launchd process small: hash the cache in bounded chunks.
|
||||
if (access(cache, X_OK) != 0 || !cached_binary_matches_marker()) return;
|
||||
char current[4096];
|
||||
uint32_t size = sizeof(current);
|
||||
if (_NSGetExecutablePath(current, &size) != 0 || strcmp(current, cache) == 0) return;
|
||||
if (rename(marker, pending) != 0) return;
|
||||
char *const arguments[] = {(char *)cache, NULL};
|
||||
execv(cache, arguments);
|
||||
NSLog(@"vphoned: cached binary launch failed: %s", strerror(errno));
|
||||
fprintf(stderr, "vphoned proxy: cached binary exec failed: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
void vp_native_confirm_cached_binary(void) {
|
||||
@@ -33,5 +61,5 @@ void vp_native_confirm_cached_binary(void) {
|
||||
uint32_t size = sizeof(current);
|
||||
if (_NSGetExecutablePath(current, &size) != 0 || strcmp(current, cache) != 0) return;
|
||||
if (rename(pending, marker) != 0)
|
||||
NSLog(@"vphoned: could not confirm cached binary: %s", strerror(errno));
|
||||
fprintf(stderr, "vphoned: could not confirm cached binary: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#include "Include/VphonedNative.h"
|
||||
|
||||
#include <crt_externs.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <spawn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static volatile sig_atomic_t stopping = 0;
|
||||
static const char *pending_update = "/var/root/Library/Caches/vphoned.api-v2.pending";
|
||||
|
||||
static void request_stop(int signal_number) {
|
||||
stopping = signal_number;
|
||||
}
|
||||
|
||||
static int worker_fd(void) {
|
||||
int argc = *_NSGetArgc();
|
||||
char **argv = *_NSGetArgv();
|
||||
if (argc != 3 || strcmp(argv[1], "--io") != 0) return -1;
|
||||
char *end = NULL;
|
||||
long value = strtol(argv[2], &end, 10);
|
||||
if (!end || *end != '\0' || value < 3 || value > INT_MAX) return -1;
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
int vp_native_process_mode(void) {
|
||||
int argc = *_NSGetArgc();
|
||||
if (argc == 1) return 0;
|
||||
return worker_fd() >= 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
static void *watch_proxy(void *context) {
|
||||
int fd = (int)(intptr_t)context;
|
||||
char byte;
|
||||
while (read(fd, &byte, 1) < 0 && errno == EINTR) {}
|
||||
// Only the proxy owns the write end. Its death must not leave an
|
||||
// unaccounted worker holding the VSOCK ports open.
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
int vp_native_watch_proxy(void) {
|
||||
int fd = worker_fd();
|
||||
if (fd < 0) return -1;
|
||||
pthread_attr_t attributes;
|
||||
if (pthread_attr_init(&attributes) != 0) return -1;
|
||||
int result = pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);
|
||||
if (result == 0) result = pthread_attr_setstacksize(&attributes, 64 * 1024);
|
||||
pthread_t thread;
|
||||
if (result == 0) result = pthread_create(&thread, &attributes, watch_proxy, (void *)(intptr_t)fd);
|
||||
pthread_attr_destroy(&attributes);
|
||||
return result == 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
static void pause_before_retry(unsigned seconds) {
|
||||
struct timespec remaining = {.tv_sec = seconds, .tv_nsec = 0};
|
||||
while (!stopping && nanosleep(&remaining, &remaining) < 0 && errno == EINTR) {}
|
||||
}
|
||||
|
||||
static void stop_worker(pid_t pid, int write_fd) {
|
||||
close(write_fd);
|
||||
kill(pid, SIGTERM);
|
||||
for (int tries = 0; tries < 30; tries++) {
|
||||
pid_t waited = waitpid(pid, NULL, WNOHANG);
|
||||
if (waited == pid || (waited < 0 && errno == ECHILD)) return;
|
||||
struct timespec delay = {.tv_sec = 0, .tv_nsec = 100000000};
|
||||
nanosleep(&delay, NULL);
|
||||
}
|
||||
kill(pid, SIGKILL);
|
||||
while (waitpid(pid, NULL, 0) < 0 && errno == EINTR) {}
|
||||
}
|
||||
|
||||
int vp_native_run_proxy(void) {
|
||||
char executable[4096];
|
||||
uint32_t size = sizeof(executable);
|
||||
if (_NSGetExecutablePath(executable, &size) != 0) return 1;
|
||||
|
||||
struct sigaction action = {.sa_handler = request_stop};
|
||||
sigemptyset(&action.sa_mask);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
|
||||
unsigned retry_delay = 1;
|
||||
while (!stopping) {
|
||||
int pipe_fds[2];
|
||||
if (pipe(pipe_fds) != 0) return 1;
|
||||
posix_spawn_file_actions_t file_actions;
|
||||
int error = posix_spawn_file_actions_init(&file_actions);
|
||||
if (error != 0) {
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
return 1;
|
||||
}
|
||||
error = posix_spawn_file_actions_addclose(&file_actions, pipe_fds[1]);
|
||||
if (error != 0) {
|
||||
posix_spawn_file_actions_destroy(&file_actions);
|
||||
close(pipe_fds[0]);
|
||||
close(pipe_fds[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
char fd_argument[24];
|
||||
snprintf(fd_argument, sizeof(fd_argument), "%d", pipe_fds[0]);
|
||||
char *arguments[] = {executable, "--io", fd_argument, NULL};
|
||||
pid_t child = -1;
|
||||
error = posix_spawn(&child, executable, &file_actions, NULL, arguments, environ);
|
||||
posix_spawn_file_actions_destroy(&file_actions);
|
||||
close(pipe_fds[0]);
|
||||
if (error != 0) {
|
||||
fprintf(stderr, "vphoned proxy: posix_spawn failed: %s\n", strerror(error));
|
||||
close(pipe_fds[1]);
|
||||
pause_before_retry(retry_delay);
|
||||
} else {
|
||||
if (stopping) {
|
||||
stop_worker(child, pipe_fds[1]);
|
||||
break;
|
||||
}
|
||||
int status = 0;
|
||||
pid_t waited;
|
||||
do {
|
||||
waited = waitpid(child, &status, 0);
|
||||
} while (waited < 0 && errno == EINTR && !stopping);
|
||||
if (stopping) {
|
||||
if (waited == child) close(pipe_fds[1]);
|
||||
else stop_worker(child, pipe_fds[1]);
|
||||
break;
|
||||
}
|
||||
close(pipe_fds[1]);
|
||||
if (waited == child && WIFEXITED(status) && WEXITSTATUS(status) == 0) {
|
||||
// agent.apply_update exits the worker after installing the
|
||||
// cached binary. launchd restarts us into that new image.
|
||||
return 0;
|
||||
}
|
||||
if (access(pending_update, F_OK) == 0) {
|
||||
// A cached worker failed before binding. The next launchd
|
||||
// start falls back to the bundled binary.
|
||||
return 1;
|
||||
}
|
||||
fprintf(stderr, "vphoned proxy: worker exited; retrying\n");
|
||||
pause_before_retry(retry_delay);
|
||||
}
|
||||
if (retry_delay < 10) retry_delay *= 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user