]>
Commit | Line | Data |
---|---|---|
d24ca4b8 MAL |
1 | /* |
2 | * QEMU System Emulator | |
3 | * | |
4 | * Copyright (c) 2003-2008 Fabrice Bellard | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
19 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
22 | * THE SOFTWARE. | |
23 | */ | |
922a01a0 | 24 | |
d24ca4b8 | 25 | #include "qemu/osdep.h" |
8228e353 | 26 | #include "chardev/char.h" |
d24ca4b8 MAL |
27 | #include "io/channel-socket.h" |
28 | #include "io/channel-tls.h" | |
194b7f0d | 29 | #include "io/net-listener.h" |
d24ca4b8 | 30 | #include "qemu/error-report.h" |
922a01a0 | 31 | #include "qemu/option.h" |
d24ca4b8 MAL |
32 | #include "qapi/error.h" |
33 | #include "qapi/clone-visitor.h" | |
9af23989 | 34 | #include "qapi/qapi-visit-sockets.h" |
d24ca4b8 | 35 | |
8228e353 | 36 | #include "chardev/char-io.h" |
d24ca4b8 MAL |
37 | |
38 | /***********************************************************/ | |
39 | /* TCP Net console */ | |
40 | ||
41 | #define TCP_MAX_FDS 16 | |
42 | ||
ce1230c0 PX |
43 | typedef struct { |
44 | char buf[21]; | |
45 | size_t buflen; | |
46 | } TCPChardevTelnetInit; | |
47 | ||
d24ca4b8 MAL |
48 | typedef struct { |
49 | Chardev parent; | |
50 | QIOChannel *ioc; /* Client I/O channel */ | |
51 | QIOChannelSocket *sioc; /* Client master channel */ | |
194b7f0d | 52 | QIONetListener *listener; |
a8aa6197 | 53 | GSource *hup_source; |
d24ca4b8 MAL |
54 | QCryptoTLSCreds *tls_creds; |
55 | int connected; | |
56 | int max_size; | |
57 | int do_telnetopt; | |
58 | int do_nodelay; | |
d24ca4b8 MAL |
59 | int *read_msgfds; |
60 | size_t read_msgfds_num; | |
61 | int *write_msgfds; | |
62 | size_t write_msgfds_num; | |
63 | ||
64 | SocketAddress *addr; | |
65 | bool is_listen; | |
66 | bool is_telnet; | |
ae92cbd5 | 67 | bool is_tn3270; |
ce1230c0 PX |
68 | GSource *telnet_source; |
69 | TCPChardevTelnetInit *telnet_init; | |
d24ca4b8 | 70 | |
2c716ba1 | 71 | GSource *reconnect_timer; |
d24ca4b8 MAL |
72 | int64_t reconnect_time; |
73 | bool connect_err_reported; | |
74 | } SocketChardev; | |
75 | ||
76 | #define SOCKET_CHARDEV(obj) \ | |
77 | OBJECT_CHECK(SocketChardev, (obj), TYPE_CHARDEV_SOCKET) | |
78 | ||
79 | static gboolean socket_reconnect_timeout(gpointer opaque); | |
ce1230c0 | 80 | static void tcp_chr_telnet_init(Chardev *chr); |
d24ca4b8 | 81 | |
2c716ba1 PX |
82 | static void tcp_chr_reconn_timer_cancel(SocketChardev *s) |
83 | { | |
84 | if (s->reconnect_timer) { | |
85 | g_source_destroy(s->reconnect_timer); | |
86 | g_source_unref(s->reconnect_timer); | |
87 | s->reconnect_timer = NULL; | |
88 | } | |
89 | } | |
90 | ||
d24ca4b8 MAL |
91 | static void qemu_chr_socket_restart_timer(Chardev *chr) |
92 | { | |
93 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
94 | char *name; | |
95 | ||
96 | assert(s->connected == 0); | |
d24ca4b8 | 97 | name = g_strdup_printf("chardev-socket-reconnect-%s", chr->label); |
2c716ba1 PX |
98 | s->reconnect_timer = qemu_chr_timeout_add_ms(chr, |
99 | s->reconnect_time * 1000, | |
100 | socket_reconnect_timeout, | |
101 | chr); | |
102 | g_source_set_name(s->reconnect_timer, name); | |
d24ca4b8 MAL |
103 | g_free(name); |
104 | } | |
105 | ||
106 | static void check_report_connect_error(Chardev *chr, | |
107 | Error *err) | |
108 | { | |
109 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
110 | ||
111 | if (!s->connect_err_reported) { | |
112 | error_report("Unable to connect character device %s: %s", | |
113 | chr->label, error_get_pretty(err)); | |
114 | s->connect_err_reported = true; | |
115 | } | |
116 | qemu_chr_socket_restart_timer(chr); | |
117 | } | |
118 | ||
194b7f0d DB |
119 | static void tcp_chr_accept(QIONetListener *listener, |
120 | QIOChannelSocket *cioc, | |
121 | void *opaque); | |
d24ca4b8 | 122 | |
b0a335e3 AN |
123 | static int tcp_chr_read_poll(void *opaque); |
124 | static void tcp_chr_disconnect(Chardev *chr); | |
125 | ||
d24ca4b8 MAL |
126 | /* Called with chr_write_lock held. */ |
127 | static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len) | |
128 | { | |
129 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
130 | ||
131 | if (s->connected) { | |
132 | int ret = io_channel_send_full(s->ioc, buf, len, | |
133 | s->write_msgfds, | |
134 | s->write_msgfds_num); | |
135 | ||
81e34930 XC |
136 | /* free the written msgfds in any cases |
137 | * other than ret < 0 && errno == EAGAIN | |
138 | */ | |
139 | if (!(ret < 0 && EAGAIN == errno) | |
140 | && s->write_msgfds_num) { | |
d24ca4b8 MAL |
141 | g_free(s->write_msgfds); |
142 | s->write_msgfds = 0; | |
143 | s->write_msgfds_num = 0; | |
144 | } | |
145 | ||
b0a335e3 AN |
146 | if (ret < 0 && errno != EAGAIN) { |
147 | if (tcp_chr_read_poll(chr) <= 0) { | |
148 | tcp_chr_disconnect(chr); | |
149 | return len; | |
150 | } /* else let the read handler finish it properly */ | |
151 | } | |
152 | ||
d24ca4b8 MAL |
153 | return ret; |
154 | } else { | |
155 | /* XXX: indicate an error ? */ | |
156 | return len; | |
157 | } | |
158 | } | |
159 | ||
160 | static int tcp_chr_read_poll(void *opaque) | |
161 | { | |
162 | Chardev *chr = CHARDEV(opaque); | |
163 | SocketChardev *s = SOCKET_CHARDEV(opaque); | |
164 | if (!s->connected) { | |
165 | return 0; | |
166 | } | |
167 | s->max_size = qemu_chr_be_can_write(chr); | |
168 | return s->max_size; | |
169 | } | |
170 | ||
d24ca4b8 MAL |
171 | static void tcp_chr_process_IAC_bytes(Chardev *chr, |
172 | SocketChardev *s, | |
173 | uint8_t *buf, int *size) | |
174 | { | |
ae92cbd5 JL |
175 | /* Handle any telnet or tn3270 client's basic IAC options. |
176 | * For telnet options, it satisfies char by char mode with no echo. | |
177 | * For tn3270 options, it satisfies binary mode with EOR. | |
178 | * All IAC options will be removed from the buf and the do_opt | |
179 | * pointer will be used to track the state of the width of the | |
180 | * IAC information. | |
d24ca4b8 | 181 | * |
ae92cbd5 JL |
182 | * RFC854: "All TELNET commands consist of at least a two byte sequence. |
183 | * The commands dealing with option negotiation are three byte sequences, | |
184 | * the third byte being the code for the option referenced." | |
185 | * "IAC BREAK", "IAC IP", "IAC NOP" and the double IAC are two bytes. | |
186 | * "IAC SB", "IAC SE" and "IAC EOR" are saved to split up data boundary | |
187 | * for tn3270. | |
188 | * NOP, Break and Interrupt Process(IP) might be encountered during a TN3270 | |
189 | * session, and NOP and IP need to be done later. | |
d24ca4b8 MAL |
190 | */ |
191 | ||
192 | int i; | |
193 | int j = 0; | |
194 | ||
195 | for (i = 0; i < *size; i++) { | |
196 | if (s->do_telnetopt > 1) { | |
197 | if ((unsigned char)buf[i] == IAC && s->do_telnetopt == 2) { | |
198 | /* Double IAC means send an IAC */ | |
199 | if (j != i) { | |
200 | buf[j] = buf[i]; | |
201 | } | |
202 | j++; | |
203 | s->do_telnetopt = 1; | |
204 | } else { | |
205 | if ((unsigned char)buf[i] == IAC_BREAK | |
206 | && s->do_telnetopt == 2) { | |
207 | /* Handle IAC break commands by sending a serial break */ | |
208 | qemu_chr_be_event(chr, CHR_EVENT_BREAK); | |
209 | s->do_telnetopt++; | |
ae92cbd5 JL |
210 | } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_EOR |
211 | || (unsigned char)buf[i] == IAC_SB | |
212 | || (unsigned char)buf[i] == IAC_SE) | |
213 | && s->do_telnetopt == 2) { | |
214 | buf[j++] = IAC; | |
215 | buf[j++] = buf[i]; | |
216 | s->do_telnetopt++; | |
217 | } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_IP | |
218 | || (unsigned char)buf[i] == IAC_NOP) | |
219 | && s->do_telnetopt == 2) { | |
220 | /* TODO: IP and NOP need to be implemented later. */ | |
221 | s->do_telnetopt++; | |
d24ca4b8 MAL |
222 | } |
223 | s->do_telnetopt++; | |
224 | } | |
225 | if (s->do_telnetopt >= 4) { | |
226 | s->do_telnetopt = 1; | |
227 | } | |
228 | } else { | |
229 | if ((unsigned char)buf[i] == IAC) { | |
230 | s->do_telnetopt = 2; | |
231 | } else { | |
232 | if (j != i) { | |
233 | buf[j] = buf[i]; | |
234 | } | |
235 | j++; | |
236 | } | |
237 | } | |
238 | } | |
239 | *size = j; | |
240 | } | |
241 | ||
242 | static int tcp_get_msgfds(Chardev *chr, int *fds, int num) | |
243 | { | |
244 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
245 | ||
246 | int to_copy = (s->read_msgfds_num < num) ? s->read_msgfds_num : num; | |
247 | ||
248 | assert(num <= TCP_MAX_FDS); | |
249 | ||
250 | if (to_copy) { | |
251 | int i; | |
252 | ||
253 | memcpy(fds, s->read_msgfds, to_copy * sizeof(int)); | |
254 | ||
255 | /* Close unused fds */ | |
256 | for (i = to_copy; i < s->read_msgfds_num; i++) { | |
257 | close(s->read_msgfds[i]); | |
258 | } | |
259 | ||
260 | g_free(s->read_msgfds); | |
261 | s->read_msgfds = 0; | |
262 | s->read_msgfds_num = 0; | |
263 | } | |
264 | ||
265 | return to_copy; | |
266 | } | |
267 | ||
268 | static int tcp_set_msgfds(Chardev *chr, int *fds, int num) | |
269 | { | |
270 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
271 | ||
272 | /* clear old pending fd array */ | |
273 | g_free(s->write_msgfds); | |
274 | s->write_msgfds = NULL; | |
275 | s->write_msgfds_num = 0; | |
276 | ||
277 | if (!s->connected || | |
278 | !qio_channel_has_feature(s->ioc, | |
279 | QIO_CHANNEL_FEATURE_FD_PASS)) { | |
280 | return -1; | |
281 | } | |
282 | ||
283 | if (num) { | |
284 | s->write_msgfds = g_new(int, num); | |
285 | memcpy(s->write_msgfds, fds, num * sizeof(int)); | |
286 | } | |
287 | ||
288 | s->write_msgfds_num = num; | |
289 | ||
290 | return 0; | |
291 | } | |
292 | ||
293 | static ssize_t tcp_chr_recv(Chardev *chr, char *buf, size_t len) | |
294 | { | |
295 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
296 | struct iovec iov = { .iov_base = buf, .iov_len = len }; | |
297 | int ret; | |
298 | size_t i; | |
299 | int *msgfds = NULL; | |
300 | size_t msgfds_num = 0; | |
301 | ||
302 | if (qio_channel_has_feature(s->ioc, QIO_CHANNEL_FEATURE_FD_PASS)) { | |
303 | ret = qio_channel_readv_full(s->ioc, &iov, 1, | |
304 | &msgfds, &msgfds_num, | |
305 | NULL); | |
306 | } else { | |
307 | ret = qio_channel_readv_full(s->ioc, &iov, 1, | |
308 | NULL, NULL, | |
309 | NULL); | |
310 | } | |
311 | ||
312 | if (ret == QIO_CHANNEL_ERR_BLOCK) { | |
313 | errno = EAGAIN; | |
314 | ret = -1; | |
315 | } else if (ret == -1) { | |
316 | errno = EIO; | |
317 | } | |
318 | ||
319 | if (msgfds_num) { | |
320 | /* close and clean read_msgfds */ | |
321 | for (i = 0; i < s->read_msgfds_num; i++) { | |
322 | close(s->read_msgfds[i]); | |
323 | } | |
324 | ||
325 | if (s->read_msgfds_num) { | |
326 | g_free(s->read_msgfds); | |
327 | } | |
328 | ||
329 | s->read_msgfds = msgfds; | |
330 | s->read_msgfds_num = msgfds_num; | |
331 | } | |
332 | ||
333 | for (i = 0; i < s->read_msgfds_num; i++) { | |
334 | int fd = s->read_msgfds[i]; | |
335 | if (fd < 0) { | |
336 | continue; | |
337 | } | |
338 | ||
339 | /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */ | |
340 | qemu_set_block(fd); | |
341 | ||
342 | #ifndef MSG_CMSG_CLOEXEC | |
343 | qemu_set_cloexec(fd); | |
344 | #endif | |
345 | } | |
346 | ||
347 | return ret; | |
348 | } | |
349 | ||
350 | static GSource *tcp_chr_add_watch(Chardev *chr, GIOCondition cond) | |
351 | { | |
352 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
353 | return qio_channel_create_watch(s->ioc, cond); | |
354 | } | |
355 | ||
dfe9ea20 MAL |
356 | static void remove_hup_source(SocketChardev *s) |
357 | { | |
358 | if (s->hup_source != NULL) { | |
359 | g_source_destroy(s->hup_source); | |
360 | g_source_unref(s->hup_source); | |
361 | s->hup_source = NULL; | |
362 | } | |
363 | } | |
364 | ||
d24ca4b8 MAL |
365 | static void tcp_chr_free_connection(Chardev *chr) |
366 | { | |
367 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
368 | int i; | |
369 | ||
d24ca4b8 MAL |
370 | if (s->read_msgfds_num) { |
371 | for (i = 0; i < s->read_msgfds_num; i++) { | |
372 | close(s->read_msgfds[i]); | |
373 | } | |
374 | g_free(s->read_msgfds); | |
375 | s->read_msgfds = NULL; | |
376 | s->read_msgfds_num = 0; | |
377 | } | |
378 | ||
dfe9ea20 | 379 | remove_hup_source(s); |
a8aa6197 | 380 | |
d24ca4b8 | 381 | tcp_set_msgfds(chr, NULL, 0); |
b19456dd | 382 | remove_fd_in_watch(chr); |
d24ca4b8 MAL |
383 | object_unref(OBJECT(s->sioc)); |
384 | s->sioc = NULL; | |
385 | object_unref(OBJECT(s->ioc)); | |
386 | s->ioc = NULL; | |
387 | g_free(chr->filename); | |
388 | chr->filename = NULL; | |
389 | s->connected = 0; | |
390 | } | |
391 | ||
392 | static char *SocketAddress_to_str(const char *prefix, SocketAddress *addr, | |
393 | bool is_listen, bool is_telnet) | |
394 | { | |
395 | switch (addr->type) { | |
bd269ebc | 396 | case SOCKET_ADDRESS_TYPE_INET: |
d24ca4b8 MAL |
397 | return g_strdup_printf("%s%s:%s:%s%s", prefix, |
398 | is_telnet ? "telnet" : "tcp", | |
bd269ebc MA |
399 | addr->u.inet.host, |
400 | addr->u.inet.port, | |
d24ca4b8 MAL |
401 | is_listen ? ",server" : ""); |
402 | break; | |
bd269ebc | 403 | case SOCKET_ADDRESS_TYPE_UNIX: |
d24ca4b8 | 404 | return g_strdup_printf("%sunix:%s%s", prefix, |
bd269ebc | 405 | addr->u.q_unix.path, |
d24ca4b8 MAL |
406 | is_listen ? ",server" : ""); |
407 | break; | |
bd269ebc MA |
408 | case SOCKET_ADDRESS_TYPE_FD: |
409 | return g_strdup_printf("%sfd:%s%s", prefix, addr->u.fd.str, | |
d24ca4b8 MAL |
410 | is_listen ? ",server" : ""); |
411 | break; | |
bd269ebc | 412 | case SOCKET_ADDRESS_TYPE_VSOCK: |
d2e49aad | 413 | return g_strdup_printf("%svsock:%s:%s", prefix, |
bd269ebc MA |
414 | addr->u.vsock.cid, |
415 | addr->u.vsock.port); | |
d24ca4b8 MAL |
416 | default: |
417 | abort(); | |
418 | } | |
419 | } | |
420 | ||
bbcde969 MAL |
421 | static void update_disconnected_filename(SocketChardev *s) |
422 | { | |
423 | Chardev *chr = CHARDEV(s); | |
424 | ||
425 | g_free(chr->filename); | |
90a6d17b MAL |
426 | if (s->addr) { |
427 | chr->filename = SocketAddress_to_str("disconnected:", s->addr, | |
428 | s->is_listen, s->is_telnet); | |
429 | } else { | |
430 | chr->filename = g_strdup("disconnected:socket"); | |
431 | } | |
bbcde969 MAL |
432 | } |
433 | ||
9cca7578 DB |
434 | /* NB may be called even if tcp_chr_connect has not been |
435 | * reached, due to TLS or telnet initialization failure, | |
436 | * so can *not* assume s->connected == true | |
437 | */ | |
d24ca4b8 MAL |
438 | static void tcp_chr_disconnect(Chardev *chr) |
439 | { | |
440 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
9cca7578 | 441 | bool emit_close = s->connected; |
d24ca4b8 MAL |
442 | |
443 | tcp_chr_free_connection(chr); | |
444 | ||
194b7f0d | 445 | if (s->listener) { |
3da9de5c PX |
446 | qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept, |
447 | chr, NULL, chr->gcontext); | |
d24ca4b8 | 448 | } |
bbcde969 | 449 | update_disconnected_filename(s); |
9cca7578 DB |
450 | if (emit_close) { |
451 | qemu_chr_be_event(chr, CHR_EVENT_CLOSED); | |
452 | } | |
d24ca4b8 MAL |
453 | if (s->reconnect_time) { |
454 | qemu_chr_socket_restart_timer(chr); | |
455 | } | |
456 | } | |
457 | ||
458 | static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque) | |
459 | { | |
460 | Chardev *chr = CHARDEV(opaque); | |
461 | SocketChardev *s = SOCKET_CHARDEV(opaque); | |
462 | uint8_t buf[CHR_READ_BUF_LEN]; | |
463 | int len, size; | |
464 | ||
465 | if (!s->connected || s->max_size <= 0) { | |
466 | return TRUE; | |
467 | } | |
468 | len = sizeof(buf); | |
469 | if (len > s->max_size) { | |
470 | len = s->max_size; | |
471 | } | |
472 | size = tcp_chr_recv(chr, (void *)buf, len); | |
c863fdec | 473 | if (size == 0 || (size == -1 && errno != EAGAIN)) { |
d24ca4b8 MAL |
474 | /* connection closed */ |
475 | tcp_chr_disconnect(chr); | |
476 | } else if (size > 0) { | |
477 | if (s->do_telnetopt) { | |
478 | tcp_chr_process_IAC_bytes(chr, s, buf, &size); | |
479 | } | |
480 | if (size > 0) { | |
481 | qemu_chr_be_write(chr, buf, size); | |
482 | } | |
483 | } | |
484 | ||
485 | return TRUE; | |
486 | } | |
487 | ||
a8aa6197 KK |
488 | static gboolean tcp_chr_hup(QIOChannel *channel, |
489 | GIOCondition cond, | |
490 | void *opaque) | |
491 | { | |
492 | Chardev *chr = CHARDEV(opaque); | |
493 | tcp_chr_disconnect(chr); | |
494 | return G_SOURCE_REMOVE; | |
495 | } | |
496 | ||
d24ca4b8 MAL |
497 | static int tcp_chr_sync_read(Chardev *chr, const uint8_t *buf, int len) |
498 | { | |
499 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
500 | int size; | |
501 | ||
502 | if (!s->connected) { | |
503 | return 0; | |
504 | } | |
505 | ||
bcdeb9be | 506 | qio_channel_set_blocking(s->ioc, true, NULL); |
d24ca4b8 | 507 | size = tcp_chr_recv(chr, (void *) buf, len); |
bcdeb9be | 508 | qio_channel_set_blocking(s->ioc, false, NULL); |
d24ca4b8 MAL |
509 | if (size == 0) { |
510 | /* connection closed */ | |
511 | tcp_chr_disconnect(chr); | |
512 | } | |
513 | ||
514 | return size; | |
515 | } | |
516 | ||
517 | static char *sockaddr_to_str(struct sockaddr_storage *ss, socklen_t ss_len, | |
518 | struct sockaddr_storage *ps, socklen_t ps_len, | |
519 | bool is_listen, bool is_telnet) | |
520 | { | |
521 | char shost[NI_MAXHOST], sserv[NI_MAXSERV]; | |
522 | char phost[NI_MAXHOST], pserv[NI_MAXSERV]; | |
523 | const char *left = "", *right = ""; | |
524 | ||
525 | switch (ss->ss_family) { | |
526 | #ifndef _WIN32 | |
527 | case AF_UNIX: | |
528 | return g_strdup_printf("unix:%s%s", | |
529 | ((struct sockaddr_un *)(ss))->sun_path, | |
530 | is_listen ? ",server" : ""); | |
531 | #endif | |
532 | case AF_INET6: | |
533 | left = "["; | |
534 | right = "]"; | |
535 | /* fall through */ | |
536 | case AF_INET: | |
537 | getnameinfo((struct sockaddr *) ss, ss_len, shost, sizeof(shost), | |
538 | sserv, sizeof(sserv), NI_NUMERICHOST | NI_NUMERICSERV); | |
539 | getnameinfo((struct sockaddr *) ps, ps_len, phost, sizeof(phost), | |
540 | pserv, sizeof(pserv), NI_NUMERICHOST | NI_NUMERICSERV); | |
541 | return g_strdup_printf("%s:%s%s%s:%s%s <-> %s%s%s:%s", | |
542 | is_telnet ? "telnet" : "tcp", | |
543 | left, shost, right, sserv, | |
544 | is_listen ? ",server" : "", | |
545 | left, phost, right, pserv); | |
546 | ||
547 | default: | |
548 | return g_strdup_printf("unknown"); | |
549 | } | |
550 | } | |
551 | ||
dfe9ea20 MAL |
552 | static void update_ioc_handlers(SocketChardev *s) |
553 | { | |
554 | Chardev *chr = CHARDEV(s); | |
555 | ||
556 | if (!s->connected) { | |
557 | return; | |
558 | } | |
559 | ||
560 | remove_fd_in_watch(chr); | |
561 | chr->gsource = io_add_watch_poll(chr, s->ioc, | |
562 | tcp_chr_read_poll, | |
563 | tcp_chr_read, chr, | |
564 | chr->gcontext); | |
565 | ||
566 | remove_hup_source(s); | |
567 | s->hup_source = qio_channel_create_watch(s->ioc, G_IO_HUP); | |
568 | g_source_set_callback(s->hup_source, (GSourceFunc)tcp_chr_hup, | |
569 | chr, NULL); | |
570 | g_source_attach(s->hup_source, chr->gcontext); | |
571 | } | |
572 | ||
d24ca4b8 MAL |
573 | static void tcp_chr_connect(void *opaque) |
574 | { | |
575 | Chardev *chr = CHARDEV(opaque); | |
576 | SocketChardev *s = SOCKET_CHARDEV(opaque); | |
577 | ||
578 | g_free(chr->filename); | |
579 | chr->filename = sockaddr_to_str( | |
580 | &s->sioc->localAddr, s->sioc->localAddrLen, | |
581 | &s->sioc->remoteAddr, s->sioc->remoteAddrLen, | |
582 | s->is_listen, s->is_telnet); | |
583 | ||
584 | s->connected = 1; | |
dfe9ea20 | 585 | update_ioc_handlers(s); |
63618135 | 586 | qemu_chr_be_event(chr, CHR_EVENT_OPENED); |
d24ca4b8 MAL |
587 | } |
588 | ||
ce1230c0 PX |
589 | static void tcp_chr_telnet_destroy(SocketChardev *s) |
590 | { | |
591 | if (s->telnet_source) { | |
592 | g_source_destroy(s->telnet_source); | |
593 | g_source_unref(s->telnet_source); | |
594 | s->telnet_source = NULL; | |
595 | } | |
596 | } | |
597 | ||
bb86d05f | 598 | static void tcp_chr_update_read_handler(Chardev *chr) |
d24ca4b8 MAL |
599 | { |
600 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
601 | ||
3da9de5c PX |
602 | if (s->listener) { |
603 | /* | |
604 | * It's possible that chardev context is changed in | |
605 | * qemu_chr_be_update_read_handlers(). Reset it for QIO net | |
606 | * listener if there is. | |
607 | */ | |
608 | qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept, | |
609 | chr, NULL, chr->gcontext); | |
610 | } | |
611 | ||
ce1230c0 PX |
612 | if (s->telnet_source) { |
613 | tcp_chr_telnet_init(CHARDEV(s)); | |
614 | } | |
615 | ||
dfe9ea20 | 616 | update_ioc_handlers(s); |
d24ca4b8 MAL |
617 | } |
618 | ||
d24ca4b8 MAL |
619 | static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc, |
620 | GIOCondition cond G_GNUC_UNUSED, | |
621 | gpointer user_data) | |
622 | { | |
ce1230c0 PX |
623 | SocketChardev *s = user_data; |
624 | Chardev *chr = CHARDEV(s); | |
625 | TCPChardevTelnetInit *init = s->telnet_init; | |
d24ca4b8 MAL |
626 | ssize_t ret; |
627 | ||
ce1230c0 PX |
628 | assert(init); |
629 | ||
d24ca4b8 MAL |
630 | ret = qio_channel_write(ioc, init->buf, init->buflen, NULL); |
631 | if (ret < 0) { | |
632 | if (ret == QIO_CHANNEL_ERR_BLOCK) { | |
633 | ret = 0; | |
634 | } else { | |
ce1230c0 | 635 | tcp_chr_disconnect(chr); |
8b2ec54f | 636 | goto end; |
d24ca4b8 MAL |
637 | } |
638 | } | |
639 | init->buflen -= ret; | |
640 | ||
641 | if (init->buflen == 0) { | |
ce1230c0 | 642 | tcp_chr_connect(chr); |
8b2ec54f | 643 | goto end; |
d24ca4b8 MAL |
644 | } |
645 | ||
646 | memmove(init->buf, init->buf + ret, init->buflen); | |
647 | ||
8b2ec54f PX |
648 | return G_SOURCE_CONTINUE; |
649 | ||
650 | end: | |
ce1230c0 PX |
651 | g_free(s->telnet_init); |
652 | s->telnet_init = NULL; | |
653 | g_source_unref(s->telnet_source); | |
654 | s->telnet_source = NULL; | |
8b2ec54f | 655 | return G_SOURCE_REMOVE; |
d24ca4b8 MAL |
656 | } |
657 | ||
658 | static void tcp_chr_telnet_init(Chardev *chr) | |
659 | { | |
660 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
ce1230c0 | 661 | TCPChardevTelnetInit *init; |
d24ca4b8 MAL |
662 | size_t n = 0; |
663 | ||
ce1230c0 PX |
664 | /* Destroy existing task */ |
665 | tcp_chr_telnet_destroy(s); | |
666 | ||
667 | if (s->telnet_init) { | |
668 | /* We are possibly during a handshake already */ | |
669 | goto cont; | |
670 | } | |
671 | ||
672 | s->telnet_init = g_new0(TCPChardevTelnetInit, 1); | |
673 | init = s->telnet_init; | |
674 | ||
d24ca4b8 MAL |
675 | #define IACSET(x, a, b, c) \ |
676 | do { \ | |
677 | x[n++] = a; \ | |
678 | x[n++] = b; \ | |
679 | x[n++] = c; \ | |
680 | } while (0) | |
681 | ||
ae92cbd5 JL |
682 | if (!s->is_tn3270) { |
683 | init->buflen = 12; | |
684 | /* Prep the telnet negotion to put telnet in binary, | |
685 | * no echo, single char mode */ | |
686 | IACSET(init->buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */ | |
687 | IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */ | |
688 | IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */ | |
689 | IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */ | |
690 | } else { | |
691 | init->buflen = 21; | |
692 | /* Prep the TN3270 negotion based on RFC1576 */ | |
693 | IACSET(init->buf, 0xff, 0xfd, 0x19); /* IAC DO EOR */ | |
694 | IACSET(init->buf, 0xff, 0xfb, 0x19); /* IAC WILL EOR */ | |
695 | IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO BINARY */ | |
696 | IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL BINARY */ | |
697 | IACSET(init->buf, 0xff, 0xfd, 0x18); /* IAC DO TERMINAL TYPE */ | |
698 | IACSET(init->buf, 0xff, 0xfa, 0x18); /* IAC SB TERMINAL TYPE */ | |
699 | IACSET(init->buf, 0x01, 0xff, 0xf0); /* SEND IAC SE */ | |
700 | } | |
d24ca4b8 MAL |
701 | |
702 | #undef IACSET | |
703 | ||
ce1230c0 PX |
704 | cont: |
705 | s->telnet_source = qio_channel_add_watch_source(s->ioc, G_IO_OUT, | |
706 | tcp_chr_telnet_init_io, | |
707 | s, NULL, | |
708 | chr->gcontext); | |
d24ca4b8 MAL |
709 | } |
710 | ||
711 | ||
712 | static void tcp_chr_tls_handshake(QIOTask *task, | |
713 | gpointer user_data) | |
714 | { | |
715 | Chardev *chr = user_data; | |
716 | SocketChardev *s = user_data; | |
717 | ||
718 | if (qio_task_propagate_error(task, NULL)) { | |
719 | tcp_chr_disconnect(chr); | |
720 | } else { | |
63bab2b6 | 721 | if (s->do_telnetopt) { |
d24ca4b8 MAL |
722 | tcp_chr_telnet_init(chr); |
723 | } else { | |
724 | tcp_chr_connect(chr); | |
725 | } | |
726 | } | |
727 | } | |
728 | ||
729 | ||
730 | static void tcp_chr_tls_init(Chardev *chr) | |
731 | { | |
732 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
733 | QIOChannelTLS *tioc; | |
734 | Error *err = NULL; | |
735 | gchar *name; | |
736 | ||
737 | if (s->is_listen) { | |
738 | tioc = qio_channel_tls_new_server( | |
739 | s->ioc, s->tls_creds, | |
740 | NULL, /* XXX Use an ACL */ | |
741 | &err); | |
742 | } else { | |
743 | tioc = qio_channel_tls_new_client( | |
744 | s->ioc, s->tls_creds, | |
bd269ebc | 745 | s->addr->u.inet.host, |
d24ca4b8 MAL |
746 | &err); |
747 | } | |
748 | if (tioc == NULL) { | |
749 | error_free(err); | |
750 | tcp_chr_disconnect(chr); | |
751 | return; | |
752 | } | |
753 | name = g_strdup_printf("chardev-tls-%s-%s", | |
754 | s->is_listen ? "server" : "client", | |
755 | chr->label); | |
756 | qio_channel_set_name(QIO_CHANNEL(tioc), name); | |
757 | g_free(name); | |
758 | object_unref(OBJECT(s->ioc)); | |
759 | s->ioc = QIO_CHANNEL(tioc); | |
760 | ||
761 | qio_channel_tls_handshake(tioc, | |
762 | tcp_chr_tls_handshake, | |
763 | chr, | |
1939ccda | 764 | NULL, |
05b6cc4a | 765 | chr->gcontext); |
d24ca4b8 MAL |
766 | } |
767 | ||
768 | ||
769 | static void tcp_chr_set_client_ioc_name(Chardev *chr, | |
770 | QIOChannelSocket *sioc) | |
771 | { | |
772 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
773 | char *name; | |
774 | name = g_strdup_printf("chardev-tcp-%s-%s", | |
775 | s->is_listen ? "server" : "client", | |
776 | chr->label); | |
777 | qio_channel_set_name(QIO_CHANNEL(sioc), name); | |
778 | g_free(name); | |
779 | ||
780 | } | |
781 | ||
782 | static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc) | |
783 | { | |
784 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
785 | ||
786 | if (s->ioc != NULL) { | |
787 | return -1; | |
788 | } | |
789 | ||
790 | s->ioc = QIO_CHANNEL(sioc); | |
791 | object_ref(OBJECT(sioc)); | |
792 | s->sioc = sioc; | |
793 | object_ref(OBJECT(sioc)); | |
794 | ||
795 | qio_channel_set_blocking(s->ioc, false, NULL); | |
796 | ||
797 | if (s->do_nodelay) { | |
798 | qio_channel_set_delay(s->ioc, false); | |
799 | } | |
194b7f0d | 800 | if (s->listener) { |
3da9de5c PX |
801 | qio_net_listener_set_client_func_full(s->listener, NULL, NULL, |
802 | NULL, chr->gcontext); | |
d24ca4b8 MAL |
803 | } |
804 | ||
805 | if (s->tls_creds) { | |
806 | tcp_chr_tls_init(chr); | |
807 | } else { | |
808 | if (s->do_telnetopt) { | |
809 | tcp_chr_telnet_init(chr); | |
810 | } else { | |
811 | tcp_chr_connect(chr); | |
812 | } | |
813 | } | |
814 | ||
815 | return 0; | |
816 | } | |
817 | ||
818 | ||
819 | static int tcp_chr_add_client(Chardev *chr, int fd) | |
820 | { | |
821 | int ret; | |
822 | QIOChannelSocket *sioc; | |
823 | ||
824 | sioc = qio_channel_socket_new_fd(fd, NULL); | |
825 | if (!sioc) { | |
826 | return -1; | |
827 | } | |
828 | tcp_chr_set_client_ioc_name(chr, sioc); | |
829 | ret = tcp_chr_new_client(chr, sioc); | |
830 | object_unref(OBJECT(sioc)); | |
831 | return ret; | |
832 | } | |
833 | ||
194b7f0d DB |
834 | static void tcp_chr_accept(QIONetListener *listener, |
835 | QIOChannelSocket *cioc, | |
836 | void *opaque) | |
d24ca4b8 MAL |
837 | { |
838 | Chardev *chr = CHARDEV(opaque); | |
d24ca4b8 | 839 | |
194b7f0d DB |
840 | tcp_chr_set_client_ioc_name(chr, cioc); |
841 | tcp_chr_new_client(chr, cioc); | |
d24ca4b8 MAL |
842 | } |
843 | ||
844 | static int tcp_chr_wait_connected(Chardev *chr, Error **errp) | |
845 | { | |
846 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
847 | QIOChannelSocket *sioc; | |
848 | ||
849 | /* It can't wait on s->connected, since it is set asynchronously | |
850 | * in TLS and telnet cases, only wait for an accepted socket */ | |
851 | while (!s->ioc) { | |
852 | if (s->is_listen) { | |
c51c4f88 AF |
853 | info_report("QEMU waiting for connection on: %s", |
854 | chr->filename); | |
194b7f0d DB |
855 | sioc = qio_net_listener_wait_client(s->listener); |
856 | tcp_chr_set_client_ioc_name(chr, sioc); | |
857 | tcp_chr_new_client(chr, sioc); | |
858 | object_unref(OBJECT(sioc)); | |
d24ca4b8 MAL |
859 | } else { |
860 | sioc = qio_channel_socket_new(); | |
861 | tcp_chr_set_client_ioc_name(chr, sioc); | |
862 | if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) { | |
863 | object_unref(OBJECT(sioc)); | |
864 | return -1; | |
865 | } | |
866 | tcp_chr_new_client(chr, sioc); | |
867 | object_unref(OBJECT(sioc)); | |
868 | } | |
869 | } | |
870 | ||
871 | return 0; | |
872 | } | |
873 | ||
874 | static void char_socket_finalize(Object *obj) | |
875 | { | |
876 | Chardev *chr = CHARDEV(obj); | |
877 | SocketChardev *s = SOCKET_CHARDEV(obj); | |
878 | ||
879 | tcp_chr_free_connection(chr); | |
2c716ba1 | 880 | tcp_chr_reconn_timer_cancel(s); |
d24ca4b8 | 881 | qapi_free_SocketAddress(s->addr); |
ce1230c0 PX |
882 | tcp_chr_telnet_destroy(s); |
883 | g_free(s->telnet_init); | |
194b7f0d | 884 | if (s->listener) { |
3da9de5c PX |
885 | qio_net_listener_set_client_func_full(s->listener, NULL, NULL, |
886 | NULL, chr->gcontext); | |
194b7f0d | 887 | object_unref(OBJECT(s->listener)); |
d24ca4b8 MAL |
888 | } |
889 | if (s->tls_creds) { | |
890 | object_unref(OBJECT(s->tls_creds)); | |
891 | } | |
892 | ||
893 | qemu_chr_be_event(chr, CHR_EVENT_CLOSED); | |
894 | } | |
895 | ||
896 | static void qemu_chr_socket_connected(QIOTask *task, void *opaque) | |
897 | { | |
898 | QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task)); | |
899 | Chardev *chr = CHARDEV(opaque); | |
900 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
901 | Error *err = NULL; | |
902 | ||
903 | if (qio_task_propagate_error(task, &err)) { | |
904 | check_report_connect_error(chr, err); | |
905 | error_free(err); | |
906 | goto cleanup; | |
907 | } | |
908 | ||
909 | s->connect_err_reported = false; | |
910 | tcp_chr_new_client(chr, sioc); | |
911 | ||
912 | cleanup: | |
913 | object_unref(OBJECT(sioc)); | |
914 | } | |
915 | ||
3e7d4d20 PX |
916 | static void tcp_chr_connect_async(Chardev *chr) |
917 | { | |
918 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
919 | QIOChannelSocket *sioc; | |
920 | ||
921 | sioc = qio_channel_socket_new(); | |
922 | tcp_chr_set_client_ioc_name(chr, sioc); | |
923 | qio_channel_socket_connect_async(sioc, s->addr, | |
924 | qemu_chr_socket_connected, | |
925 | chr, NULL, chr->gcontext); | |
926 | } | |
927 | ||
d24ca4b8 MAL |
928 | static gboolean socket_reconnect_timeout(gpointer opaque) |
929 | { | |
930 | Chardev *chr = CHARDEV(opaque); | |
931 | SocketChardev *s = SOCKET_CHARDEV(opaque); | |
d24ca4b8 | 932 | |
2c716ba1 PX |
933 | g_source_unref(s->reconnect_timer); |
934 | s->reconnect_timer = NULL; | |
d24ca4b8 MAL |
935 | |
936 | if (chr->be_open) { | |
937 | return false; | |
938 | } | |
939 | ||
3e7d4d20 | 940 | tcp_chr_connect_async(chr); |
d24ca4b8 MAL |
941 | |
942 | return false; | |
943 | } | |
944 | ||
945 | static void qmp_chardev_open_socket(Chardev *chr, | |
946 | ChardevBackend *backend, | |
947 | bool *be_opened, | |
948 | Error **errp) | |
949 | { | |
950 | SocketChardev *s = SOCKET_CHARDEV(chr); | |
951 | ChardevSocket *sock = backend->u.socket.data; | |
d24ca4b8 MAL |
952 | bool do_nodelay = sock->has_nodelay ? sock->nodelay : false; |
953 | bool is_listen = sock->has_server ? sock->server : true; | |
954 | bool is_telnet = sock->has_telnet ? sock->telnet : false; | |
ae92cbd5 | 955 | bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false; |
d24ca4b8 MAL |
956 | bool is_waitconnect = sock->has_wait ? sock->wait : false; |
957 | int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0; | |
958 | QIOChannelSocket *sioc = NULL; | |
bd269ebc | 959 | SocketAddress *addr; |
d24ca4b8 | 960 | |
d24ca4b8 MAL |
961 | s->is_listen = is_listen; |
962 | s->is_telnet = is_telnet; | |
ae92cbd5 | 963 | s->is_tn3270 = is_tn3270; |
d24ca4b8 MAL |
964 | s->do_nodelay = do_nodelay; |
965 | if (sock->tls_creds) { | |
966 | Object *creds; | |
967 | creds = object_resolve_path_component( | |
968 | object_get_objects_root(), sock->tls_creds); | |
969 | if (!creds) { | |
970 | error_setg(errp, "No TLS credentials with id '%s'", | |
971 | sock->tls_creds); | |
972 | goto error; | |
973 | } | |
974 | s->tls_creds = (QCryptoTLSCreds *) | |
975 | object_dynamic_cast(creds, | |
976 | TYPE_QCRYPTO_TLS_CREDS); | |
977 | if (!s->tls_creds) { | |
978 | error_setg(errp, "Object with id '%s' is not TLS credentials", | |
979 | sock->tls_creds); | |
980 | goto error; | |
981 | } | |
982 | object_ref(OBJECT(s->tls_creds)); | |
983 | if (is_listen) { | |
984 | if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) { | |
985 | error_setg(errp, "%s", | |
986 | "Expected TLS credentials for server endpoint"); | |
987 | goto error; | |
988 | } | |
989 | } else { | |
990 | if (s->tls_creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) { | |
991 | error_setg(errp, "%s", | |
992 | "Expected TLS credentials for client endpoint"); | |
993 | goto error; | |
994 | } | |
995 | } | |
996 | } | |
997 | ||
bd269ebc | 998 | s->addr = addr = socket_address_flatten(sock->addr); |
d24ca4b8 MAL |
999 | |
1000 | qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE); | |
ca0b64e5 | 1001 | /* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */ |
bd269ebc | 1002 | if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) { |
d24ca4b8 MAL |
1003 | qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_FD_PASS); |
1004 | } | |
1005 | ||
1006 | /* be isn't opened until we get a connection */ | |
1007 | *be_opened = false; | |
1008 | ||
bbcde969 | 1009 | update_disconnected_filename(s); |
d24ca4b8 MAL |
1010 | |
1011 | if (is_listen) { | |
ae92cbd5 | 1012 | if (is_telnet || is_tn3270) { |
d24ca4b8 MAL |
1013 | s->do_telnetopt = 1; |
1014 | } | |
1015 | } else if (reconnect > 0) { | |
1016 | s->reconnect_time = reconnect; | |
1017 | } | |
1018 | ||
5573f98f MAL |
1019 | if (s->reconnect_time) { |
1020 | tcp_chr_connect_async(chr); | |
1021 | } else { | |
d24ca4b8 MAL |
1022 | if (s->is_listen) { |
1023 | char *name; | |
194b7f0d | 1024 | s->listener = qio_net_listener_new(); |
d24ca4b8 MAL |
1025 | |
1026 | name = g_strdup_printf("chardev-tcp-listener-%s", chr->label); | |
194b7f0d | 1027 | qio_net_listener_set_name(s->listener, name); |
d24ca4b8 MAL |
1028 | g_free(name); |
1029 | ||
194b7f0d DB |
1030 | if (qio_net_listener_open_sync(s->listener, s->addr, errp) < 0) { |
1031 | object_unref(OBJECT(s->listener)); | |
1032 | s->listener = NULL; | |
d24ca4b8 MAL |
1033 | goto error; |
1034 | } | |
bc763d71 MAL |
1035 | |
1036 | qapi_free_SocketAddress(s->addr); | |
194b7f0d | 1037 | s->addr = socket_local_address(s->listener->sioc[0]->fd, errp); |
bc763d71 MAL |
1038 | update_disconnected_filename(s); |
1039 | ||
d24ca4b8 MAL |
1040 | if (is_waitconnect && |
1041 | qemu_chr_wait_connected(chr, errp) < 0) { | |
1042 | return; | |
1043 | } | |
1044 | if (!s->ioc) { | |
3da9de5c PX |
1045 | qio_net_listener_set_client_func_full(s->listener, |
1046 | tcp_chr_accept, | |
1047 | chr, NULL, | |
1048 | chr->gcontext); | |
d24ca4b8 MAL |
1049 | } |
1050 | } else if (qemu_chr_wait_connected(chr, errp) < 0) { | |
1051 | goto error; | |
1052 | } | |
1053 | } | |
1054 | ||
1055 | return; | |
1056 | ||
1057 | error: | |
1058 | if (sioc) { | |
1059 | object_unref(OBJECT(sioc)); | |
1060 | } | |
1061 | } | |
1062 | ||
1063 | static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend, | |
1064 | Error **errp) | |
1065 | { | |
1066 | bool is_listen = qemu_opt_get_bool(opts, "server", false); | |
1067 | bool is_waitconnect = is_listen && qemu_opt_get_bool(opts, "wait", true); | |
1068 | bool is_telnet = qemu_opt_get_bool(opts, "telnet", false); | |
ae92cbd5 | 1069 | bool is_tn3270 = qemu_opt_get_bool(opts, "tn3270", false); |
d24ca4b8 MAL |
1070 | bool do_nodelay = !qemu_opt_get_bool(opts, "delay", true); |
1071 | int64_t reconnect = qemu_opt_get_number(opts, "reconnect", 0); | |
1072 | const char *path = qemu_opt_get(opts, "path"); | |
1073 | const char *host = qemu_opt_get(opts, "host"); | |
1074 | const char *port = qemu_opt_get(opts, "port"); | |
0935700f | 1075 | const char *fd = qemu_opt_get(opts, "fd"); |
d24ca4b8 | 1076 | const char *tls_creds = qemu_opt_get(opts, "tls-creds"); |
dfd100f2 | 1077 | SocketAddressLegacy *addr; |
d24ca4b8 MAL |
1078 | ChardevSocket *sock; |
1079 | ||
0935700f | 1080 | if ((!!path + !!fd + !!host) != 1) { |
9bb4060c | 1081 | error_setg(errp, |
0935700f | 1082 | "Exactly one of 'path', 'fd' or 'host' required"); |
9bb4060c DB |
1083 | return; |
1084 | } | |
1085 | ||
d24ca4b8 | 1086 | backend->type = CHARDEV_BACKEND_KIND_SOCKET; |
9bb4060c DB |
1087 | if (path) { |
1088 | if (tls_creds) { | |
1089 | error_setg(errp, "TLS can only be used over TCP socket"); | |
d24ca4b8 MAL |
1090 | return; |
1091 | } | |
9bb4060c | 1092 | } else if (host) { |
d24ca4b8 MAL |
1093 | if (!port) { |
1094 | error_setg(errp, "chardev: socket: no port given"); | |
1095 | return; | |
1096 | } | |
0935700f DB |
1097 | } else if (fd) { |
1098 | /* We don't know what host to validate against when in client mode */ | |
1099 | if (tls_creds && !is_listen) { | |
1100 | error_setg(errp, "TLS can not be used with pre-opened client FD"); | |
1101 | return; | |
1102 | } | |
d24ca4b8 | 1103 | } else { |
9bb4060c | 1104 | g_assert_not_reached(); |
d24ca4b8 MAL |
1105 | } |
1106 | ||
1107 | sock = backend->u.socket.data = g_new0(ChardevSocket, 1); | |
1108 | qemu_chr_parse_common(opts, qapi_ChardevSocket_base(sock)); | |
1109 | ||
1110 | sock->has_nodelay = true; | |
1111 | sock->nodelay = do_nodelay; | |
1112 | sock->has_server = true; | |
1113 | sock->server = is_listen; | |
1114 | sock->has_telnet = true; | |
1115 | sock->telnet = is_telnet; | |
ae92cbd5 JL |
1116 | sock->has_tn3270 = true; |
1117 | sock->tn3270 = is_tn3270; | |
d24ca4b8 MAL |
1118 | sock->has_wait = true; |
1119 | sock->wait = is_waitconnect; | |
1120 | sock->has_reconnect = true; | |
1121 | sock->reconnect = reconnect; | |
1122 | sock->tls_creds = g_strdup(tls_creds); | |
1123 | ||
dfd100f2 | 1124 | addr = g_new0(SocketAddressLegacy, 1); |
d24ca4b8 MAL |
1125 | if (path) { |
1126 | UnixSocketAddress *q_unix; | |
dfd100f2 | 1127 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_UNIX; |
d24ca4b8 MAL |
1128 | q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1); |
1129 | q_unix->path = g_strdup(path); | |
9bb4060c | 1130 | } else if (host) { |
dfd100f2 | 1131 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_INET; |
d24ca4b8 MAL |
1132 | addr->u.inet.data = g_new(InetSocketAddress, 1); |
1133 | *addr->u.inet.data = (InetSocketAddress) { | |
1134 | .host = g_strdup(host), | |
1135 | .port = g_strdup(port), | |
1136 | .has_to = qemu_opt_get(opts, "to"), | |
1137 | .to = qemu_opt_get_number(opts, "to", 0), | |
1138 | .has_ipv4 = qemu_opt_get(opts, "ipv4"), | |
1139 | .ipv4 = qemu_opt_get_bool(opts, "ipv4", 0), | |
1140 | .has_ipv6 = qemu_opt_get(opts, "ipv6"), | |
1141 | .ipv6 = qemu_opt_get_bool(opts, "ipv6", 0), | |
1142 | }; | |
0935700f DB |
1143 | } else if (fd) { |
1144 | addr->type = SOCKET_ADDRESS_LEGACY_KIND_FD; | |
1145 | addr->u.fd.data = g_new(String, 1); | |
1146 | addr->u.fd.data->str = g_strdup(fd); | |
9bb4060c DB |
1147 | } else { |
1148 | g_assert_not_reached(); | |
d24ca4b8 MAL |
1149 | } |
1150 | sock->addr = addr; | |
1151 | } | |
1152 | ||
123676e9 MAL |
1153 | static void |
1154 | char_socket_get_addr(Object *obj, Visitor *v, const char *name, | |
1155 | void *opaque, Error **errp) | |
1156 | { | |
1157 | SocketChardev *s = SOCKET_CHARDEV(obj); | |
1158 | ||
1159 | visit_type_SocketAddress(v, name, &s->addr, errp); | |
1160 | } | |
1161 | ||
da2d19b0 MAL |
1162 | static bool |
1163 | char_socket_get_connected(Object *obj, Error **errp) | |
1164 | { | |
1165 | SocketChardev *s = SOCKET_CHARDEV(obj); | |
1166 | ||
1167 | return s->connected; | |
1168 | } | |
1169 | ||
d24ca4b8 MAL |
1170 | static void char_socket_class_init(ObjectClass *oc, void *data) |
1171 | { | |
1172 | ChardevClass *cc = CHARDEV_CLASS(oc); | |
1173 | ||
1174 | cc->parse = qemu_chr_parse_socket; | |
1175 | cc->open = qmp_chardev_open_socket; | |
1176 | cc->chr_wait_connected = tcp_chr_wait_connected; | |
1177 | cc->chr_write = tcp_chr_write; | |
1178 | cc->chr_sync_read = tcp_chr_sync_read; | |
1179 | cc->chr_disconnect = tcp_chr_disconnect; | |
1180 | cc->get_msgfds = tcp_get_msgfds; | |
1181 | cc->set_msgfds = tcp_set_msgfds; | |
1182 | cc->chr_add_client = tcp_chr_add_client; | |
1183 | cc->chr_add_watch = tcp_chr_add_watch; | |
1184 | cc->chr_update_read_handler = tcp_chr_update_read_handler; | |
123676e9 MAL |
1185 | |
1186 | object_class_property_add(oc, "addr", "SocketAddress", | |
1187 | char_socket_get_addr, NULL, | |
1188 | NULL, NULL, &error_abort); | |
da2d19b0 MAL |
1189 | |
1190 | object_class_property_add_bool(oc, "connected", char_socket_get_connected, | |
1191 | NULL, &error_abort); | |
d24ca4b8 MAL |
1192 | } |
1193 | ||
1194 | static const TypeInfo char_socket_type_info = { | |
1195 | .name = TYPE_CHARDEV_SOCKET, | |
1196 | .parent = TYPE_CHARDEV, | |
1197 | .instance_size = sizeof(SocketChardev), | |
1198 | .instance_finalize = char_socket_finalize, | |
1199 | .class_init = char_socket_class_init, | |
1200 | }; | |
1201 | ||
1202 | static void register_types(void) | |
1203 | { | |
1204 | type_register_static(&char_socket_type_info); | |
1205 | } | |
1206 | ||
1207 | type_init(register_types); |