Skip to content

Commit 26543c1

Browse files
committed
feat(Vsock): Add initial VsockServer
1 parent 7286d8e commit 26543c1

6 files changed

Lines changed: 242 additions & 4 deletions

File tree

src/base/Transports.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ export interface StdioAddress {
1919
cwd?: string
2020
}
2121

22-
export interface VsockAddress {
23-
type: Transport.vsock
24-
port?: number
25-
cid?: number
22+
export class VsockAddress {
23+
public readonly type: Transport.vsock = Transport.vsock
24+
25+
public readonly port: number
26+
27+
public constructor (port: number = 6000) {
28+
this.port = port
29+
}
2630
}
2731

2832
export type TcpAddressInitializer =

src/vsock/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vsock-server

src/vsock/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
vsock-server: VsockServer.c
2+
gcc -o vsock-server VsockServer.c

src/vsock/VsockServer.c

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* vsocket-server - A server for AF_VSOCK to allow communications
3+
* between the host and KVM virtual machines.
4+
*
5+
* Inspired by and mostly based on `nc-vsock` by Stefan Hajnoczi
6+
* https://github.com/stefanha/nc-vsock/. This is a simplified
7+
* version of `nc-vsock` that only listens, does not
8+
* support tunneling to TCP socket, and does support
9+
* echoing (mainly for testing).
10+
*
11+
* See also https://github.com/firecracker-microvm/firecracker/blob/master/tests/host_tools/vsock_helper.c
12+
*/
13+
14+
#include <errno.h>
15+
#include <stdio.h>
16+
#include <stdlib.h>
17+
#include <stdbool.h>
18+
#include <string.h>
19+
#include <unistd.h>
20+
#include <fcntl.h>
21+
#include <sys/socket.h>
22+
#include <sys/select.h>
23+
#include <linux/vm_sockets.h>
24+
25+
/**
26+
* Set a file descriptor to be non-blocking
27+
*/
28+
static void set_non_blocking(int fd) {
29+
int ret = fcntl(fd, F_GETFL);
30+
if (ret < 0) {
31+
perror("fcntl");
32+
return;
33+
}
34+
fcntl(fd, F_SETFL, ret & ~O_NONBLOCK);
35+
}
36+
37+
/**
38+
* Transfer data between file descriptors
39+
*/
40+
static int transfer_data(int in_fd, int out_fd) {
41+
char buffer[4096];
42+
char *data = buffer;
43+
44+
size_t size = read(in_fd, buffer, sizeof(buffer));
45+
if (size <= 0) return -1;
46+
47+
ssize_t remaining = size;
48+
while (remaining > 0) {
49+
size = write(out_fd, data, remaining);
50+
if (size < 0 && errno == EAGAIN) {
51+
size = 0;
52+
} else if (size <= 0) {
53+
return -1;
54+
}
55+
56+
if (remaining > size) {
57+
for (;;) {
58+
fd_set wfds;
59+
FD_ZERO(&wfds);
60+
FD_SET(out_fd, &wfds);
61+
if (select(out_fd + 1, NULL, &wfds, NULL, NULL) < 0) {
62+
if (errno == EINTR) {
63+
continue;
64+
} else {
65+
perror("select");
66+
return -1;
67+
}
68+
}
69+
70+
if (FD_ISSET(out_fd, &wfds)) {
71+
break;
72+
}
73+
}
74+
}
75+
76+
data += size;
77+
remaining -= size;
78+
}
79+
return 0;
80+
}
81+
82+
int main(int argc, char **argv) {
83+
if (argc < 2 || argc > 3) {
84+
fprintf(stderr, "Usage: vsock-server <port> [--echo | --pass]\n");
85+
return 1;
86+
}
87+
88+
char *port_str = argv[1];
89+
char *end = NULL;
90+
long port = strtol(port_str, &end, 10);
91+
if (port_str == end || *end != '\0') {
92+
fprintf(stderr, "invalid port number: %s\n", port_str);
93+
return 1;
94+
}
95+
96+
enum mode {
97+
pass,
98+
echo
99+
} mode = pass;
100+
if (argc == 3) {
101+
char* option = argv[2];
102+
if (strcmp(option, "--echo") == 0) {
103+
mode = echo;
104+
} else if (strcmp(option, "--pass") == 0) {
105+
mode = pass;
106+
} else {
107+
fprintf(stderr, "invalid mode: %s\n", option);
108+
return 1;
109+
}
110+
}
111+
112+
struct sockaddr_vm sa_listen = {
113+
.svm_family = AF_VSOCK,
114+
.svm_cid = VMADDR_CID_ANY,
115+
.svm_port = port
116+
};
117+
118+
int listen_fd = socket(AF_VSOCK, SOCK_STREAM, 0);
119+
if (listen_fd < 0) {
120+
perror("socket");
121+
return 1;
122+
}
123+
124+
if (bind(listen_fd, (struct sockaddr*)&sa_listen, sizeof(sa_listen)) != 0) {
125+
perror("bind");
126+
close(listen_fd);
127+
return 1;
128+
}
129+
130+
if (listen(listen_fd, 1) != 0) {
131+
perror("listen");
132+
close(listen_fd);
133+
return 1;
134+
}
135+
136+
struct sockaddr_vm sa_client;
137+
socklen_t socklen_client = sizeof(sa_client);
138+
139+
int client_fd = accept(listen_fd, (struct sockaddr*)&sa_client, &socklen_client);
140+
if (client_fd < 0) {
141+
perror("accept");
142+
close(listen_fd);
143+
return 1;
144+
}
145+
146+
close(listen_fd);
147+
148+
fd_set rfds;
149+
int nfds = client_fd + 1;
150+
151+
set_non_blocking(STDIN_FILENO);
152+
set_non_blocking(STDOUT_FILENO);
153+
set_non_blocking(client_fd);
154+
155+
while (true) {
156+
FD_ZERO(&rfds);
157+
FD_SET(STDIN_FILENO, &rfds);
158+
FD_SET(client_fd, &rfds);
159+
160+
if (select(nfds, &rfds, NULL, NULL, NULL) < 0) {
161+
if (errno == EINTR) {
162+
continue;
163+
} else {
164+
perror("select");
165+
return 1;
166+
}
167+
}
168+
169+
if (FD_ISSET(STDIN_FILENO, &rfds) && port > 0) {
170+
if (transfer_data(STDIN_FILENO, client_fd) < 0) {
171+
return 1;
172+
}
173+
}
174+
175+
if (FD_ISSET(client_fd, &rfds)) {
176+
if (transfer_data(client_fd, mode == echo ? client_fd : STDOUT_FILENO) < 0) {
177+
return 1;
178+
}
179+
}
180+
}
181+
182+
return 0;
183+
}

src/vsock/VsockServer.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import VsockServer from './VsockServer'
2+
3+
test('VsockServer', async () => {
4+
const server = new VsockServer()
5+
await server.start()
6+
await server.stop()
7+
})

src/vsock/VsockServer.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { getLogger } from '@stencila/logga';
2+
import Executor from '../base/Executor';
3+
import { VsockAddress } from '../base/Transports';
4+
import StreamServer from '../stream/StreamServer';
5+
import { spawn, ChildProcess } from 'child_process';
6+
7+
const log = getLogger('executa:vsock:server')
8+
9+
export default class VsockServer extends StreamServer {
10+
11+
public readonly port: number
12+
13+
private server?: ChildProcess
14+
15+
public constructor(
16+
executor?: Executor,
17+
address: VsockAddress = new VsockAddress()
18+
) {
19+
super(executor)
20+
21+
this.port = address.port
22+
}
23+
24+
public get address(): VsockAddress {
25+
return new VsockAddress(this.port)
26+
}
27+
28+
public async start(): Promise<void> {
29+
if (this.server === undefined) {
30+
const server = (this.server = spawn(__dirname + '/vsock-server', [`${this.port}`]))
31+
server.on('error', log.error)
32+
super.start(server.stdout, server.stdin)
33+
}
34+
}
35+
36+
public async stop(): Promise<void> {
37+
if (this.server !== undefined) {
38+
this.server.kill()
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)