• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 20355307520

18 Dec 2025 09:15PM UTC coverage: 72.578% (-0.1%) from 72.709%
20355307520

push

github

DaanDeMeyer
mkosi: update debian commit reference to d9f2aa170

* d9f2aa1704 Install systemd-tpm2-generator.8 only for UEFI builds
* ac1c7d8048 Drop dependencies on libcap-dev, no longer used since v259
* c36e5871ca Do not install systemd-sysv-generator.8 in upstream build
* bac0cca0e8 Install new files for upstream build
* 2855fb1302 Update changelog for 259-1 release

309322 of 426195 relevant lines covered (72.58%)

1149469.57 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

14.0
/src/ssh-generator/ssh-proxy.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <unistd.h>
4

5
#include "sd-varlink.h"
6

7
#include "alloc-util.h"
8
#include "fd-util.h"
9
#include "io-util.h"
10
#include "iovec-util.h"
11
#include "log.h"
12
#include "main-func.h"
13
#include "path-lookup.h"
14
#include "socket-util.h"
15
#include "string-util.h"
16
#include "strv.h"
17
#include "time-util.h"
18

19
#define HEADER_READ_TIMEOUT_USEC (5 * USEC_PER_SEC)
20

21
static int process_vsock_cid(unsigned cid, const char *port) {
×
22
        int r;
×
23

24
        assert(cid != VMADDR_CID_ANY);
×
25
        assert(port);
×
26

27
        union sockaddr_union sa = {
×
28
                .vm.svm_cid = cid,
29
                .vm.svm_family = AF_VSOCK,
30
        };
31

32
        r = vsock_parse_port(port, &sa.vm.svm_port);
×
33
        if (r < 0)
×
34
                return log_error_errno(r, "Failed to parse vsock port: %s", port);
×
35

36
        _cleanup_close_ int fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0);
×
37
        if (fd < 0)
×
38
                return log_error_errno(errno, "Failed to allocate AF_VSOCK socket: %m");
×
39

40
        if (connect(fd, &sa.sa, sockaddr_len(&sa)) < 0)
×
41
                return log_error_errno(errno, "Failed to connect to vsock:%u:%u: %m", sa.vm.svm_cid, sa.vm.svm_port);
×
42

43
        /* OpenSSH wants us to send a single byte along with the file descriptor, hence do so */
44
        r = send_one_fd_iov(STDOUT_FILENO, fd, &iovec_nul_byte, /* iovlen= */ 1, /* flags= */ 0);
×
45
        if (r < 0)
×
46
                return log_error_errno(r, "Failed to send socket via STDOUT: %m");
×
47

48
        log_debug("Successfully sent AF_VSOCK socket via STDOUT.");
×
49
        return 0;
50
}
51

52
static int process_vsock_string(const char *host, const char *port) {
×
53
        unsigned cid;
×
54
        int r;
×
55

56
        assert(host);
×
57
        assert(port);
×
58

59
        r = vsock_parse_cid(host, &cid);
×
60
        if (r < 0)
×
61
                return log_error_errno(r, "Failed to parse vsock cid: %s", host);
×
62

63
        return process_vsock_cid(cid, port);
×
64
}
65

66
static int process_unix(const char *path) {
3✔
67
        int r;
3✔
68

69
        assert(path);
3✔
70

71
        /* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */
72
        _cleanup_free_ char *prefixed = NULL;
3✔
73
        if (!STARTSWITH_SET(path, "/", "./")) {
3✔
74
                prefixed = strjoin("/", path);
3✔
75
                if (!prefixed)
3✔
76
                        return log_oom();
×
77

78
                path = prefixed;
79
        }
80

81
        _cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
6✔
82
        if (fd < 0)
3✔
83
                return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m");
×
84

85
        r = connect_unix_path(fd, AT_FDCWD, path);
3✔
86
        if (r < 0)
3✔
87
                return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path);
×
88

89
        r = send_one_fd_iov(STDOUT_FILENO, fd, &iovec_nul_byte, /* iovlen= */ 1, /* flags= */ 0);
3✔
90
        if (r < 0)
3✔
91
                return log_error_errno(r, "Failed to send socket via STDOUT: %m");
×
92

93
        log_debug("Successfully sent AF_UNIX socket via STDOUT.");
3✔
94
        return 0;
95
}
96

97
static int skip_ok_port_res(int fd, const char *path, const char *port) {
×
98
        struct timeval oldtv;
×
99
        socklen_t oldtv_size = sizeof(oldtv);
×
100
        if (getsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &oldtv, &oldtv_size) < 0)
×
101
                return log_error_errno(errno, "Failed to get socket receive timeout for %s: %m", path);
×
102
        if (oldtv_size != sizeof(oldtv))
×
103
                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unexpected size of socket receive timeout for %s", path);
×
104
        if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(HEADER_READ_TIMEOUT_USEC), sizeof(struct timeval)) < 0)
×
105
                return log_error_errno(errno, "Failed to set socket receive timeout for %s: %m", path);
×
106

107
        char recv_buf[STRLEN("OK 65535\n")];
×
108
        size_t bytes_recv = 0, bytes_avail = 0, pos = 0;
×
109
        static const char expected_prefix[] = "OK ";
×
110

111
        for (;;) {
×
112
                if (pos >= bytes_avail) {
×
113
                        assert(bytes_recv <= bytes_avail);
×
114
                        if (bytes_avail >= sizeof(recv_buf)) {
×
115
                                /*
116
                                  Full buffer means that we have peeked as many bytes as possible and not seeing the ending \n .
117
                                  So the server is believed to not send OK PORT response, and we just pass out the socket to ssh client,
118
                                  and let it handle the connection.
119

120
                                  If we have not received any bytes from the socket buffer, we can safely pass out the socket,
121
                                  since no change has been made to the socket buffer. Otherwise, if some bytes have been received,
122
                                  the socket buffer has been changed, the only option is to give up and terminate the connection.
123
                                  Similar logic applies below when we meet other kinds of unexpected responses.
124
                                */
125
                                if (bytes_recv == 0) {
×
126
                                        log_debug("Received too many bytes while waiting for OK PORT response from %s\n"
×
127
                                                  "Assume the multiplexer is not sending OK PORT.",
128
                                                  path);
129
                                        goto passout_fd;
×
130
                                }
131
                                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Received too many bytes while waiting for OK PORT response from %s", path);
×
132
                        }
133
                        if (bytes_avail > bytes_recv) {
×
134
                                /*
135
                                  Discard already peeked bytes before peeking more.
136

137
                                  XXX: We cannot use SO_RCVLOWAT to set the minimum number of bytes to be peeked to peek entire
138
                                       OK PORT response at once to prevent changes to the recving buffer, because SO_RCVLOWAT
139
                                       does not work on unix sockets with recv(..., MSG_PEEK). Also poll() does not help here,
140
                                       because poll() returns readable as long as there is any data in the socket buffer for
141
                                       unix sockets, not respecting SO_RCVLOWAT.
142

143
                                  XXX: We could have used SO_PEEK_OFF to continously peek more data without changing the socket
144
                                       receive buffer, but this fucntion breaks since Linux 4.3 due to a kernel bug, which is fixed
145
                                       in Linux 6.18 commit 7bf3a476ce43 ("af_unix: Read sk_peek_offset() again after sleeping in
146
                                       unix_stream_read_generic()."). It is also not possible to detect whether the kernel is
147
                                       affected by this bug at runtime.
148

149
                                  As a result, we have no other choice but to discard already peeked data here.
150
                                */
151
                                ssize_t rlen = recv(fd, recv_buf + bytes_recv, bytes_avail - bytes_recv, /* flags= */ 0);
×
152
                                if (rlen < 0)
×
153
                                        return log_error_errno(errno, "Failed to discard OK PORT response from %s: %m", path);
×
154
                                if ((size_t) rlen != bytes_avail - bytes_recv)
×
155
                                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while discarding OK PORT response from %s", path);
×
156
                                log_debug("Successfully discarded %zi bytes of response: %.*s", rlen, (int) rlen, recv_buf + bytes_recv);
×
157
                                bytes_recv = bytes_avail;
158
                        }
159
                        ssize_t len = recv(fd, recv_buf + bytes_avail, sizeof(recv_buf) - bytes_avail, MSG_PEEK);
×
160
                        if (len < 0) {
×
161
                                if (errno != EAGAIN)
×
162
                                        return log_error_errno(errno, "Failed to receive OK from %s: %m", path);
×
163
                                if (bytes_recv == 0) {
×
164
                                        log_debug("Timeout while waiting for OK PORT response from %s\n"
×
165
                                                  "Assume the multiplexer will not send OK PORT.",
166
                                                  path);
167
                                        goto passout_fd;
×
168
                                }
169
                                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Timed out to receive OK PORT from %s", path);
×
170

171
                        }
172
                        if (len == 0) {
×
173
                                log_debug("Connection closed while waiting for OK PORT response from %s", path);
×
174
                                if (bytes_recv == 0) {
×
175
                                        log_debug("No data received, which means the connecting port is not open.");
×
176
                                        return log_error_errno(SYNTHETIC_ERRNO(ECONNREFUSED), "Port %s on %s is not open", port, path);
×
177
                                }
178
                                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Connection closed before full OK PORT response received from %s.", path);
×
179
                        }
180
                        bytes_avail += len;
×
181
                }
182
                assert(pos < bytes_avail);
×
183
                if (pos < strlen(expected_prefix) && recv_buf[pos] != expected_prefix[pos]) {
×
184
                        if (bytes_recv == 0) {
×
185
                                log_debug("Received response does not start with expected OK PORT response from %s\n"
×
186
                                          "Assume the multiplexer will not send OK PORT.",
187
                                          path);
188
                                goto passout_fd;
×
189
                        }
190
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Received invalid response while waiting for OK PORT from %s", path);
×
191
                }
192
                if (recv_buf[pos] == '\n') {
×
193
                        pos += 1;
×
194
                        break;
×
195
                }
196
                pos += 1;
×
197
        }
198

199
        assert(pos <= sizeof(recv_buf));
×
200
        assert(bytes_recv <= pos);
×
201
        if (bytes_recv < pos) {
×
202
                ssize_t len = recv(fd, recv_buf + bytes_recv, pos - bytes_recv, /* flags= */ 0);
×
203
                if (len < 0)
×
204
                        return log_error_errno(errno, "Failed to discard OK PORT response from %s: %m", path);
×
205
                if ((size_t) len != pos - bytes_recv)
×
206
                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while discarding OK PORT response from %s", path);
×
207
                log_debug("Successfully discarded response from %s: %.*s", path, (int) pos, recv_buf);
×
208
        }
209

210
passout_fd:
×
211
        if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &oldtv, sizeof(oldtv)) < 0)
×
212
                return log_error_errno(errno, "Failed to restore socket receive timeout for %s: %m", path);
×
213
        return 0;
214
}
215

216
static int process_vsock_mux(const char *path, const char *port) {
×
217
        int r;
×
218

219
        assert(path);
×
220
        assert(port);
×
221

222
        /* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */
223
        _cleanup_free_ char *prefixed = NULL;
×
224
        if (!STARTSWITH_SET(path, "/", "./")) {
×
225
                prefixed = strjoin("/", path);
×
226
                if (!prefixed)
×
227
                        return log_oom();
×
228

229
                path = prefixed;
230
        }
231

232
        _cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
×
233
        if (fd < 0)
×
234
                return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m");
×
235

236
        r = connect_unix_path(fd, AT_FDCWD, path);
×
237
        if (r < 0)
×
238
                return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path);
×
239

240
        /* Based on the protocol as defined here:
241
         * https://github.com/cloud-hypervisor/cloud-hypervisor/blob/main/docs/vsock.md
242
         * https://github.com/firecracker-microvm/firecracker/blob/main/docs/vsock.md */
243
        _cleanup_free_ char *connect_cmd = NULL;
×
244
        connect_cmd = strjoin("CONNECT ", port, "\n");
×
245
        if (!connect_cmd)
×
246
                return log_oom();
×
247

248
        r = loop_write(fd, connect_cmd, SIZE_MAX);
×
249
        if (r < 0)
×
250
                return log_error_errno(r, "Failed to send CONNECT to %s:%s: %m", path, port);
×
251

252
        r = skip_ok_port_res(fd, path, port);
×
253
        if (r < 0)
×
254
                return r;
255

256
        r = send_one_fd_iov(STDOUT_FILENO, fd, &iovec_nul_byte, /* iovlen= */ 1, /* flags= */ 0);
×
257
        if (r < 0)
×
258
                return log_error_errno(r, "Failed to send socket via STDOUT: %m");
×
259

260
        log_debug("Successfully sent AF_UNIX socket via STDOUT.");
×
261
        return 0;
262
}
263

264
static int fetch_machine(const char *machine, RuntimeScope scope, sd_json_variant **ret) {
×
265
        int r;
×
266

267
        assert(machine);
×
268
        assert(ret);
×
269

270
        _cleanup_free_ char *addr = NULL;
×
271
        r = runtime_directory_generic(scope, "machine/io.systemd.Machine", &addr);
×
272
        if (r < 0)
×
273
                return r;
274

275
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
×
276
        r = sd_varlink_connect_address(&vl, addr);
×
277
        if (r < 0)
×
278
                return log_error_errno(r, "Failed to connect to machined on %s: %m", addr);
×
279

280
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL;
×
281
        const char *error_id;
×
282
        r = sd_varlink_callbo(
×
283
                        vl,
284
                        "io.systemd.Machine.List",
285
                        &result,
286
                        &error_id,
287
                        SD_JSON_BUILD_PAIR_STRING("name", machine));
288
        if (r < 0)
×
289
                return log_error_errno(r, "Failed to issue io.systemd.Machine.List() varlink call: %m");
×
290
        if (error_id) {
×
291
                if (streq(error_id, "io.systemd.Machine.NoSuchMachine"))
×
292
                        return -ESRCH;
293

294
                r = sd_varlink_error_to_errno(error_id, result); /* If this is a system errno style error, output it with %m */
×
295
                if (r != -EBADR)
×
296
                        return log_error_errno(r, "Failed to issue io.systemd.Machine.List() varlink call: %m");
×
297

298
                return log_error_errno(r, "Failed to issue io.systemd.Machine.List() varlink call: %s", error_id);
×
299
        }
300

301
        *ret = TAKE_PTR(result);
×
302
        return 0;
×
303
}
304

305
static int process_machine(const char *machine, const char *port) {
×
306
        int r;
×
307

308
        assert(machine);
×
309
        assert(port);
×
310

311
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL;
×
312
        r = fetch_machine(machine, RUNTIME_SCOPE_USER, &result);
×
313
        if (r == -ESRCH)
×
314
                r = fetch_machine(machine, RUNTIME_SCOPE_SYSTEM, &result);
×
315
        if (r < 0)
×
316
                return r;
317

318
        uint32_t cid = VMADDR_CID_ANY;
×
319

320
        static const sd_json_dispatch_field dispatch_table[] = {
×
321
                { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, 0, 0 },
322
                {}
323
        };
324

325
        r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &cid);
×
326
        if (r < 0)
×
327
                return log_error_errno(r, "Failed to parse Varlink reply: %m");
×
328

329
        if (cid == VMADDR_CID_ANY)
×
330
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine %s has no AF_VSOCK CID assigned.", machine);
×
331

332
        return process_vsock_cid(cid, port);
×
333
}
334

335
static char *startswith_sep(const char *s, const char *prefix) {
6✔
336
        const char *p = startswith(s, prefix);
6✔
337

338
        if (p && IN_SET(*p, '/', '%'))
6✔
339
                return (char*) p + 1;
3✔
340

341
        return NULL;
342
}
343

344
static int run(int argc, char* argv[]) {
3✔
345

346
        log_setup();
3✔
347

348
        if (argc != 3)
3✔
349
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: host and port.");
×
350

351
        const char *host = argv[1], *port = argv[2];
3✔
352

353
        const char *p = startswith_sep(host, "vsock");
3✔
354
        if (p)
3✔
355
                return process_vsock_string(p, port);
×
356

357
        p = startswith_sep(host, "unix");
3✔
358
        if (p)
3✔
359
                return process_unix(p);
3✔
360

361
        p = startswith_sep(host, "vsock-mux");
×
362
        if (p)
×
363
                return process_vsock_mux(p, port);
×
364

365
        p = startswith_sep(host, "machine");
×
366
        if (p)
×
367
                return process_machine(p, port);
×
368

369
        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host);
×
370
}
371

372
DEFINE_MAIN_FUNCTION(run);
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc