Skip to content

Commit

Permalink
Use NSObject instead of C struct in XPC message
Browse files Browse the repository at this point in the history
  • Loading branch information
ravicious committed Jul 31, 2024
1 parent 44ddd30 commit b03f33c
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 52 deletions.
25 changes: 8 additions & 17 deletions lib/vnet/daemon/client_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -261,28 +260,20 @@ func startByCalling(ctx context.Context, bundlePath string, config Config) error
errC := make(chan error, 1)

go func() {
var pinner runtime.Pinner
defer pinner.Unpin()

req := C.StartVnetRequest{
bundle_path: C.CString(bundlePath),
vnet_config: &C.VnetConfig{
socket_path: C.CString(config.SocketPath),
ipv6_prefix: C.CString(config.IPv6Prefix),
dns_addr: C.CString(config.DNSAddr),
home_path: C.CString(config.HomePath),
},
socket_path: C.CString(config.SocketPath),
ipv6_prefix: C.CString(config.IPv6Prefix),
dns_addr: C.CString(config.DNSAddr),
home_path: C.CString(config.HomePath),
}
defer func() {
C.free(unsafe.Pointer(req.bundle_path))
C.free(unsafe.Pointer(req.vnet_config.socket_path))
C.free(unsafe.Pointer(req.vnet_config.ipv6_prefix))
C.free(unsafe.Pointer(req.vnet_config.dns_addr))
C.free(unsafe.Pointer(req.vnet_config.home_path))
C.free(unsafe.Pointer(req.socket_path))
C.free(unsafe.Pointer(req.ipv6_prefix))
C.free(unsafe.Pointer(req.dns_addr))
C.free(unsafe.Pointer(req.home_path))
}()
// Structs passed directly as arguments to cgo functions are automatically pinned.
// However, structs within structs have to be pinned by hand.
pinner.Pin(req.vnet_config)

var res C.StartVnetResult
defer func() {
Expand Down
8 changes: 6 additions & 2 deletions lib/vnet/daemon/client_darwin.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ void OpenSystemSettingsLoginItems(void);

typedef struct StartVnetRequest {
const char *bundle_path;
VnetConfig *vnet_config;

const char *socket_path;
const char *ipv6_prefix;
const char *dns_addr;
const char *home_path;
} StartVnetRequest;

typedef struct StartVnetResult {
Expand Down Expand Up @@ -73,7 +77,7 @@ void StartVnet(StartVnetRequest *request, StartVnetResult *outResult);
void InvalidateDaemonClient(void);

@interface VNEDaemonClient : NSObject
- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion;
- (void)startVnet:(VNEConfig *)config completion:(void (^)(NSError *error))completion;
// invalidate executes all outstanding reply blocks, error handling blocks,
// and invalidation blocks and forbids from sending or receiving new messages.
- (void)invalidate;
Expand Down
17 changes: 11 additions & 6 deletions lib/vnet/daemon/client_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,17 @@ - (NSXPCConnection *)connection {
return _connection;
}

- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *))completion {
- (void)startVnet:(VNEConfig *)config completion:(void (^)(NSError *))completion {
// This way of calling the XPC proxy ensures either the error handler or
// the reply block gets called.
// https://forums.developer.apple.com/forums/thread/713429
id proxy = [self.connection remoteObjectProxyWithErrorHandler:^(NSError *error) {
completion(error);
}];

[(id<VNEDaemonProtocol>)proxy startVnet:vnetConfig
completion:^(NSError *error) {
completion(error);
}];
[(id<VNEDaemonProtocol>)proxy startVnet:config completion:^(NSError *error) {
completion(error);
}];
}

- (void)invalidate {
Expand Down Expand Up @@ -151,9 +150,15 @@ void StartVnet(StartVnetRequest *request, StartVnetResult *outResult) {
daemonClient = [[VNEDaemonClient alloc] initWithBundlePath:@(request->bundle_path) codeSigningRequirement:requirement];
}

VNEConfig *config = [[VNEConfig alloc] init];
[config setSocketPath:@(request->socket_path)];
[config setIpv6Prefix:@(request->ipv6_prefix)];
[config setDnsAddr:@(request->dns_addr)];
[config setHomePath:@(request->home_path)];

dispatch_semaphore_t sema = dispatch_semaphore_create(0);

[daemonClient startVnet:request->vnet_config
[daemonClient startVnet:config
completion:^(NSError *error) {
if (error) {
outResult->ok = false;
Expand Down
30 changes: 23 additions & 7 deletions lib/vnet/daemon/common_darwin.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,28 @@ extern const int VNEAlreadyRunningError;
// https://developer.apple.com/documentation/security/1395809-seccodecopysigninginformation?language=objc
extern const int VNEMissingCodeSigningIdentifiersError;

typedef struct VnetConfig {
const char *socket_path;
const char *ipv6_prefix;
const char *dns_addr;
const char *home_path;
} VnetConfig;
// VNEConfig is used to send a config necessary to start VNet between the client and the daemon
// service. When adding or removing properties, remember to adjust the implementation of VNEConfig
// as well.
//
// Although it's not the primary use case, it's possible for the client to connect to a service
// of a different version of tsh where VNEConfig does not have the same properties.
// Thanks to the conformance to NSSecureCoding, adding and removing properties does not cause either
// end of the connection to blow up:
//
// * If the client sends a property that the daemon doesn't know about, the property will be ignored
// on the daemon side.
// * If the client does not send a property that the daemon expects, the property will not be set on
// the daemon side.
//
// In either case, the expectation is that the Obj-C side pushes the config to the Go side which
// actually validates the config.
@interface VNEConfig : NSObject <NSSecureCoding>
@property(copy) NSString *socketPath;
@property(copy) NSString *ipv6Prefix;
@property(copy) NSString *dnsAddr;
@property(copy) NSString *homePath;
@end

@protocol VNEDaemonProtocol
// startVnet passes the config back to Go code (which then starts VNet in a separate thread)
Expand All @@ -29,7 +45,7 @@ typedef struct VnetConfig {
// Only the first call to this method starts VNet. Subsequent calls return VNEAlreadyRunningError.
// The daemon process exits after VNet is stopped, after which it can be spawned again by calling
// this method.
- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion;
- (void)startVnet:(VNEConfig *)vnetConfig completion:(void (^)(NSError *error))completion;
@end

// Returns the label for the daemon by getting the identifier of the bundle
Expand Down
25 changes: 25 additions & 0 deletions lib/vnet/daemon/common_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "common_darwin.h"

#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
Expand Down Expand Up @@ -118,3 +119,27 @@ bool getCodeSigningRequirement(NSString **outRequirement, NSError **outError) {

return true;
}

@implementation VNEConfig
+ (BOOL)supportsSecureCoding {
return YES;
}

- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.socketPath forKey:@"socketPath"];
[coder encodeObject:self.ipv6Prefix forKey:@"ipv6Prefix"];
[coder encodeObject:self.dnsAddr forKey:@"dnsAddr"];
[coder encodeObject:self.homePath forKey:@"homePath"];
}

- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
if (self = [super init]) {
[self setSocketPath:[coder decodeObjectOfClass:[NSString class] forKey:@"socketPath"]];
[self setIpv6Prefix:[coder decodeObjectOfClass:[NSString class] forKey:@"ipv6Prefix"]];
[self setDnsAddr:[coder decodeObjectOfClass:[NSString class] forKey:@"dnsAddr"]];
[self setHomePath:[coder decodeObjectOfClass:[NSString class] forKey:@"homePath"]];
}
return self;
}

@end
30 changes: 10 additions & 20 deletions lib/vnet/daemon/service_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,13 @@ @interface VNEDaemonService () <NSXPCListenerDelegate, VNEDaemonProtocol>

// started describes whether the XPC listener is listening for new connections.
@property(readonly) BOOL started;

@property(readonly) NSString *socketPath;
@property(readonly) NSString *ipv6Prefix;
@property(readonly) NSString *dnsAddr;
@property(readonly) NSString *homePath;
@property(readonly) VNEConfig *config;
@property(readonly) VNEClientCred *clientCred;

@end

@implementation VNEDaemonService {
NSXPCListener *_listener;
// gotConfig describes if the daemon received a VNet config from a client.
BOOL _gotConfig;
dispatch_semaphore_t _gotVnetConfigSema;
}

Expand Down Expand Up @@ -94,7 +88,7 @@ - (void)waitForVnetConfig {

#pragma mark - VNEDaemonProtocol

- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion {
- (void)startVnet:(VNEConfig *)config completion:(void (^)(NSError *error))completion {
@synchronized(self) {
// startVnet is expected to be called only once per daemon's lifetime.
// Between the process with the daemon client exiting and the admin process (which runs the
Expand All @@ -103,19 +97,15 @@ - (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))
//
// In such scenarios, we want to return an error so that the client can wait for the daemon
// to exit and retry the call.
if (_gotConfig) {
if (_config != nil) {
NSError *error = [[NSError alloc] initWithDomain:@(VNEErrorDomain)
code:VNEAlreadyRunningError
userInfo:nil];
completion(error);
return;
}

_gotConfig = YES;
_socketPath = @(vnetConfig->socket_path);
_ipv6Prefix = @(vnetConfig->ipv6_prefix);
_dnsAddr = @(vnetConfig->dns_addr);
_homePath = @(vnetConfig->home_path);
_config = config;

NSXPCConnection *currentConn = [NSXPCConnection currentConnection];
_clientCred = [[VNEClientCred alloc] init];
Expand Down Expand Up @@ -193,14 +183,14 @@ void WaitForVnetConfig(VnetConfigResult *outResult, ClientCred *outClientCred) {
}

@synchronized(daemonService) {
outResult->socket_path = VNECopyNSString([daemonService socketPath]);
outResult->ipv6_prefix = VNECopyNSString([daemonService ipv6Prefix]);
outResult->dns_addr = VNECopyNSString([daemonService dnsAddr]);
outResult->home_path = VNECopyNSString([daemonService homePath]);
outResult->socket_path = VNECopyNSString(daemonService.config.socketPath);
outResult->ipv6_prefix = VNECopyNSString(daemonService.config.ipv6Prefix);
outResult->dns_addr = VNECopyNSString(daemonService.config.dnsAddr);
outResult->home_path = VNECopyNSString(daemonService.config.homePath);

if (daemonService.clientCred && [daemonService.clientCred valid]) {
outClientCred->egid = [daemonService.clientCred egid];
outClientCred->euid = [daemonService.clientCred euid];
outClientCred->egid = daemonService.clientCred.egid;
outClientCred->euid = daemonService.clientCred.euid;
outClientCred->valid = true;
}

Expand Down

0 comments on commit b03f33c

Please sign in to comment.