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

systemd / systemd / 22246189288

20 Feb 2026 07:59PM UTC coverage: 72.238% (-0.2%) from 72.47%
22246189288

push

github

bluca
Add BNCF NewBook 11 ACCEL_MOUNT_MATRIX  to 60-sensor.hwdb

Corrects DE autorotation

Device description: https://www.bncfai.com/product/773/

313540 of 434040 relevant lines covered (72.24%)

1346131.39 hits per line

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

12.73
/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 "path-util.h"
15
#include "socket-util.h"
16
#include "stat-util.h"
17
#include "string-util.h"
18
#include "strv.h"
19
#include "time-util.h"
20

21
#define HEADER_READ_TIMEOUT_USEC (5 * USEC_PER_SEC)
22

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

26
        assert(cid != VMADDR_CID_ANY);
×
27
        assert(port);
×
28

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

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

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

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

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

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

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

58
        assert(host);
×
59
        assert(port);
×
60

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

65
        return process_vsock_cid(cid, port);
×
66
}
67

68
static int process_unix(const char *path) {
3✔
69
        int r;
3✔
70

71
        assert(path);
3✔
72

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

80
                path = prefixed;
81
        }
82

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

221
        assert(path);
×
222
        assert(port);
×
223

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

231
                path = prefixed;
232
        }
233

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

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

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

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

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

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

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

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

269
        assert(machine);
×
270
        assert(ret);
×
271

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

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

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

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

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

303
        *ret = TAKE_PTR(result);
×
304
        return 0;
×
305
}
306

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

310
        assert(machine);
×
311
        assert(port);
×
312

313
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *result = NULL;
×
314
        RuntimeScope scope = RUNTIME_SCOPE_USER;
×
315
        r = fetch_machine(machine, scope, &result);
×
316
        if (r == -ESRCH) {
×
317
                scope = RUNTIME_SCOPE_SYSTEM;
×
318
                r = fetch_machine(machine, scope, &result);
×
319
        }
320
        if (r < 0)
×
321
                return r;
322

323
        struct {
×
324
                uint32_t cid;
325
                const char *class;
326
                const char *service;
327
        } p = {
×
328
                .cid = VMADDR_CID_ANY,
329
        };
330

331
        static const sd_json_dispatch_field dispatch_table[] = {
×
332
                { "vSockCid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32,       voffsetof(p, cid),     0                 },
333
                { "class",    SD_JSON_VARIANT_STRING,   sd_json_dispatch_const_string, voffsetof(p, class),   SD_JSON_MANDATORY },
334
                { "service",  SD_JSON_VARIANT_STRING,   sd_json_dispatch_const_string, voffsetof(p, service), 0                 },
335
        };
336

337
        r = sd_json_dispatch(result, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &p);
×
338
        if (r < 0)
×
339
                return log_error_errno(r, "Failed to parse Varlink reply: %m");
×
340

341
        if (streq(p.class, "container")) {
×
342
                _cleanup_free_ char *path = NULL;
×
343

344
                if (!streq_ptr(p.service, "systemd-nspawn"))
×
345
                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Don't know how to SSH into '%s' container %s.", p.service, machine);
×
346

347
                r = runtime_directory_generic(scope, "systemd/nspawn/unix-export", &path);
×
348
                if (r < 0)
×
349
                        return log_error_errno(r, "Failed to determine runtime directory: %m");
×
350

351
                if (!path_extend(&path, machine, "ssh"))
×
352
                        return log_oom();
×
353

354
                r = is_socket(path);
×
355
                if (r < 0)
×
356
                        return log_error_errno(r, "Failed to check if '%s' exists and is a socket: %m", path);
×
357
                if (r == 0)
×
358
                        return log_error_errno(
×
359
                                        SYNTHETIC_ERRNO(ENOENT),
360
                                        "'%s' does not exist or is not a socket, are sshd and systemd-ssh-generator installed and enabled in the container?",
361
                                        path);
362

363
                return process_unix(path);
×
364
        }
365

366
        if (!streq(p.class, "vm"))
×
367
                return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Don't know how to SSH into machine %s with class '%s'.", machine, p.class);
×
368

369
        if (p.cid == VMADDR_CID_ANY)
×
370
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine %s has no AF_VSOCK CID assigned.", machine);
×
371

372
        return process_vsock_cid(p.cid, port);
×
373
}
374

375
static char *startswith_sep(const char *s, const char *prefix) {
6✔
376
        const char *p = startswith(s, prefix);
6✔
377

378
        if (p && IN_SET(*p, '/', '%'))
6✔
379
                return (char*) p + 1;
3✔
380

381
        return NULL;
382
}
383

384
static int run(int argc, char* argv[]) {
3✔
385

386
        log_setup();
3✔
387

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

391
        const char *host = argv[1], *port = argv[2];
3✔
392

393
        const char *p = startswith_sep(host, "vsock");
3✔
394
        if (p)
3✔
395
                return process_vsock_string(p, port);
×
396

397
        p = startswith_sep(host, "unix");
3✔
398
        if (p)
3✔
399
                return process_unix(p);
3✔
400

401
        p = startswith_sep(host, "vsock-mux");
×
402
        if (p)
×
403
                return process_vsock_mux(p, port);
×
404

405
        p = startswith_sep(host, "machine");
×
406
        if (p)
×
407
                return process_machine(p, port);
×
408

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

412
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